From 05f33b8e6afbe12152d81a0344a17a306ceac3ca Mon Sep 17 00:00:00 2001 From: Aaron Madlon-Kay Date: Tue, 18 Nov 2025 08:43:26 +0900 Subject: [PATCH] Don't require an Activity unless absolutely necessary Reading, writing a file does not need a GUI, for instance when invoked from a background task --- .../FilePickerWritableImpl.kt | 33 +++++++++++-------- .../FilePickerWritablePlugin.kt | 15 +++++---- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/android/src/main/kotlin/codeux/design/filepicker/file_picker_writable/FilePickerWritableImpl.kt b/android/src/main/kotlin/codeux/design/filepicker/file_picker_writable/FilePickerWritableImpl.kt index f4e224f..73ec9d9 100644 --- a/android/src/main/kotlin/codeux/design/filepicker/file_picker_writable/FilePickerWritableImpl.kt +++ b/android/src/main/kotlin/codeux/design/filepicker/file_picker_writable/FilePickerWritableImpl.kt @@ -4,6 +4,7 @@ import android.app.Activity import android.app.Activity.RESULT_OK import android.content.ActivityNotFoundException import android.content.ContentResolver +import android.content.Context import android.content.Intent import android.database.Cursor import android.net.Uri @@ -19,8 +20,11 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.File -interface ActivityProvider : CoroutineScope { +interface ContextProvider : CoroutineScope { val activity: Activity? + + val applicationContext: Context? + fun logDebug(message: String, e: Throwable? = null) @MainThread fun openFile(fileInfo: Map) @@ -29,7 +33,7 @@ interface ActivityProvider : CoroutineScope { } class FilePickerWritableImpl( - private val plugin: ActivityProvider + private val plugin: ContextProvider ) : PluginRegistry.ActivityResultListener, PluginRegistry.NewIntentListener { companion object { @@ -175,8 +179,8 @@ class FilePickerWritableImpl( fileUri: Uri, initialFileContent: File ) { - val activity = requireActivity() - val contentResolver = activity.applicationContext.contentResolver + val context = requireContext() + val contentResolver = context.applicationContext.contentResolver val takeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION contentResolver.takePersistableUriPermission(fileUri, takeFlags) @@ -213,9 +217,9 @@ class FilePickerWritableImpl( @MainThread private suspend fun copyContentUriAndReturnFileInfo(fileUri: Uri): Map { - val activity = requireActivity() + val context = requireContext() - val contentResolver = activity.applicationContext.contentResolver + val contentResolver = context.applicationContext.contentResolver return withContext(Dispatchers.IO) { var persistable = false @@ -235,7 +239,7 @@ class FilePickerWritableImpl( // use a maximum of 20 characters. // It's just a temp file name so does not really matter. fileName.take(20), - null, activity.cacheDir + null, context.cacheDir ) plugin.logDebug("Copy file $fileUri to $tempFile") contentResolver.openInputStream(fileUri).use { input -> @@ -301,8 +305,8 @@ class FilePickerWritableImpl( throw FilePickerException("File at source not found. $file") } val fileUri = Uri.parse(identifier) - val activity = requireActivity() - val contentResolver = activity.contentResolver + val context = requireContext() + val contentResolver = context.contentResolver withContext(Dispatchers.IO) { // with Android 10 and later, use wt // https://issuetracker.google.com/issues/135714729?pli=1 @@ -319,16 +323,16 @@ class FilePickerWritableImpl( } fun disposeIdentifier(identifier: String) { - val activity = requireActivity() - val contentResolver = activity.applicationContext.contentResolver + val context = requireContext() + val contentResolver = context.applicationContext.contentResolver val takeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION contentResolver.releasePersistableUriPermission(Uri.parse(identifier), takeFlags) } fun disposeAllIdentifiers() { - val activity = requireActivity() - val contentResolver = activity.applicationContext.contentResolver + val context = requireContext() + val contentResolver = context.applicationContext.contentResolver val takeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION for (permission in contentResolver.persistedUriPermissions) { @@ -340,6 +344,9 @@ class FilePickerWritableImpl( private fun requireActivity() = (plugin.activity ?: throw FilePickerException("Illegal state, expected activity to be there.")) + private fun requireContext() = (plugin.activity ?: plugin.applicationContext + ?: throw FilePickerException("Illegal state, expected application context or activity to be there.")) + private val CONTENT_PROVIDER_SCHEMES = setOf( ContentResolver.SCHEME_CONTENT, ContentResolver.SCHEME_FILE, diff --git a/android/src/main/kotlin/codeux/design/filepicker/file_picker_writable/FilePickerWritablePlugin.kt b/android/src/main/kotlin/codeux/design/filepicker/file_picker_writable/FilePickerWritablePlugin.kt index 9371a4b..96d250c 100644 --- a/android/src/main/kotlin/codeux/design/filepicker/file_picker_writable/FilePickerWritablePlugin.kt +++ b/android/src/main/kotlin/codeux/design/filepicker/file_picker_writable/FilePickerWritablePlugin.kt @@ -1,6 +1,7 @@ package codeux.design.filepicker.file_picker_writable import android.app.Activity +import android.content.Context import android.net.Uri import android.util.Log import androidx.annotation.MainThread @@ -8,7 +9,6 @@ import androidx.annotation.NonNull import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.activity.ActivityAware import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding -import io.flutter.plugin.common.BinaryMessenger import io.flutter.plugin.common.EventChannel import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel @@ -25,7 +25,7 @@ private const val TAG = "FilePickerWritable" /** FilePickerWritablePlugin */ class FilePickerWritablePlugin : FlutterPlugin, MethodCallHandler, ActivityAware, - ActivityProvider, CoroutineScope by MainScope() { + ContextProvider, CoroutineScope by MainScope() { /// The MethodChannel that will the communication between Flutter and native Android /// /// This local reference serves to register the plugin with the Flutter Engine and unregister it @@ -34,23 +34,26 @@ class FilePickerWritablePlugin : FlutterPlugin, MethodCallHandler, private val impl: FilePickerWritableImpl = FilePickerWritableImpl(this) private var currentBinding: ActivityPluginBinding? = null + override var applicationContext: Context? = null + private val eventQueue = LinkedList>() private var eventSink: EventChannel.EventSink? = null override fun onAttachedToEngine( @NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding ) { - initializePlugin(flutterPluginBinding.binaryMessenger) + initializePlugin(flutterPluginBinding) } - private fun initializePlugin(binaryMessenger: BinaryMessenger) { + private fun initializePlugin(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + applicationContext = flutterPluginBinding.applicationContext channel = MethodChannel( - binaryMessenger, + flutterPluginBinding.binaryMessenger, "design.codeux.file_picker_writable" ) channel.setMethodCallHandler(this) EventChannel( - binaryMessenger, + flutterPluginBinding.binaryMessenger, "design.codeux.file_picker_writable/events" ).setStreamHandler(object : EventChannel.StreamHandler {