Store file in Download Folder Android 10, 11, 12+ in Flutter.

Store file in Download Folder Android 10, 11, 12+ in Flutter.

How to save file in Download, Music, Pictures and Public Directories in Android 10, Android 11, Android 12 and above.

In flutter you might has faced an issue with saving file in public storage on Android 10+. This can be frustrating because there is no way to save the files using ``` path_provider




%[https://vimeo.com/737275685?width=820&color=ED5565]

#### We are going to create a sample, where we first download the file from a URL using ```Dio``` package and then we are going to store that inside 
```Download/AppName``` folder.

#### Steps:

#### 1. Add relevant packages in pubspec.yaml.
#### 2. Add relevant storage and network permissions in AndroidManifest.xml.
#### 3. Call a native android function from flutter.
#### 4. Request storage runtime permission.
#### 5. Download file to local directory.
#### 6. Move file to Download/AppName folder.

---
Step 1:

 Add relevant packages in ```
pubspec.yaml```

path_provider: ^2.0.11 dio: ^4.0.6 device_info_plus: ^4.0.1


Step 2:

 Add relevant storage and network permissions in `AndroidManifest.xml`.

Step 3:

Now, We have to request runtime storage permission. We are not going to request the permissions from Flutter rather we are going to call native function of android from flutter, That will request the permission. We have to request the permissions from native function otherwise this won't work.


- Create a `Button` from which we will request the permission.

ElevatedButton( onPressed: requestStoragePermission, child: Text('Request Storage Permissions'), ),


- Create a function on which will call native android method.

Future requestStoragePermission() async { try { await platform.invokeMethod('requestStoragePermission'); } on PlatformException catch (e) { print(e); } }


- Define platform variable above build method.

static const platform = MethodChannel( 'com.example.androidstorage.android_12_flutter_storage/storage');


- In your MainActivity create a variable named CHANNEL and set a value to it. This value should match with the dart invokeMethod value. It they mismatch, flutter code will not able to call android method.

class MainActivity: FlutterActivity() {

private val CHANNEL ="com.example.androidstorage.android_12_flutter_storage/storage"

}


- Override `configureFlutterEngine` inside `MainActivity` and add `MethodChannel` code.

override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result -> if (call.method == "requestStoragePermission") { requestPermission(this@MainActivity as Context) } else { result.notImplemented() } } }


- Create a new kt file named `FileUtils.kt` and add a method named `requestPermission`. Inside that method, call `ActivityCompat.requestPermissions` to request the storage runtime permissions.

object FileUtils {

fun requestPermission(context: Context) { ActivityCompat.requestPermissions( context as Activity, arrayOf( android.Manifest.permission.READ_EXTERNAL_STORAGE, android.Manifest.permission.WRITE_EXTERNAL_STORAGE, android.Manifest.permission.ACCESS_MEDIA_LOCATION ), 101 ); }

}


Step 4: Download file to local directory.

ElevatedButton( onPressed: downloadRecording, child: const Text('Download Recording!'), ),

void downloadRecording() async { String url = "file-examples.com/storage/fe8faa459062eec04..";

String fileName = "Audio-Recording.mp3"; String path = await _getFilePath(fileName);

await dio.download( url, path, onReceiveProgress: (receivedBytes, totalBytes) { print("Rec: $receivedBytes , Total: $totalBytes");

setState(() { progress = ((receivedBytes / totalBytes) * 100); if (progress == 100.0) { _saveFileToRecordings(path); } }); }, deleteOnError: true, ).then((value) => print(value.toString())); }

Future _saveFileToRecordings(String path) async { DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin(); final androidInfo = await deviceInfoPlugin.androidInfo; print("Device Version: ${androidInfo.version.sdkInt}");

if (Platform.isAndroid && androidInfo.version.sdkInt! >= 29) { try { await platform.invokeMethod('saveFile', {'path': path}); } on PlatformException catch (e) { print(e); } } else {} }

Future _getFilePath(String fileName) async { DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin(); final androidInfo = await deviceInfoPlugin.androidInfo; print("Device Version: ${androidInfo.version.sdkInt}");

if (Platform.isAndroid && androidInfo.version.sdkInt! >= 29) { final dir = await getExternalStorageDirectory(); print("File Name: ${dir!.path}/$fileName"); return "${dir.path}/$fileName"; } else { var dir = Directory('/storage/emulated/0/Download/AppName'); print("File Name: ${dir.path}/$fileName"); return "${dir.path}/$fileName"; } }


Step 6. Move file to Download/AppName folder.

override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result -> if (call.method == "saveFile") { val hashMap = call.arguments as HashMap<,\> //Get the arguments as a HashMap val path = hashMap["path"] FileUtils.saveBitmapToStorage(this@MainActivity as Context,path.toString()) } else if (call.method == "requestStoragePermission") { requestPermission(this@MainActivity as Context) } else { result.notImplemented() } } }

fun checkPermissionForExternalStorage(context: Context): Boolean { return ActivityCompat.checkSelfPermission( context, android.Manifest.permission.WRITE_EXTERNAL_STORAGE

) === PackageManager.PERMISSION_GRANTED }

fun saveBitmapToStorage(context: Context, bitmap: String): Uri? { var result: Uri? = null if (checkPermissionForExternalStorage(context)) { var filename: File? = null val outputStream: java.io.OutputStream? val DEFAULT_IMAGE_NAME: String = java.util.UUID.randomUUID().toString() try {

/Check if the android version is equal or greater than Android 10/ if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) { val resolver: ContentResolver = context.getContentResolver() val contentValues = ContentValues() contentValues.put(MediaStore.Audio.Media.DISPLAY_NAME, DEFAULT_IMAGE_NAME) contentValues.put(MediaStore.Audio.Media.TITLE, DEFAULT_IMAGE_NAME) contentValues.put( MediaStore.Audio.Media.MIME_TYPE, getMIMEType(context, bitmap) ) contentValues.put(MediaStore.Audio.Media.RELATIVE_PATH, "Music/" + "AppName") val imageUri: Uri? = resolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, contentValues) var file = File(bitmap);

outputStream = resolver.openOutputStream(imageUri!!) val bytesArray: ByteArray = file.readBytes()

outputStream!!.write(bytesArray) outputStream!!.flush() result = imageUri Log.d("FileUtils", bitmap); SingleMediaScanner(context, file) } else { } } catch (e: java.lang.Exception) { Log.d("FileUtils", e.message!!); e.printStackTrace() } } return result }

fun getMIMEType(con: Context, url: String?): String? {

var mType: String? = null val mExtension = MimeTypeMap.getFileExtensionFromUrl(url) if (mExtension != null) { mType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(mExtension) } return mType }


Step 7. Create `SingleMediaScanner` in android directory.

import android.content.Context; import android.media.MediaScannerConnection; import android.net.Uri;

import java.io.File;

public class SingleMediaScanner implements MediaScannerConnection.MediaScannerConnectionClient {

private MediaScannerConnection mMs; private File mFile;

public SingleMediaScanner(Context context, File f) { mFile = f; mMs = new MediaScannerConnection(context, this); mMs.connect(); }

@Override public void onMediaScannerConnected() { mMs.scanFile(mFile.getAbsolutePath(), null); }

@Override public void onScanCompleted(String path, Uri uri) { mMs.disconnect(); }

}