1. 概要
上記は「EventChannel」を使い、Native(Android)側と連携する内容でした。
今回は「MessageChannel」を使い、Native(Android)側と連携する内容です。
対象としては開発を1年程やってて自分で最初から開発してみたい方になります。そのため細かい用語などの説明はしません。
2. FlutterとNativeの連携方法
こちらを参考
3. プロジェクトの準備
3-1. プロジェクトを作成
3-2. Dependencyのインストール
flutter pub add flutter_riverpod
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.2
flutter_riverpod: ^1.0.4
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
4. ソースコード(Kotlin)
4-1. android/app/src/main/kotlin/com/example/batterylevel/MainActivity.kt
- このファイルはFlutterプロジェクトを作成すると、自動生成されるので内容を変更
package com.example.message_channel_demo
import android.os.Handler
import android.os.Looper
import androidx.annotation.NonNull
import androidx.annotation.Nullable
import io.flutter.Log
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.BasicMessageChannel
import io.flutter.plugin.common.StringCodec
import io.flutter.plugin.common.JSONMessageCodec
import io.flutter.plugin.common.StandardMessageCodec
import io.flutter.plugins.GeneratedPluginRegistrant
class MainActivity : FlutterActivity() {
private val channelName = "samples.flutter.dev/counter"
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
val channel : BasicMessageChannel<Any?> = BasicMessageChannel<Any?>(flutterEngine.dartExecutor.binaryMessenger, channelName, StandardMessageCodec())
val handler : MyMessageHandler = MyMessageHandler(channel)
channel.setMessageHandler(handler)
}
}
class MyMessageHandler constructor (val channel : BasicMessageChannel<Any?>) : BasicMessageChannel.MessageHandler<Any?> {
private val handler = Handler(Looper.getMainLooper())
private var counter: Int = 0
private val runnable: Runnable = object : Runnable {
override fun run() {
sendMsgToFlutter()
}
}
init {
handler.post(runnable)
}
fun sendMsgToFlutter() {
channel.send(getCounter()) { reply ->
Log.d("Android", "$reply")
}
handler.postDelayed(runnable, 4000)
}
private fun getCounter() : Map<String, Any> {
return mapOf("name" to "Native", "counter" to counter++)
}
override fun onMessage(message: Any?, reply: BasicMessageChannel.Reply<Any?>) {
val name = (message as Map<String, Any>)["name"]
val cnt = (message as Map<String, Any>)["counter"]
Log.d("Android", "<Msg From Flutter> = Name:$name, Counter:$cnt")
reply.reply("Hey, $name! Your counter is : $cnt")
}
}
- BasicMessageChannel
- https://api.flutter.dev/javadoc/io/flutter/plugin/common/BasicMessageChannel.html
- setMessageHandler
- 当チャンネルにてメッセージハンドラー(イベントリスナー)を設定
- Looper
- https://developer.android.com/reference/kotlin/android/os/Looper
- スレッドにて作成されたメッセージをFIFOで処理するMessageQueue
- Handler
- https://developer.android.com/reference/kotlin/android/os/Handler
- LooperでMessageQueue管理ができるようにするハンドラー
- post / postDelayed
- メッセージをMessageQueueに追加
- 4000ms(4秒)毎に発生し、カウントアップ
- BasicMessageChannel.MessageHandler
- https://api.flutter.dev/javadoc/io/flutter/plugin/common/BasicMessageChannel.MessageHandler.html
- onMessage
- メッセージ(+Reply)を管理
- MessageChannelが確立されたら、呼ばれる
- 「CHANNEL」について
- samples.flutter.dev/counter
- Flutter側との通路
The client and host sides of a channel are connected through a channel name passed in the channel constructor. All channel names used in a single app must be unique; prefix the channel name with a unique ‘domain prefix’, for example: samples.flutter.dev/battery.
https://docs.flutter.dev/development/platform-integration/platform-channels?tab=type-mappings-kotlin-tab
5. ソースコード(Dart)
5-1. lib/main.dart
// ignore_for_file: avoid_print
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:message_channel_demo/models/counter_data.dart';
import 'package:message_channel_demo/notifiers/counter_notifier.dart';
import 'views/home.dart';
final counterProvider =
StateNotifierProvider<CounterNotifier, CounterData>((ref) {
return CounterNotifier();
});
void main() {
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter MessageChannel',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter MessageChannel'),
);
}
}
5-2. lib/views/home.dart
// ignore_for_file: avoid_print
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:message_channel_demo/main.dart';
import '../repository/counter_repository.dart';
class MyHomePage extends ConsumerWidget {
MyHomePage({Key? key, required String title}) : super(key: key);
bool _isLoading = false;
@override
Widget build(BuildContext context, WidgetRef ref) {
final data = ref.watch(counterProvider);
final counterNotifier = ref.read(counterProvider.notifier);
if (_isLoading == false) {
_init(counterNotifier);
_isLoading = true;
}
return Scaffold(
appBar: AppBar(
title: const Text('Flutter MessageChannel'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Counter of ${data.nativeName}: ${data.nativeCounter}',
style: Theme.of(context).textTheme.headline4),
const SizedBox(height: 20),
Text('Counter of ${data.flutterName}: ${data.flutterCounter}',
style: Theme.of(context).textTheme.headline4),
],
),
),
);
}
void _init(notifier) {
final CounterRepository counterRepository = CounterRepository();
counterRepository.setMessageHandler(notifier);
int flutterCounter = 0;
counterRepository.sendMsgToNative(notifier, flutterCounter++);
Timer.periodic(const Duration(seconds: 9), (_) {
counterRepository.sendMsgToNative(notifier, flutterCounter++);
});
}
}
5-3. lib/models/counter_data.dart
import 'package:flutter/material.dart';
@immutable
class CounterData {
final String nativeName;
final int? nativeCounter;
final String flutterName;
final int? flutterCounter;
const CounterData(
{required this.nativeName,
required this.nativeCounter,
required this.flutterName,
required this.flutterCounter});
}
5-4. lib/repository/counter_repository.dart
// ignore_for_file: avoid_print
import 'package:flutter/services.dart';
class CounterRepository {
static const _channel = BasicMessageChannel<dynamic>(
'samples.flutter.dev/counter', StandardMessageCodec());
void setMessageHandler(notifier) {
_channel.setMessageHandler((message) async {
var name = message['name'] as String;
var cnt = message['counter'] as int;
notifier.setNativeData(name, cnt);
print('[Msg From Native] = Name:$name, Counter:$cnt');
return 'Hey, $name! Your counter is : $cnt';
});
}
void sendMsgToNative(notifier, flutterCounter) async {
final reply = await _channel.send(<String, dynamic>{
'name': 'Flutter',
'counter': '$flutterCounter',
});
notifier.setFlutterData('Flutter', flutterCounter);
print(reply);
}
}
Native側のメソッドを呼び出す。
- 「MessageChannel」
- samples.flutter.dev/counter
- Native側との通路
- 「setMessageHandler」
- Native側よりメッセージを受信
- +Reply
- 「sendMsgToNative」
- Native側へメッセージを送信
5-5. lib/notifiers/counter_notifier.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:message_channel_demo/models/counter_data.dart';
class CounterNotifier extends StateNotifier<CounterData> {
CounterNotifier()
: super(const CounterData(
nativeName: '',
nativeCounter: 0,
flutterName: '',
flutterCounter: 0));
void setNativeData(String nativeName, int? nativeCounter) {
state = CounterData(
nativeName: nativeName,
nativeCounter: nativeCounter,
flutterName: state.flutterName,
flutterCounter: state.flutterCounter);
}
void setFlutterData(String flutterName, int? flutterCounter) {
state = CounterData(
nativeName: state.nativeName,
nativeCounter: state.nativeCounter,
flutterName: flutterName,
flutterCounter: flutterCounter);
}
}
6. 結果
6-1. キャプチャー
6-2. ログ
D/Android ( 3049): <Msg From Flutter> = Name:Flutter, Counter:0
I/flutter ( 3049): [Msg From Native] = Name:Native, Counter:0
D/Android ( 3049): Hey, Native! Your counter is : 0
I/flutter ( 3049): Hey, Flutter! Your counter is : 0
I/flutter ( 3049): [Msg From Native] = Name:Native, Counter:1
D/Android ( 3049): Hey, Native! Your counter is : 1
I/flutter ( 3049): [Msg From Native] = Name:Native, Counter:2
D/Android ( 3049): Hey, Native! Your counter is : 2
D/Android ( 3049): <Msg From Flutter> = Name:Flutter, Counter:1
I/flutter ( 3049): Hey, Flutter! Your counter is : 1
I/flutter ( 3049): [Msg From Native] = Name:Native, Counter:3
D/Android ( 3049): Hey, Native! Your counter is : 3
I/flutter ( 3049): [Msg From Native] = Name:Native, Counter:4
D/Android ( 3049): Hey, Native! Your counter is : 4
D/Android ( 3049): <Msg From Flutter> = Name:Flutter, Counter:2
I/flutter ( 3049): Hey, Flutter! Your counter is : 2
I/flutter ( 3049): [Msg From Native] = Name:Native, Counter:5
D/Android ( 3049): Hey, Native! Your counter is : 5
I/flutter ( 3049): [Msg From Native] = Name:Native, Counter:6
D/Android ( 3049): Hey, Native! Your counter is : 6
I/flutter ( 3049): [Msg From Native] = Name:Native, Counter:7
D/Android ( 3049): Hey, Native! Your counter is : 7
D/Android ( 3049): <Msg From Flutter> = Name:Flutter, Counter:3
I/flutter ( 3049): Hey, Flutter! Your counter is : 3
I/flutter ( 3049): [Msg From Native] = Name:Native, Counter:8
D/Android ( 3049): Hey, Native! Your counter is : 8
I/flutter ( 3049): [Msg From Native] = Name:Native, Counter:9
D/Android ( 3049): Hey, Native! Your counter is : 9
D/Android ( 3049): <Msg From Flutter> = Name:Flutter, Counter:4
I/flutter ( 3049): Hey, Flutter! Your counter is : 4
I/flutter ( 3049): [Msg From Native] = Name:Native, Counter:10
D/Android ( 3049): Hey, Native! Your counter is : 10
7. 参考
投稿者プロフィール
-
開発好きなシステムエンジニアです。
卓球にハマってます。