Flutter to Native Code
How to call native code from Flutter
Pengenalan
Flutter yang merupakan salah satu teknologi yang memiliki kecanggihan di lintas platform sepertinya agak sedikit aneh jika mereka sendiri tidak menyediakan jalan bagi developer untuk bisa melakukan pemanggilan kode native dari Flutter. Seandainya Flutter tidak bisa memberi jalan bagi para developer untuk melakukan pemanggilan kode native dari Flutter lalu hal ini akan memunculkan pertanyaan besar bagi kita semua yaitu,
Bagaimana bisa sebuah teknologi lintas platform dengan demo yang mengatakan Flutter enabled build beautiful native apps tapi merekanya sendiri tidak memberikan jalan bagi kita untuk melakukan pemanggilan kode native dari Flutter?
Dari pertanyaan tersebut saya memiliki 2 jawaban yang bisa saya tuliskan yaitu sebagai berikut.
- Bahwa teknologi lintas platform sebenarnya tidak akan bisa menyediakan jalan bagi para developer untuk melakukan pemanggilan kode native dikarenakan produk tersebut tidak memiliki kemampuan untuk berkomunikasi langsung ke kode native.
- Teknologi lintas platform seharusnya bisa menyediakan jalan bagi developer untuk memanggil kode native langsung. Karena seperti yang kita ketahui bahwa setiap platform seperti Android dan iOS memiliki fitur-fitur native yang terkadang di produk lintas platform tidak memiliki kemampuan untuk membuatnya sehingga jalan satu-satunya adalah kita harus menggunakan kode native untuk membuat fitur tersebut.
Lalu, pertanyaannya adalah
Apakah Flutter sebagai pendatang baru di teknologi lintas platform memiliki kemampuan untuk bisa berkomunikasi langsung dengan kode native?
Jawabannya adalah bisa. Teknologi besutan Google ini tidak mungkin mereka tidak memikirkan masalah hal seperti ini. Karena, Google sendiri pun memiliki Android jadi, sudah pasti mereka akan memikirkan bagaimana jikalau Flutter dimasa yang akan datang ternyata tidak bisa memiliki fitur native di platform tertentu. Sudah pasti jalan satu-satunya adalah harus memanggil kode native-nya dari Flutter. Dan percaya nggak percaya ternyata selama ini beberapa plugin yang kita pakai di website pub.dev merupakan hasil komunikasi langsung dengan kode native. Berikut saya tunjukkan salah satu contoh plugin-nya.
Dan berikut gambaran kode untuk komunikasi dari Flutter ke native-nya.
Perhatikan baik-baik pada gambaran kode diatas bahwa di Flutter untuk bisa berkomunikasi dengan kode native-nya kita menggunakan Platform Channel. Info lengkap mengenai apa itu Platform Channel bisa dibaca di link berikut.
Contoh 1
Pada contoh pertama ini kita akan mencoba membuat sebuah aplikasi sederhana yang akan memanggil fungsi dari kode native-nya dan kemudian hasil dari fungsi native tersebut akan kita tampilkan ke Flutter. Untuk langkah pertamanya silakan buat projek baru dan beri nama projeknya flutter_to_native_code. Untuk contoh kali ini kita pakai Kotlin dan Swift untuk kode native-nya.
Lalu, kita buka file main.dart dan isikan dengan kode berikut.
import 'package:flutter/material.dart';
void main() => runApp(App());
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MyApp(),
);
}
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _message;
@override
void initState() {
_message = 'Hello Yudi Setiawan, from Flutter code';
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter to Native Code'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
child: Text('Tap Me'),
onPressed: () {
// TODO: do something in here
},
),
Text(_message),
],
),
),
);
}
}
Output dari kode tersebut adalah seperti berikut.
Sekarang kita perlu mendeklarasikan objek MethodChannel
seperti berikut.
class _MyAppState extends State<MyApp> {
static const platform = const MethodChannel('flutter.native.code/example');
...
flutter.native.code/example
merupakan nama channel yang akan kita buat dan pastikan juga bahwa nama channel tersebut haruslah unik. Selanjutnya, kita ubah kode komentar // TODO: do something in here
menjadi seperti berikut.
try {
_message = await platform.invokeMethod('greetingFromNativeCode');
} on PlatformException catch (e) {
_message = 'Failed to invoke: ${e.message}';
}
setState(() {});
Kode diatas berfungsi untuk mengirimkan trigger ke kode native bahwa si Flutter perlu memanggil method dengan nama greetingFromNativeCode
. Sekarang coba jalankan ulang app-nya. Dan test tap button Tap Me. Yang terjadi ialah kita akan mendapatkan pesan error seperti berikut.
Pesan error tersebut mengatakan bahwa method dengan nama greetingFromNativeCode
tidak tersedia di kode native. Oleh karena itu, kita perlu membuatnya di file MainActivity.kt dan AppDelegate.swift. Untuk yang Android kita perlu buka file MainActivity.kt dan isi dengan kode berikut.
import android.os.Bundle
import io.flutter.app.FlutterActivity
import io.flutter.plugins.GeneratedPluginRegistrant
import io.flutter.plugin.common.MethodChannel
class MainActivity: FlutterActivity() {
val channel = "flutter.native.code/example"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GeneratedPluginRegistrant.registerWith(this)
MethodChannel(flutterView, channel).setMethodCallHandler { call, result ->
if (call.method == "greetingFromNativeCode") {
result.success("Hello Yudi Setiawan, from Android code")
}
}
}
}
Kode tersebut adalah kode di Kotlin bahwa kita ada mendeklarasikan objek MethodChannel
dengan nama channel yang sama yang sudah kita deklarasikan tadi di kode Dart. Lalu, didalamnya ada callback dimana, kita ambil nilai nama method yang akan dipanggil oleh si kode Dart. Dan kita buat pengkondisiannya untuk setiap kemungkinan method yang akan dipanggil oleh kode Dart. Pada contoh diatas kita ada menambahkan kondisi untuk method dengan nama greetingFromNativeCode
. Jika sesuai nama methodnya maka, kita kembalikan hasil success ke Dart dengan kode berikut.
result.success("...")
Pada contoh, diatas kita hanya kembalikan nilai String dengan pesan "Hello Yudi Setiawan, from Android code"
Sekarang coba jalankan ulang lagi app-nya dari awal dan test tap button-nya lagi. Seharusnya berhasil dan akan menghasilkan output seperti berikut.
Selanjutnya, kita perlu buat untuk yang iOS. Silakan buka file AppDelegate.swift dan ubah kode didalamnya menjadi seperti berikut.
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let channel = FlutterMethodChannel(name: "flutter.native.code/example", binaryMessenger: controller.binaryMessenger)
channel.setMethodCallHandler({
(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
if (call.method == "greetingFromNativeCode") {
result("Hello Yudi Setiawan, from iOS code")
}
})
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
Dan berikut output untuk yang iOS.
Contoh 2
Pada contoh pertama kita telah berhasil membuat Flutter bisa memanggil kode native. Sekarang kita coba untuk contoh yang kedua dimana, kita ada kirim data ke kode native lalu native mengembalikan hasilnya ke Flutter. Sekarang, kita buka file main.dart dan tambahkan widget TextField
dan letakkan tepat diatas dari widget RaisedButton
. Berikut ialah kode lengkapnya.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(App());
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MyApp(),
);
}
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
static const platform = const MethodChannel('flutter.native.code/example');
String _message;
TextEditingController _controllerNama;
@override
void initState() {
_message = 'Hello Yudi Setiawan, from Flutter code';
_controllerNama = TextEditingController();
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter to Native Code'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0,
vertical: 8.0,
),
child: TextField(
controller: _controllerNama,
decoration: InputDecoration(hintText: "Input your name"),
),
),
RaisedButton(
child: Text('Tap Me'),
onPressed: () async {
try {
_message = await platform.invokeMethod(
'greetingFromNativeCode',
);
} on PlatformException catch (e) {
_message = 'Failed to invoke: ${e.message}';
}
setState(() {});
},
),
Text(_message),
],
),
),
);
}
}
Output dari kode tersebut adalah seperti berikut.
Jadi, kita akan membuat si Flutter mengirimkan data yang dari widget TextField
ke kode native. Silakan kita ubah fungsi didalam property onPressed
menjadi seperti berikut.
try {
String name = _controllerNama.text;
_message = await platform.invokeMethod(
'greetingFromNativeCode',
{'name': name},
);
} on PlatformException catch (e) {
_message = 'Failed to invoke: ${e.message}';
}
setState(() {});
Selanjutnya, kita ubah juga kode di file MainActivity.kt menjadi seperti berikut.
package com.example.flutter_to_native_code
import android.os.Bundle
import io.flutter.app.FlutterActivity
import io.flutter.plugins.GeneratedPluginRegistrant
import io.flutter.plugin.common.MethodChannel
class MainActivity: FlutterActivity() {
val channel = "flutter.native.code/example"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GeneratedPluginRegistrant.registerWith(this)
MethodChannel(flutterView, channel).setMethodCallHandler { call, result ->
if (call.method == "greetingFromNativeCode") {
val name = call.argument<String>("name") ?: "unknown"
result.success("Hello $name, from Android code")
}
}
}
}
Dan yang terakhir kita perlu ubah juga kode yang didalam file AppDelegate.swift menjadi seperti berikut.
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let channel = FlutterMethodChannel(name: "flutter.native.code/example", binaryMessenger: controller.binaryMessenger)
channel.setMethodCallHandler({
(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
if (call.method == "greetingFromNativeCode") {
let args = call.arguments as? [String: String]
let name = args!["name"]
result("Hello " + name! + ", from iOS code")
}
})
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
Jadi, pada kode di file MainActivity.kt dan AppDelegate.swift kita ada menambahkan kode untuk menangani data yang dikirimkan dari si Flutter. Dan berikut ialah output terakhirnya.
Kesimpulan
Tanpa kita sadari ternyata beberapa plugin yang ada di website pub.dev ternyata merupakan hasil dari Platform Channel. Yaitu kode-kode Flutter yang sebenarnya melakukan proses pemanggilan kode-kode native. Sampai sekarang plugin-plugin di Flutter semakin hari semakin meningkat dan keterbatasan Flutter untuk menggunakan fitur-fitur native platform sepertinya sudah tidak bisa menjadi alasan lagi bagi kita yang mau mulai mempelajarinya tapi masih ragu dengan fitur-fitur native-nya. Untuk projek lengkapnya silakan lihat di Github ya.