Dart native interop can be structured into three layers:
The goal of this layer is to provide a no-compromise, high-performance layer to make non-Dart APIs and libraries accessible from Dart. This layer is designed to be unopinionated, allowing threading, data transfer, and isolate life cycle to be built upon it. The APIs provided and generated are very thin Dart wrappers around the native APIs. They favor zero-cost abstractions such as extension types. While using this layer directly offers the most possibilities, it can lead to writing creole[^1] and presents footguns regarding memory management, threading, and the isolate and VM life cycle.
Technologies in this layer:
dart:ffi and dart:js_interop.Design principles for tech in this layer:
The objective of this layer is to offer a Dart-y API that bridges the impedance mismatch between Dart and the target language. Technologies in this layer are opinionated and may make domain-specific choices about value conversions (shallow or deep), concurrency models, etc.. They might sacrifice performance for improved usability and may provide adapters for implementing Dart types such as Map and List.
Technologies in this layer:
Design principles for tech in this layer:
This layer's purpose is to provide integration with specific SDKs built on Dart. This layer is optional. If a solution can be provided as a layer 2 solution, it should be, as this makes it reusable across multiple SDKs.
Technologies in this layer:
Layer 2 solutions have the option to provide “native escape hatches” to layer 1. This allows layer 1 to give immediate access to new OS APIs while layer 2 is being updated.
[^1]: A “creole” language, in this context, refers to code that is technically Dart but is written using the objects and patterns of the underlying native language (e.g., Objective-C). This creates a “mixed” language with its own learning curve and challenges, particularly around memory management and concurrency.