【Flutter】FlutterとKotlinでそれぞれCounterを実装しMessageChannelを使い、双方向でメッセージを送受信

flutter

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")
    }
}

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. 参考

投稿者プロフィール

Sondon
開発好きなシステムエンジニアです。
卓球にハマってます。

関連記事

  1. flutter

    【Flutter】Flutter実装メモ  enumの拡張

  2. 【Widget of the Week】#6 FutureBuilde…

  3. flutter

    【Flutter】コード自動生成とGraphQLClientを使ってG…

  4. flutter

    【Flutter】リリース,Android,iOS

  5. flutter

    【Flutter】2回目以降のリリース,Android,iOS

  6. flutter

    【Flutter】GoogleMapを使用したアプリの開発について

最近の記事

  1. AWS
  2. AWS
  3. AWS
  4. AWS
  5. flutter

制作実績一覧

  1. Checkeys