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:
(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.
Thanks, @smorgan, for your response. Let me improve your answer by adding a piece of code for other Flutter developers:
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)
}
})
...
}
}
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
//}
// update 2024:
override func application(_ application: NSApplication, open urls: [URL]) {
if (!urls.isEmpty) {
(mainFlutterWindow as! MainFlutterWindow).currentFile = urls.first!.path
}
}
}
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 "";
}
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.