Skip to content

[Interop EAP] ObjectiveC function with callback throws exception Invalid argument(s): Couldn't resolve native function No asset with id ... found #2904

@josxha

Description

@josxha

The package maplibre is very close to a complete native interop solution. The only outstanding problem is finding a solution for the currently malfunctioning callback functions.

I'd like to call the following ObjC method from Dart that uses a completion callback:

- (void)clearAmbientCacheWithCompletionHandler:(void (^)(NSError *_Nullable error))completion;
- (void)clearAmbientCacheWithCompletionHandler:(void (^)(NSError *_Nullable error))completion {
    _mbglDatabaseFileSource->clearAmbientCache([&, completion](std::exception_ptr exception){
        NSError *error;
        if (completion) {
            if (exception) {
                error = [NSError errorWithDomain:MLNErrorDomain code:MLNErrorCodeModifyingOfflineStorageFailed userInfo:@{
                    NSLocalizedDescriptionKey: @(mbgl::util::toString(exception).c_str()),
                }];
            }
            dispatch_async(dispatch_get_main_queue(), [&, completion, error](void) {
                completion(error);
            });
        }
    });
}

Source

ffigen generates the following binding with this tool/ffigen.dart configuration.

/// Construction methods for `objc.ObjCBlock<ffi.Void Function(objc.NSError?)>`.
abstract final class ObjCBlock_ffiVoid_NSError {
// ...
  static objc.ObjCBlock<ffi.Void Function(objc.NSError?)> listener(void Function(objc.NSError? ) fn,
          {bool keepIsolateAlive = true}) {
    final raw = objc.newClosureBlock(_listenerCallable.nativeFunction.cast(),
        (ffi.Pointer<objc.ObjCObjectImpl> arg0) => fn(arg0.address == 0 ? null : objc.NSError.fromPointer(arg0, retain: false, release: true)), keepIsolateAlive);
    final wrapper = _NativeLibrary_wrapListenerBlock_xtuoz7(raw);
    objc.objectRelease(raw.cast());
    return objc.ObjCBlock<ffi.Void Function(objc.NSError?)>(wrapper, retain: false, release: true);
  }
// ...
}

// ...

  /// Clears the ambient cache by deleting resources. This method does not affect
/// resources shared with offline regions.
/// 
/// @param completion The completion handler to call once resources from the
/// ambient cache have been cleared. This handler is executed asynchronously on
/// the main queue.
  void clearAmbientCacheWithCompletionHandler(objc.ObjCBlock<ffi.Void Function(objc.NSError?)> completion) {
_objc_msgSend_f167m6(object$.ref.pointer, _sel_clearAmbientCacheWithCompletionHandler_, completion.ref.pointer);

  }

When I now try to call the method:

final storage = MLNOfflineStorage.getSharedOfflineStorage();
// later
storage.clearAmbientCacheWithCompletionHandler(
      ObjCBlock_ffiVoid_NSError.listener((error) {}),
    );

I get the following exception in the console:

[ERROR:flutter/runtime/dart_vm_initializer.cc(40)] Unhandled Exception: Invalid argument(s): Couldn't resolve native function '_NativeLibrary_wrapListenerBlock_xtuoz7' in 'package:maplibre_ios/maplibre_ffi.g.dart' : No asset with id 'package:maplibre_ios/maplibre_ffi.g.dart' found. Available native assets: package:objective_c/objective_c.dylib. Attempted to fallback to process lookup. dlsym(RTLD_DEFAULT, _NativeLibrary_wrapListenerBlock_xtuoz7): symbol not found.

#0      Native._ffi_resolver.#ffiClosure0 (dart:ffi-patch/ffi_patch.dart)
#1      Native._ffi_resolver_function (dart:ffi-patch/ffi_patch.dart:1943:20)
#2      _NativeLibrary_wrapListenerBlock_xtuoz7 (package:maplibre_ios/maplibre_ffi.g.dart)
#3      ObjCBlock_ffiVoid_NSError.listener (package:maplibre_ios/maplibre_ffi.g.dart:6549:21)
#4      OfflineManagerIos.clearAmbientCache (package:maplibre/src/platform/ios/offline_manager.dart:29:33)
#5      _OfflinePageState.build.<anonymous closure>.<anonymous closure> (package:maplibre_example/offl<…>

This issue might be related to the problem described in #1475.
I am running the example app with the Swift Package Manager enabled. The dependency graph looks like this maplibre_example -> maplibre -> maplibre_ios (needed because of flutter/flutter#148424 / flutter/flutter#63240).
The native MapLibre iOS SDK gets added like this:

let package = Package(
  name: "maplibre_ios",
  platforms: [
    .iOS("12.0"),
  ],
  products: [
    .library(name: "maplibre-ios", targets: ["maplibre_ios"]),
  ],
  dependencies: [
    .package(url: "https://github.com/maplibre/maplibre-gl-native-distribution", .upToNextMinor(from: "6.21.0")),
  ],
  targets: [
    .target(
      name: "maplibre_ios",
      dependencies: [
        .product(name: "MapLibre", package: "maplibre-gl-native-distribution"),
      ],
      cSettings: [
        .headerSearchPath("include/maplibre_ios"),
      ]
    ),
  ]
)

My setup is running on the stable Flutter 3.38.5 release.

Output of`flutter doctor
% flutter doctor -v
[✓] Flutter (Channel stable, 3.38.5, on macOS 26.1 25B78 darwin-arm64, locale de-DE) [180ms]
    • Flutter version 3.38.5 on channel stable at /Users/joscha/development/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision f6ff1529fd (2 weeks ago), 2025-12-11 11:50:07 -0500
    • Engine revision 1527ae0ec5
    • Dart version 3.10.4
    • DevTools version 2.51.1
    • Feature flags: enable-web, enable-linux-desktop, enable-macos-desktop, enable-windows-desktop, enable-android, enable-ios, cli-animations, enable-native-assets,
      enable-swift-package-manager, omit-legacy-version-file, enable-lldb-debugging

[!] Android toolchain - develop for Android devices (Android SDK version 35.0.1) [245ms]
    • Android SDK at /Users/joscha/Library/Android/sdk
    • Emulator version 35.3.11.0 (build_id 12836668) (CL:N/A)
    ✗ cmdline-tools component is missing.
      Try installing or updating Android Studio.
      Alternatively, download the tools from https://developer.android.com/studio#command-line-tools-only and make sure to set the ANDROID_HOME environment variable.
      See https://developer.android.com/studio/command-line for more details.
    ✗ Android license status unknown.
      Run `flutter doctor --android-licenses` to accept the SDK licenses.
      See https://flutter.dev/to/macos-android-setup for more details.

[✓] Xcode - develop for iOS and macOS (Xcode 26.1) [430ms]
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 17B55
    • CocoaPods version 1.16.2

[✗] Chrome - develop for the web (Cannot find Chrome executable at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome) [4ms]
    ! Cannot find Chrome. Try setting CHROME_EXECUTABLE to a Chrome executable.

[✓] Connected device (2 available) [5,6s]
    • iPhone 16e (mobile) • 072F1161-B1B2-4599-9E41-80726EFEB0E8 • ios          • com.apple.CoreSimulator.SimRuntime.iOS-26-1 (simulator)
    • macOS (desktop)     • macos                                • darwin-arm64 • macOS 26.1 25B78 darwin-arm64

[✓] Network resources [319ms]
    • All expected network resources are available.

! Doctor found issues in 2 categories.

Metadata

Metadata

Assignees

Type

No type

Projects

Status

No status

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions