register static method
void
register()
Register platform adapter with C++. Must be called FIRST during SDK init (before any C++ operations).
Implementation
static void register() {
if (_isRegistered) {
_logger.debug('Platform adapter already registered');
return;
}
try {
final lib = PlatformLoader.loadCommons();
// Resolve the platform helper before giving commons any secure-storage
// callback pointers. Missing helper symbols are an initialization error.
DartBridgeSecureStorage.instance;
// Allocate the platform adapter struct
_adapterPtr = calloc<RacPlatformAdapterStruct>();
final adapter = _adapterPtr!;
// ABI guard (MUST be the first two fields). rac_init rejects the adapter
// with RAC_ERROR_ABI_VERSION_MISMATCH unless these match the commons
// build. 1 == RAC_PLATFORM_ADAPTER_ABI_VERSION.
adapter.ref.abiVersion = 1;
adapter.ref.structSize = sizeOf<RacPlatformAdapterStruct>();
// Logging callback - MUST use NativeCallable.listener for thread safety
// This allows C++ to call the logger from any thread (including background
// threads used by LLM generation) without crashing with:
// "Cannot invoke native callback from a different isolate"
_loggerCallable = NativeCallable<RacLogCallbackNative>.listener(
_platformLogCallback,
);
adapter.ref.log = _loggerCallable!.nativeFunction;
// File operations
adapter.ref.fileExists =
Pointer.fromFunction<RacFileExistsCallbackNative>(
_platformFileExistsCallback,
_exceptionalReturnFalse,
);
adapter.ref.fileRead = Pointer.fromFunction<RacFileReadCallbackNative>(
_platformFileReadCallback,
_exceptionalReturnInt32,
);
adapter.ref.fileWrite = Pointer.fromFunction<RacFileWriteCallbackNative>(
_platformFileWriteCallback,
_exceptionalReturnInt32,
);
adapter.ref.fileDelete =
Pointer.fromFunction<RacFileDeleteCallbackNative>(
_platformFileDeleteCallback,
_exceptionalReturnInt32,
);
// Synchronous platform-native secure storage.
adapter.ref.secureGet = Pointer.fromFunction<RacSecureGetCallbackNative>(
_platformSecureGetCallback,
_exceptionalReturnSecureStorage,
);
adapter.ref.secureSet = Pointer.fromFunction<RacSecureSetCallbackNative>(
_platformSecureSetCallback,
_exceptionalReturnSecureStorage,
);
adapter.ref.secureDelete =
Pointer.fromFunction<RacSecureDeleteCallbackNative>(
_platformSecureDeleteCallback,
_exceptionalReturnSecureStorage,
);
// Clock — intentionally null. C++ falls back to std::chrono::system_clock
// in rac_time.cpp. This is the correct design for Dart FFI, not a
// workaround:
// - Pointer.fromFunction trampolines are tied to the registering
// isolate and crash (SIGABRT) when invoked from non-Dart threads.
// - NativeCallable.listener is one-way/async and cannot return an
// Int64 synchronously.
// - rac_get_current_time_ms is called from C++ worker threads
// (download orchestrator std::thread, OkHttp transport pool) with
// no isolate affinity.
// std::chrono::system_clock yields equivalent ms timestamps to Swift's
// Foundation Date() callback, so no platform override is needed.
adapter.ref.nowMs = nullptr;
// Memory info callback
adapter.ref.getMemoryInfo =
Pointer.fromFunction<RacGetMemoryInfoCallbackNative>(
_platformGetMemoryInfoCallback,
_exceptionalReturnInt32,
);
// HTTP download callbacks — disabled because OkHttp transport vtable
// handles all HTTP. Pointer.fromFunction trampolines are not safe to
// call from the C++ worker thread spawned by the download orchestrator.
adapter.ref.httpDownload = nullptr;
adapter.ref.httpDownloadCancel = nullptr;
adapter.ref.extractArchive = nullptr;
// Directory enumeration — commons uses these from the model-registry
// refresh path (rescan_local) and the canonical RAModelInfo factory
// (is_downloaded gating for multi-file artifacts). Both are invoked
// from the Dart-owned isolate that initiates the corresponding SDK
// public API call, so Pointer.fromFunction trampolines are safe here
// (same constraint as file_exists / file_read above). See
// rac_platform_adapter.h doc-block for cross-SDK status.
adapter.ref.fileListDirectory =
Pointer.fromFunction<RacFileListDirectoryCallbackNative>(
_platformFileListDirectoryCallback,
_exceptionalReturnInt32,
);
adapter.ref.isNonEmptyDirectory =
Pointer.fromFunction<RacIsNonEmptyDirectoryCallbackNative>(
_platformIsNonEmptyDirectoryCallback,
_exceptionalReturnFalse,
);
// Vendor ID — intentionally null. Apple-only slot per
// rac_platform_adapter.h:get_vendor_id; commons calls it from
// rac_device_get_or_create_persistent_id() only after secure_get
// misses. Flutter pre-populates the device-id cache in
// dart_bridge_device.dart::_getOrCreateDeviceId() using
// device_info_plus (identifierForVendor on iOS, generated UUID on
// Android) and writes it through secure_set, so the commons chain
// resolves on the secure_get branch before reaching this slot. A
// direct FFI trampoline cannot bridge UIDevice.identifierForVendor
// anyway because Flutter exposes it only via the async
// device_info_plus MethodChannel, which FFI's synchronous return
// contract cannot await.
adapter.ref.getVendorId = nullptr;
adapter.ref.userData = nullptr;
// Register with C++
final setAdapter = lib
.lookupFunction<
Int32 Function(Pointer<RacPlatformAdapterStruct>),
int Function(Pointer<RacPlatformAdapterStruct>)
>('rac_set_platform_adapter');
final result = setAdapter(adapter);
if (result != RacResultCode.success) {
_logger.error(
'Failed to register platform adapter',
metadata: {'error_code': result},
);
calloc.free(adapter);
_adapterPtr = null;
SDKException.throwIfError(result);
throw StateError('Platform adapter registration failed (rc=$result)');
}
_isRegistered = true;
_logger.debug('Platform adapter registered successfully');
// Note: We don't free the adapter here as C++ holds a reference to it
// It will be valid for the lifetime of the application
} catch (_) {
_logger.error('Platform adapter registration failed');
rethrow;
}
}