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.
CodePudding user response:
You need to declare file types that your application handles using CFBundleDocumentTypes
in your Info.plist.
More importantly though, command line arguments aren't how your application will receive files to open on macOS, so that Dart code won't work. To handle files you would need to implement application(_:openFile:)
(which can be called at any time, not just at launch) in your macOS Runner, and then pass the files to Dart using platform channels.
CodePudding user response:
Thanks, @smorgan, for your response. Let me improve your answer by adding a piece of code for other Flutter developers:
- 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)
}
})
...
}
}
- in
AppDelegate.swift
you need to handleopenFile
:
@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
}
}
- 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 "";
}
- [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.