3

We want to define a Flutter asset that can be accessed from the Android platform code within the same plugin. The documentation for this is purported to be here.

Please note we are NOT talking about custom platform code within a flutter app -- we are talking about flutter plugin code from the perspective of the plugin author.

So, following the docs, we start with the pubspec.yaml file for our plugin which exists at [your_plugin_root_dir]\pubspec.yaml, and we add the asset definition:

  assets:
    - assets/sound.wav

This points to an existing file at [your_plugin_root_dir]\assets\sound.wav

All good so far, but then we have a problem:

When you create a new Flutter Plugin project in Android studio, the Android Java file for the plugin looks like this:

public class PluginTestPlugin implements FlutterPlugin, MethodCallHandler {
  /// The MethodChannel that will the communication between Flutter and native Android
  ///
  /// This local reference serves to register the plugin with the Flutter Engine and unregister it
  /// when the Flutter Engine is detached from the Activity
  private MethodChannel channel;

  @Override
  public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
    channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "plugin_test");
    channel.setMethodCallHandler(this);
  }

  @Override
  public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
    if (call.method.equals("getPlatformVersion")) {
      result.success("Android " + android.os.Build.VERSION.RELEASE);
    } else {
      result.notImplemented();
    }
  }

  @Override
  public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
    channel.setMethodCallHandler(null);
  }
}

And in order to access your asset from within that code, the docs suggest adding this code:

AssetManager assetManager = registrar.context().getAssets();
String key = registrar.lookupKeyForAsset("assets/sound.wav");
AssetFileDescriptor fd = assetManager.openFd(key);

But since there is no "registrar" instance that exists in that code, and the documentation doesn't describe how it was created or how to access it, I'm lost.

What am I missing here????

Nerdy Bunz
  • 6,040
  • 10
  • 41
  • 100

1 Answers1

5

Add another field next to the method channel, to keep hold of the binding;

private MethodChannel channel;

private FlutterPluginBinding binding;

In onAttachedToEngine set it:

channel.setMethodCallHandler(this);
binding = flutterPluginBinding;

And clear it in onDetached

channel.setMethodCallHandler(null);
binding = null;

Then use it like this (for the purposes of this I added an asset to the plugin (not the example app) called assets/foo.bin, and my plugin name is plugin1)

        String assetPath = binding
                .getFlutterAssets()
                .getAssetFilePathBySubpath("assets/foo.bin", "plugin1");
        try {
            InputStream is = binding.getApplicationContext().getAssets().open(assetPath);
            // todo - read the asset into memory or whatever
            is.close();
        } catch (IOException e) {
            Log.e("plugin1", e.getMessage());
        }

The pubspec.yaml of the plugin project needs extra lines to package the asset as follows, after the plugin section:

  plugin:
    platforms:
      android:
        package: io.swhh.plugin1
        pluginClass: Plugin1Plugin
      ios:
        pluginClass: Plugin1Plugin

  assets:
    - assets/foo.bin

You may be interested in the equivalent in iOS (excuse the lack of error handling)

  let key = FlutterDartProject.lookupKey(forAsset: "assets/foo.bin", fromPackage: "plugin1")
  let path = Bundle.main.path(forResource: key, ofType: nil, inDirectory: nil)
  let url = URL(fileURLWithPath: path!)
  // use url to load the resource
Richard Heap
  • 48,344
  • 9
  • 130
  • 112
  • 1
    Thankyou, this seems promising, but the catch(e) block is firing, and the message is "java.io.FileNotFoundException: flutter_assets/packages/metronome_plugin_2/assets/clack.wav" I have the file in [my hard drive]/.../[my flutter plugin project folder]/assets/clack.wav. Is there a way to iterate over the result of binding.getFlutterAssets to simply see what files/dirs it is looking at? – Nerdy Bunz Jan 26 '22 at 05:22
  • Did you add the asset to the pubspec.yaml in /[my flutter plugin project folder] ? – Richard Heap Jan 26 '22 at 14:18
  • There's a handy trick to list all packaged assets, which helps to see what path they are at, and that they got packaged. (You can do this in the Dart code of the example app or the plugin.) Add `print(await rootBundle.loadString('AssetManifest.json'));` to an async method, perhaps even any method that invokes `_channel.invokeMethod(xxx)` so that it's printed as you call to the native method. – Richard Heap Jan 26 '22 at 16:03
  • Thanks! I added the file to assets, but same result. The rootBundle trick yields: "I/flutter ( 4945): {"packages/cupertino_icons/assets/CupertinoIcons.ttf":["packages/cupertino_icons/assets/CupertinoIcons.ttf"],"packages/metronome_plugin_2/assets/clack.wav":["packages/metronome_plugin_2/assets/clack.wav”]}”. I also tried renaming (miss-naming) the asset file which caused build errors (this is good!)… so now I’m thinking it’s either a typo in the line “.getAssetFilePathBySubpath("assets/" + fromAsset, "metronome_plugin_2”);” or I’m missing some kind of file permissions in the Android manifest? – Nerdy Bunz Jan 27 '22 at 04:42
  • Ah, are you using the code exactly as written above? Using `.open()` to get an `InputStream`. (You can't access it as a file, as it's rolled up in the asset bundle.) – Richard Heap Jan 27 '22 at 12:12
  • You can also log `assetPath` which should be `something/packages/metronome_plugin_2/assets/clack.wav` – Richard Heap Jan 27 '22 at 12:18
  • It works now! But... I didn't change anything other than renaming the file back and re-opening the project. Thanks for your help. – Nerdy Bunz Jan 28 '22 at 04:10