1

I need my Flutter app to be associated with the file type .foo. So that if a user opens their local file manager for example, and clicks on the file bar.foo android prompts them to open the file with my flutter app.

So far I understand, that this is basically an incoming Android intent which has to be registered with a so called intent-filter as described here: Creating app which opens a custom file extension. But further, I do not understand how to handle it in Kotlin.

Therefore the next logical thing would be to find out how incoming intents work in Flutter. The documentation however doesn't help me, as it is explained only in Java and not Kotlin. I have no experience with Java at all and would like to stick to Kotlin anyway, because I have other platform specific code written in Kotlin.

In this post Deep Shah seems to have the same problem, but doesn't share the solution: Support custom file extension in a flutter app ( Open file with extension .abc in flutter ).

This post by Shanks died quietly: Open custom file extension with Flutter App.

I hope to find answers or pointers to relevant resources.

1 Answers1

5

I found a solution for android. This post should bundle all important steps into one. The things starting with YOUR_ are dependant on you. Change them accordingly.

First, you have to register the intent-filter in the AndroidManifest.xml. This will tell android to list your app as a possible option.

<!-- TODO: CHANGE ICON AND LABEL HERE -->  
<intent-filter android:icon="YOUR_ICON_LOCATION"                  
               android:label="YOUR_APP_LABEL"
               android:priority="1">
    <action android:name="android.intent.action.VIEW"/>
    <category android:name="android.intent.category.DEFAULT"/>
    <category android:name="android.intent.category.BROWSABLE"/>
    <data android:scheme="content" />
    <data android:scheme="http" />
    <data android:scheme="file" />
    <data android:mimeType="*/*" />
    <!-- TODO: CHANGE FILE EXTENSION -->  
    <data android:pathPattern=".*\\.YOUR_FILE_EXTENSION" />
</intent-filter>

Next, set up your Android Method Channel. For this, in Kotlin it is done like so:

import io.flutter.embedding.android.FlutterActivity
import android.content.Intent
import android.os.Bundle
import androidx.annotation.NonNull
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.GeneratedPluginRegistrant

class MainActivity : FlutterActivity() {
    // TODO: CHANGE METHOD CHANNEL NAME
    private val CHANNEL = "YOUR_CHANNEL_NAME"

    var openPath: String? = null
    override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        GeneratedPluginRegistrant.registerWith(flutterEngine)
        val channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
        channel.setMethodCallHandler { call, result ->
            when (call.method) {
                "getOpenFileUrl" -> {
                    result.success(openPath)
                }
                else -> result.notImplemented()
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        handleOpenFileUrl(intent)
    }

    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        handleOpenFileUrl(intent)
    }

    private fun handleOpenFileUrl(intent: Intent?) {
        val path = intent?.data?.path
        if (path != null) {
            openPath = path
        }
    }
}

Finally, set up a handler on the dart side of things. The following code has to be in a drawn Widget. I placed it into my MainView widget, as it is always drawn on app startup and is relatively high up in the Widget tree. (For me it is the first Widget outside the MaterialApp Widget)

class MainView extends StatefulWidget {
  const MainView({Key key}) : super(key: key);

  @override
  _MainViewState createState() => _MainViewState();
}

class _MainViewState extends State<MainView> with WidgetsBindingObserver {
  // TODO: CHANGE CHANNEL NAME
  static const platform = const MethodChannel("YOUR_CHANNEL_NAME");
 
  @override
  void initState() {
    super.initState();
    getOpenFileUrl();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    super.dispose();
    WidgetsBinding.instance.removeObserver(this);
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.resumed) {
      getOpenFileUrl();
    }
  }

  @override
  Widget build(BuildContext context) {
    // TODO: Implement Build Method
  }

  void getOpenFileUrl() async {
    dynamic url = await platform.invokeMethod("getOpenFileUrl");

    if (url != null) {
      setState(() {
        // TODO: DO SOMETING WITH THE FILE URL
      });
    }
  }
}

After completely killing and restarting both my app and the external app (in my case the file manager) it worked.