flutter open file externally such as on ios "open in"

2,876

Solution 1

To do this in iOS you first define the Document Types and Imported UTIs in XCode as described in the guide you mentioned, and then in your AppDelegate.m file you do:

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    /* custom code begin */
    FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;
    FlutterMethodChannel* myChannel = [FlutterMethodChannel
                                          methodChannelWithName:@"my/file"
                                          binaryMessenger:controller];
    __block NSURL *initialURL = launchOptions[UIApplicationLaunchOptionsURLKey];

    [myChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
        if ([@"checkintent" isEqualToString:call.method]) {
            if (initialURL) {
                [myChannel invokeMethod:@"loaded" arguments: [initialURL absoluteString]];
                initialURL = nil;
                result(@TRUE);
            }
        }
    }];
    /* custom code end */

    [GeneratedPluginRegistrant registerWithRegistry:self];
    // Override point for customization after application launch.
    return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

On the Dart side:

class PlayTextPageState extends State<MyHomePage> with WidgetsBindingObserver{
  static const platform = const MethodChannel('my/file');

  void initState() {
    super.initState();

    WidgetsBinding.instance.addObserver(this);

    platform.setMethodCallHandler((MethodCall call) async {
      String method = call.method;

      if (method == 'loaded') {
        String path = call.arguments; // this is the path
        ...
      }
    });
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    super.didChangeAppLifecycleState(state);

    if (state == AppLifecycleState.paused) {
      ...
    } else if (state == AppLifecycleState.resumed) {
      platform.invokeMethod("checkintent")
        .then((result) {
          // result == 1 if the app was opened with a file
        });
    }
  }
}

Solution 2

Adding on to lastant's answer, you actually also need to override application(_:open:options:) in AppDelegate.swift for this to work.

So the idea is to use UIActivityViewController in iOS to open a file in Flutter (eg: restore a backup of the SQL DB into the Flutter app from an email).

First, you need to set the UTIs in the info.plist. Here's a good link to explain how that works. https://www.raywenderlich.com/813044-uiactivityviewcontroller-tutorial-sharing-data

Second, add the channel controller code in AppDelegate.swift.

We also need to override application(:open:options:) in AppDelegate.swift because iOS will invoke application(:open:options:) when an external application wants to send your application a file. Hence we store the filename as a variable inside AppDelegate.

Here we are have a 2-way channel controller between iOS and Flutter. Everytime the Flutter app enter the AppLifecycleState.resumed state, it will invoke "checkIntent" to check back into AppDelegate to see if the filename has been set. If a filename has been set, AppDelegate will invoke the "load" method in flutter whereby you do your required processing with the file.

Remember to delete the file given to you from AppDelegate after you are done with your processing. Otherwise, it will bloat up your application.

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {

var initialURL: URL?

override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions:    [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {

/* channel controller code */
let controller: FlutterViewController = self.window?.rootViewController as! FlutterViewController
let myChannel = FlutterMethodChannel(name: "my/file", binaryMessenger: controller.binaryMessenger)


myChannel.setMethodCallHandler({(call: FlutterMethodCall, result: @escaping FlutterResult)-> Void in


    if(call.method == "checkintent"){
        if(self.initialURL != nil){
            myChannel.invokeMethod("loaded", arguments: self.initialURL?.absoluteString );
            self.initialURL = nil;

            result(true);
        } else{
            print("initialURL is null");
        }
    } else{
        print("no such channel method");
    }
});



  GeneratedPluginRegistrant.register(with: self)
  return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}


override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {

    print("import URL: \(url)");
    initialURL = url;

    // should not remove here.. remove after i get into flutter...
   // try? FileManager.default.removeItem(at: url);

    return true;
  }

}
Share:
2,876
lastlink
Author by

lastlink

.net core c#, vuejs, and flutter enthusiast.

Updated on December 08, 2022

Comments

  • lastlink
    lastlink over 1 year

    From what I can tell most of the flutter guides out there can open from local storage, but nothing about file sharing. Anybody know how to do this. This is a guide in enabling it specifically for ios https://developer.apple.com/library/archive/qa/qa1587/_index.html.

    I mean there is the https://pub.dartlang.org/packages/open_file extension, but opens from the file storage.

    To clarify this question isn't about sharing a file from the app with another, but when sharing from an external app being prompted to open in this flutter app.