転職したらスマレジだった件

スマレジのエンジニアやまてのテックブログです。マジレス大歓迎です。

Flutter|MethodChannel を使って Kotlin コードを実行するサンプルアプリの実装

スマレジの テックファーム(SES 部門) でWebエンジニアとして働いている やまて(@r_yamate) と申します。

実務では SES の派遣先で、テーブルオーダーシステムの機能改修業務の設計などを担当しています。(2022 年 3 月〜 2023 年 3 月末予定)

2023 年 1 月からは Flutter を実務で使用することになって、キャッチアップ中です。

はじめに

この記事では、 Flutter の MethodChannel を使って Kotlin のコードを実行するサンプルアプリの実装手順をまとめます。

MethodChannel とは

api.flutter.dev

docs.flutter.dev

MethodChannel は、 Flutter とネイティブプラットフォーム(今回は Android)の間でメッセージをやり取りするための仕組みです。この機能を使うことで、 Flutter アプリがネイティブプラットフォームの機能を呼び出すことができます。

(実務で Flutter から Android アプリ用の SDK ライブラリを利用するために MethodChannel を使うことがあり、使い方を確認しました。)

実装するサンプルアプリ

サンプルアプリでは、ユーザーが ab (例:1020)の2つの数値をが入力して「計算」ボタンを押すと、a+bの計算結果(例:30)を返す、という簡単な機能を実装します。

Flutter(Dart)で画面を作成し、 Dart は Kotlin に MethodChannel で接続して、 Kotlin で書いた a+b の計算をするメソッドを呼び出します。

Kotlin で計算した結果を Flutter に返し、返した値をアプリ画面に表示します。

処理フローのイメージ

※ 「a+b の計算処理を実行」する処理の部分を、Android アプリ用の SDK ライブラリで API 実行する処理にすれば、その処理結果を Flutter 側に返すことができます。

※ MethodChannel は、一意の識別子(例: CHANNEL 定数)を使って Flutter 側とネイティブ側で通信を行います。 Flutter 側からメソッドを呼び出すと、ネイティブ側の MethodChannel で設定されたハンドラが呼び出され、メソッド名や引数を受け取って処理を行います。処理が終わったら、結果を Flutter 側に返すことができます。この仕組みを使って、 Flutter アプリとネイティブプラットフォームのコードを相互に呼び出して連携させることができます。

環境

  • MacBookPro(Intel チップ)
    • macOS Monterey バージョン 12.6.1
  • Flutter バージョン 3.3.10

目次

今回の記事の内容のコードです。

github.com

1. Flutter プロジェクトの作成

IDE や flutter create コマンドで、Flutter プロジェクトを新規作成します。

flutter create method_channel_sample

作成されているプロジェクトの lib/main.dart は以下の内容です。(コメントアウトは削除します。)

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

Android エミュレーターを起動して、アプリを実行すると、ボタンを押した回数が表示されるサンプルのコードであることがわかります。

こちらをベースに書き換えていきます。

2. Flutter 画面の作成

lib/main.dart に以下のコードを追加して、 UI を作成します。

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const CalculatorScreen(title: 'Calculator'),
    );
  }
}

class CalculatorScreen extends StatefulWidget {
  const CalculatorScreen({super.key, required this.title});

  final String title;

  @override
  State<CalculatorScreen> createState() => _CalculatorScreenState();
}

class _CalculatorScreenState extends State<CalculatorScreen> {
  final TextEditingController _controllerA = TextEditingController();
  final TextEditingController _controllerB = TextEditingController();
  String _result = '';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 16.0),
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              TextField(
                controller: _controllerA,
                keyboardType: TextInputType.number,
                decoration: const InputDecoration(labelText: 'a'),
              ),
              TextField(
                controller: _controllerB,
                keyboardType: TextInputType.number,
                decoration: const InputDecoration(labelText: 'b'),
              ),
              ElevatedButton(
                onPressed: _calculate,
                child: const Text('計算'),
              ),
              Text(_result),
            ],
          ),
        ),
      ),
    );
  }

  void _calculate() async {
    // ここでKotlinのコードを呼び出す
  }
}
  • TextEditingControllerオブジェクト(_controllerA_controllerB)を使って、テキストフィールドの入力値を管理します。_resultは、計算結果を格納する文字列です。
  • _calculateは、ElevatedButtononPressedイベントで呼び出されるメソッドです。

3. MethodChannel の設定

MethodChannel を使って Flutter とネイティブプラットフォーム(Kotlin)間で通信を行う設定を行います。

3-1. Dart 側の設定

lib/main.dart に以下のコードを追加して、 MethodChannel を設定します。

import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; // 追加

// 略

class CalculatorScreen extends StatefulWidget {
// 略
}

class _CalculatorScreenState extends State<CalculatorScreen> {
    // 略

  // ① Method Channelを初期化する
  static const platform= MethodChannel('sample.flutter.dev/calculator');

  @override
  Widget build(BuildContext context) {
        // 略
  }

  // ② Method Channelを呼び出す
  void _calculate() async {
    try {
      final int a = int.parse(_controllerA.text);
      final int b = int.parse(_controllerB.text);
      final int result = awaitplatform.invokeMethod('add', {'a': a, 'b': b});
      setState(() {
        _result = '結果: $result';
      });
    } on PlatformException catch (e) {
      setState(() {
        _result = 'エラーが発生しました: ${e.message}';
      });
    }
  }
}

MethodChannel の初期化と、 Kotlin 側で定義されたメソッド(この場合はadd メソッド)を呼び出す処理を追加しました。

3-2. Kotlin 側の設定

MainActivity クラスに MethodChannel と計算ロジックを追加します。

編集:android/app/src/main/kotlin/com/example/method_channel_sample/MainActivity.kt

package com.example.method_channel_sample

import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel

class MainActivity: FlutterActivity() {
    private val CHANNEL = "sample.flutter.dev/calculator"

    override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
            if (call.method == "add") {
                val a = call.argument<Int>("a") ?: 0
                val b = call.argument<Int>("b") ?: 0
                result.success(add(a, b))
            } else {
                result.notImplemented()
            }
        }
    }

    private fun add(a: Int, b: Int): Int {
        return a + b
    }
}

4. アプリの実行

アプリを実行して、ユーザーが ab を入力し、「計算」ボタンを押すと a+b の計算結果が表示されることを確認します。

おわりに

Flutter の MethodChannel を使って Kotlin のコードを実行するサンプルアプリの実装が完了しました。

Flutter は触ったばかりな上、Kotlin のコードも分からない私には難易度が高かったですが、 MethodChannel を理解しようとすることで、 Flutter 自体について理解が深まった部分もあり、とても勉強になりました。

今回の記事は以上です。ありがとうございました。

参考

api.flutter.dev

docs.flutter.dev

qiita.com

zenn.dev



今回記事にした MethodChannel も何も分からない状態から、使い方ちょっと分かるところまでは来れた。そのことをちゃんと喜ぼうと思います。