Flutter Desktop MacOS: how to open a file from Finder with Flutter app

Issue

I’ve written a Flutter Desktop MacOS application that uses a command line argument to process a file:

void main(List<String> args) async {
  if (args.isNotEmpty) {
    runApp(MyApp(args.first))
  } else ...;
}

which works as expected when I run it from the shell:

# this command is ok:
/Applications/TommyView.app/Contents/MacOS/TommyView Pictures/hey.png

But when I assign this app to all *.png images, and want to run it from Finder, it shows:

enter image description here

(or sometimes another error depending on Info.plist: TommyView cannot open files in the “PNG image” format.)

Also I noticed that the execution goes to "else" case (i.e. args are empty).

I guess some magic is missing in Info.plist. Please help to figure out.

Solution

Thanks, @smorgan, for your response. Let me improve your answer by adding a piece of code for other Flutter developers:

  1. In MainFlutterWindow.swift add the following:
class MainFlutterWindow: NSWindow {
    open var currentFile: String? // add this variable

  override func awakeFromNib() {
    ...

    // interop with Flutter
    let channel = FlutterMethodChannel(name: "myChannel", binaryMessenger: flutterViewController.engine.binaryMessenger)
    channel.setMethodCallHandler({
        (call: FlutterMethodCall, result: FlutterResult) -> Void in
        if (call.method == "getCurrentFile") {
            result(self.currentFile)
        } else {
            result(FlutterMethodNotImplemented)
        }
    })
    ...
  }
}
  1. in AppDelegate.swift you need to handle openFile:
@NSApplicationMain
class AppDelegate: FlutterAppDelegate {
    ...
    // called when a user double-clicks on a file in Finder
    override func application(_ sender: NSApplication, openFile filename: String) -> Bool {
        (mainFlutterWindow as! MainFlutterWindow).currentFile = filename
      return true
    }
}
  1. now in your main.dart:
void main(List<String> args) async {
  WidgetsFlutterBinding.ensureInitialized();
  final startFile = await getStartFile(args);
  runApp(MyApp(startFile));
}

Future<String> getStartFile(List<String> args) async {
  if (args.isNotEmpty) return args.first;
  if (Platform.isMacOS) {
    // in MacOS, we need to make a call to Swift native code to check if a file has been opened with our App
    const hostApi = MethodChannel("myChannel");
    final String? currentFile = await hostApi.invokeMethod("getCurrentFile");
    if (currentFile != null) return currentFile;
  }
  return "";
}
  1. [Optional] add file extensions to Info.plist to make it "visible" in MacOS recommended apps:
<dict>
    ...
    <key>CFBundleDocumentTypes</key>
    <array>
        <dict>
            <key>CFBundleTypeExtensions</key>
            <array>
                <string>jpg</string>
                <string>jpeg</string>
                <string>png</string>
                <string>gif</string>
                <string>webp</string>
                <string>bmp</string>
                <string>wbmp</string>
            </array>
            <key>CFBundleTypeRole</key>
            <string>Viewer</string>
        </dict>
    </array>

I hope it will help.

Answered By – Mitrakov Artem

Answer Checked By – David Marino (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *