flutteryamldbussnapcraftsnap

Flutter application built with snapcraft using file_picker not showing dialog


I've built a Flutter application that works perfectly on Windows, Linux and Android. However, when I compile and run the code as a snap package, the file dialog windows do not appear.

The dialogs use the file_picker plugin (v8.1.2).

I've spent hours following different instructions on the internet and asked multiple different AI's, but nothing seems to work.

This is my snapcraft.yaml file:

name: jpl
version: 0.6.0.0
summary: TBC
description: TBC.

confinement: strict
base: core22
grade: devel

apps:
  jpl:
    command: jpl
    extensions: [gnome]
    plugs:
      - dbus
      - desktop
      - home
      - system-observe

parts:
  jpl:
    source: .
    plugin: flutter
    flutter-target: lib/main.dart

plugs:
  dbus:
    bus: session
    name: "org.gtk.Actions"

I don't get any errors from the compilation, but I do get the following error when I run the app:

AppArmor =
Time: Sep  7 12:12:51
Log: apparmor="DENIED" operation="dbus_method_call"  bus="session" path="/au/com/jpl/window/1" interface="org.gtk.Actions" member="DescribeAll" name=":1.26" mask="receive" pid=142250 label="snap.jpl.jpl" peer_pid=1663 peer_label="unconfined"
DBus access

Any help would be greatly appreciated.

Thanks.


Edit 1:

I added the following to my snapcraft.yaml file, which no longer shows the error I mentioned above when I run my app, but the dialog windows are still not showing.

apps:
  ...
  jpl:
    ...
    slots:
      dbus-gtk
...
slots:
  dbus-gtk:
    interface: dbus
    bus: session
    name: au.com.jpl

I have also tried with desktop-legacy, but that had no effect.


Solution

  • I ended up using multiple libraries - one for desktop, one for mobile, and one for web. This is because file_picker wasn't working for snap, but file_selector doesn't work for mobile, and neither work for saving files when running on the web.

    I've outlined it below for anyone else having this issue (the below is changed from my own code to make it simpler/clearer for the sake of the example).

    pubspec.yaml

    dependencies:
      file_picker: ^8.1.2
      file_selector: ^1.0.3
    

    import_data.dart

    import "package:file_picker/file_picker.dart";
    import "package:file_selector/file_selector.dart";
    
    Future<void> _openDesktop() async {
      XTypeGroup types = const XTypeGroup(
        label: "JSON",
        extensions: ["json"],
      );
    
      XFile? file = await openFile(
        acceptedTypeGroups: [types],
      );
    
      if (file != null) {
        String contents = await file.readAsString();
      }
    }
    
    Future<void> _openMobile() async {
      FilePickerResult? path = await FilePicker.platform.pickFiles(
        dialogTitle: "Open File",
        type: FileType.custom,
        allowedExtensions: ["json"],
        withReadStream: true,
        lockParentWindow: true,
      );
    
      if (result != null) {
        Stream<List<int>> stream = result.files.first.readStream!
        StringBuffer buffer = StringBuffer();
    
        stream.transform(utf8.decoder).listen((data) {
          buffer.write(data);
        },
        onError: (exception) {
          // Show an error message
        },
        onDone: () {
          String contents = buffer.toString();
        }
      }
    }
    
    Future<void> _openWeb() async {
      XTypeGroup types = const XTypeGroup(
        label: "JSON",
        extensions: ["json"],
      );
    
      FilePickerResult? result = openFile(
        acceptedTypeGroups: [types],
      );
    
      if (result != null) {
        String contents = await result.readAsString();
      }
    }
    

    export_data.dart (obviously the contents isn't valid JSON, just providing an example)

    import "package:file_picker/file_picker.dart";
    import "package:file_selector/file_selector.dart";
    import "package:ledger/web_empty.dart"
        if(dart.library.web) "package:web/web.dart";
    
    Future<void> _saveDesktop() async {
      XTypeGroup types = const XTypeGroup(
        label: "JSON",
        extensions: ["json"],
      );
    
      FileSaveLocation? result = await getSaveLocation(
        acceptedTypeGroups: [types],
        suggestedName: "Data.json",
      );
    
      if (result != null) {
        await File(result.path).writeAsString("contents");
      }
    }
    
    Future<void> _saveMobile() async {
      await FilePicker.platform.saveFile(
        dialogTitle: "Save File",
        fileName: "Data.json",
        type: FileType.custom,
        allowedExtensions: ["json"],
        bytes: utf8.encode("contents"),
        lockParentWindow: true,
      );
    }
    
    Future<void> _saveWeb() async {
      String data = Uri.encodeComponent("contents");
      HTMLAnchorElement()
        ..href = "data:text/plain;charset=utf-8,$data"
        ..download = "Data.json"
        ..click();
    }
    

    web_empty.dart (I had to create this otherwise it wouldn't compile for snap - see the conditional import in export_data.dart)

    class HTMLAnchorElement {
      String download = "";
      String href = "";
    
      void click() {}
    }
    

    Hope this helps!