How to Call Dart from Android or iOS Native Code
Add a Dart library to a native app to work seamlessly with Kotlin, Java, Swift, or Objective-C code
Imagine discovering an awesome Dart library… the tiki-sdk-dart library, for example (which is both awesome and in Dart). If you want to use that library in an Android or iOS app, the no-brainer solution is to create/add it to a Flutter app. Then use Flutter to compile the entire app to native platform code.
But what if you already have a native app? How do you add a Dart library to a native app to work seamlessly with your Kotlin, Java, Swift, or Objective-C code?
In this case, the Dart code is compiled ahead-of-time (AOT) into an XCFramework (iOS) or AAR (Android) that can be imported into your native project. Then you wrap the Dart code in native functions to expose the required Dart APIs.
Sounds like too much work? It is! Luckily, this is precisely what Flutter does when compiling apps natively. If Flutter already does this, why not leverage Flutter to package Dart code for native libraries?
One of Flutter’s features is the ability to call native code from Dart code using Platform Channels. In Flutter, it is easy to find example documentation on how to call native APIs with Dart code. But what is not obvious is that Platform Channels are a two-way binding, making it possible to call Dart code from native code, too.
Dart Project
First, we set up a new Dart project called hello_tiki.
hello_tiki/lib/hello_tiki.dart
class HelloTiki {
void hello() {
print("hello 🍍!");
}
}
hello_tiki/pubspec.yaml
name: hello_tiki
description: Hello TIKI library.
version: 1.0.0+1
environment:
sdk: '>=2.18.2 <3.0.0'
dependencies:
flutter:
sdk: flutter
...
Flutter Plugin
Flutter Plugins are a special type of package combining Dart code and platform-specific (native) code. Without a plugin, if you try to add your platform-specific code to the .android or .ios folders, it will be overwritten anytime you run a flutter pub get. In the parent directory of your Dart project, create a plugin.
flutter create -t plugin --platforms android,ios hello_tiki_plugin
Adding your Dart Project as a plugin dependency.
hello_tiki_plugin/pubspec.yaml
name: hello_tiki_plugin
description: A new Flutter plugin project.
version: 0.0.1
environment:
sdk: '>=2.18.4 <3.0.0'
flutter: ">=2.5.0"
dependencies:
flutter:
sdk: flutter
hello_tiki:
path: ../hello_tiki
plugin_platform_interface: ^2.0.2
...
flutter:
plugin:
platforms:
android:
package: com.example.hello_tiki_plugin
pluginClass: HelloTikiPlugin
ios:
pluginClass: HelloTikiPlugin
In the plugin project, we’re going to add the Platform Channel handlers for both Dart and iOS/Android. First, for the Dart side, Flutter generated 3 files (hello_tiki_plugin, hello_tiki_plugin_method_channel, and hello_tiki_plugin_platform_interaction). First, add the method channel constructor to the hello_tiki_plugin.
hello_tiki_plugin/lib/hello_tiki_plugin.dart
...
class HelloTikiPlugin {
final MethodChannelHelloTikiPlugin methodChannelHelloTikiPlugin;
HelloTikiPlugin()
: methodChannelHelloTikiPlugin = MethodChannelHelloTikiPlugin();
...
}
Next, define your method handlers in the hello_tiki_plugin_method_channel method call handler
hello_tiki_plugin/lib/hello_tiki_plugin_method_channel.dart
...
import 'package:hello_tiki/hello_tiki.dart';
class MethodChannelHelloTikiPlugin extends HelloTikiPluginPlatform {
...
Future<void> methodHandler(MethodCall call) async {
switch (call.method) {
case "hello_tiki":
HelloTiki().hello();
break;
}
}
}
With our Dart side of the handlers defined, we’re going to flip over and set up the Android/iOS handlers. Plugins are constructed and registered the FlutterEngine PluginRegistry on engine construction —meaning, to explicitly call plugin functions, you need to retrieve the registered instance.
hello_tiki_plugin/android/src/main/kotlin/com/example/hello_tiki_plugin/HelloTikiPlugin.kt
class HelloTikiPlugin() : FlutterPlugin, MethodCallHandler {
private lateinit var channel: MethodChannel
companion object {
fun fromEngine(flutterEngine: FlutterEngine): HelloTikiPlugin {
return flutterEngine.plugins.get(HelloTikiPlugin::class.java) as HelloTikiPlugin
}
}
...
fun sayHello() {
channel.invokeMethod("hello_tiki", null)
}
}
hello_tiki_plugin/ios/Classes/SwiftHelloTikiPlugin.swift
import Flutter
import UIKit
public class SwiftHelloTikiPlugin: NSObject, FlutterPlugin {
var channel: FlutterMethodChannel
public init(registrar: FlutterPluginRegistrar) {
channel = FlutterMethodChannel(name: "hello_tiki_channel", binaryMessenger: registrar.messenger())
super.init()
registrar.addMethodCallDelegate(self, channel: channel)
}
public static func register(with registrar: FlutterPluginRegistrar) {
let instance = SwiftHelloTikiPlugin(registrar: registrar)
}
...
public func sayHello(){
channel.invokeMethod("hello_tiki", arguments: nil)
}
}
Now that we have Dart code that can be called from native functions, we can package it up to run in our iOS/Android apps.
Flutter Module
The Flutter Module is a special kind of package that allows Flutter code to be imported and used inside native projects. It is mainly used in the add-to-app process —when you want to add Flutter code to an existing project without having to rewrite the whole project in Flutter.
So, if we want to import our hello-tiki example into a native project, we must first create a Flutter module with the Dart code. The Flutter module will compile our code, embed the Dart VM, and package it into a native library. Create a Flutter module in the parent directory for your plugin:
flutter create -t module hello_tiki_module
Next, we add the plugin as a dependency to the module.
hello_tiki_module/pubspec.yaml
name: hello_tiki_module
description: Flutter Module to Hello TIKI library.
version: 1.0.0+1
environment:
sdk: '>=2.18.2 <3.0.0'
dependencies:
flutter:
sdk: flutter
hello_tiki_plugin:
path: ../hello_tiki_plugin
...
flutter:
uses-material-design: true
module:
androidX: true
androidPackage: com.example.hello_tiki_module
iosBundleIdentifier: com.example.hello_tiki_module
Import to iOS or Android
Now we can follow the add-to-app instructions and compile our library for iOS or Android. I have chosen the simplest approaches for Android and iOS: Gradle and CocoaPods.
Note: These steps assume you already have working iOS and Android projects. If you do not, first create demo projects for each in the same parent directory as your Dart module.
Android (Gradle):
Make sure that your hello_tiki_module is in the same parent directory as your Android Project. If not, adjust the file routes in #2 accordingly.
Include the Flutter module as a subproject in the host app’s settings.gradle by adding to the bottom of the file:
setBinding(new Binding([gradle: this])) evaluate(new File( settingsDir.parentFile, 'hello_tiki_module/.android/include_flutter.groovy' )) include ':hello_tiki_module' project (':hello_tiki_module').projectDir = new File(settingsDir.getParentFile(), 'hello_tiki_module')
Note: If you get a “Build was configured to prefer settings repositories over project repositories but repository 'maven' was added by plugin class 'FlutterPlugin'“ error, then
Remove the dependencyResolutionManagement block from settings.gradle file.
Add to the project’s build.gradle
allprojects { repositories { google() mavenCentral() } }
Add implementation project(':flutter') to your App’s build.gradle dependencies block
Run a Gradle Sync
Note: Android Studio may warn about the "Binding" import. You can safely ignore it.
Once it is synced, you need to configure the FlutterEngine. If you’re using Flutter UIs, FlutterActivity has some easy helper methods to do this. In this example, we’re not, so we’re going to init the engine manually.
private fun initFlutter(context: Context): FlutterEngine { val loader = FlutterLoader() loader.startInitialization(context) loader.ensureInitializationComplete(context, null) val flutterEngine = FlutterEngine(context) flutterEngine.dartExecutor.executeDartEntrypoint( DartExecutor .DartEntrypoint .createDefault() ) GeneratedPluginRegister.registerGeneratedPlugins(flutterEngine) return flutterEngine }
Call init on Application startup. For example, in onCreate for your main Activity
... class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { ... val flutterEngine = initFlutter(this.applicationContext) ... } }
Done! Now, you can use the Hello TIKI methods in Android:
HelloTikiPlugin.fromEngine(flutterEngine).sayHello()
In Logcat look for "hello 🍍!"
iOS (CocoaPods):
Set the platform version to 13
platform :ios, '13.0'
Add the following lines to your Podfile:
Note: Change flutter_application_path if the module is not in the parent directory.
flutter_application_path = '../hello_tiki_module' load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
For each Podfile target that needs to embed Flutter, call:
install_all_flutter_pods(flutter_application_path)
for example:
target 'MyApp' do install_all_flutter_pods(flutter_application_path) end
In the Podfile’s post_install block, call flutter_post_install (installer):
post_install do |installer| flutter_post_install(installer) if defined?(flutter_post_install) end
Run pod install
The final step is to configure the FlutterEngine and register the plugin. We use a manual plugin registration to retain an instance of the FlutterPlugin to call the sayHello function.
class FlutterDependencies: ObservableObject { let flutterEngine = FlutterEngine(name: "my flutter engine") var helloTikiPlugin: SwiftHelloTikiPlugin init(){ flutterEngine.run() let registrar = flutterEngine.registrar(forPlugin: "HelloTikiPlugin") helloTikiPlugin = SwiftHelloTikiPlugin(registrar: registrar!) } @main struct D2N_ExampleApp: App { @StateObject var flutterDependencies = FlutterDependencies() ... } }
Done! Now you can use Hello Tiki methods in iOS:
helloTikiPlugin.sayHello()
And that’s it! You’re calling Dart functions from native code by leveraging Flutter Platform Channels.
This is a very simple example and works very well for internal code or simple dart library consumption. To learn how to build and distribute native libraries written in Dart, like we do for tiki-sdk-dart, check out our platform-specific blogs.
Hi, thanks for the great tutorial. This is what exactly I am looking for. However, after following this tutorial, I still cannot invoke my flutter method from native android. Could you please provide the entire code example? I go to the link you put in this article but unfortunately, the code is not available anymore. And how about the return value of dart method is not void but some types such as String? How to get the value from native and display it from Android application?
Thanks a lot for the post and your help!
Great article and tutorial! However, is it possible to pass a parameter from native to our dart package?
for example i want to pass an integer (a &b) inside the plugin
channel.invokeMethod("sum",a,b null), but i got error..