LoginSignup
54
43

More than 3 years have passed since last update.

FlutterからKotlin/Swiftのネイティブコードを呼んでみた

Last updated at Posted at 2019-11-05

FlutterからネイティブAPIを呼びたい

MethodChannelを使って、FlutterからKotlin(Android)とSwift(iOS)のネイティブコード呼び出しを試してみました。

Writing custom platform-specific codeに記載のサンプルコードを動かしてみたので、実際の流れをメモしておきます。

Java/Objective-C版はこちら
FlutterからJava/Objective-Cのネイティブコードを呼んでみた - Qiita

サンプルソースはこちら
https://github.com/unsolublesugar/flutter-sample-custom-platform-specific-code

サンプルプロジェクトの作成

まずはサンプルプロジェクトを作成します。サンプルアプリでは、バッテリー残量を取得するネイティブコードを実装します。
$ flutter create batterylevel

新規プロジェクト作成時にKotlinとSwiftとがサポートされていなかった場合は、以下フラグ付きコマンドで作成しなおしてください。

$ flutter create -i swift -a kotlin batterylevel

※ドキュメントによると、デフォルトでJava、Objective-Cがサポートされているとのことですが、v1.9.1ではオプションなしでKotlinとSwiftが使えました。

-i, --ios-language             [objc, swift (default)]
-a, --android-language         [java, kotlin (default)]

Flutter側の呼び出しコード

サンプル画面のWidgetとバッテリー残量取得メソッドの実装を加えます。以下、_MyHomePageStateのみ抜粋したmain.dartファイルです。

main.dart
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
...
class _MyHomePageState extends State<MyHomePage> {
  static const platform = const MethodChannel('samples.flutter.dev/battery');

  String _batteryLevel = 'Unknown battery level.';

  Future<void> _getBatteryLevel() async {
    // Get battery level.
    String batteryLevel;
    try {
      final int result = await platform.invokeMethod('getBatteryLevel');
      batteryLevel = 'Battery level at $result % .';
    } on PlatformException catch (e) {
      batteryLevel = "Failed to get battery level: '${e.message}'.";
    }

    setState(() {
      _batteryLevel = batteryLevel;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Material(
      child: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            RaisedButton(
              child: Text('Get Battery Level'),
              onPressed: _getBatteryLevel,
            ),
            Text(_batteryLevel),
          ],
        ),
      ),
    );
  }
}

MethodChannelで呼び出すバッテリー残量取得メソッドgetBatteryLevelは、KotlinとSwift側でそれぞれ定義していきます。

なお、ひとつのアプリで使用されるチャンネル名は、一意である必要があります。チャンネル名の前にはドメインプレフィックスを付けます。
MethodChannel('samples.flutter.dev/battery');

Kotlin側のネイティブコード(Android)

それではKotlin(Android)のコードを書いていきましょう。

必要なimportの追加

MainActivity.ktにimportを追加。

MainActivity.kt
import android.os.Bundle
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES

import io.flutter.app.FlutterActivity
import io.flutter.plugins.GeneratedPluginRegistrant
import io.flutter.plugin.common.MethodChannel

getBatteryLevelを実装

MainActivityonCreateメソッド内でMethodChannelを定義し、setMethodCallHandlerを呼び出します。

MainActivity.kt
class MainActivity: FlutterActivity() {
  private val CHANNEL = "samples.flutter.dev/battery"

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    GeneratedPluginRegistrant.registerWith(this)

    MethodChannel(flutterView, CHANNEL).setMethodCallHandler { call, result ->
    }
  }
}

ここで定義したチャンネル名samples.flutter.dev/batteryを、Flutter側の呼び出しで使用します。

Android端末のバッテリーレベルを取得するコードを追加します。

MainActivity.kt

  private fun getBatteryLevel(): Int {
    val batteryLevel: Int
    if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
      val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
      batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
    } else {
      val intent = ContextWrapper(applicationContext).registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
      batteryLevel = intent!!.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
    }
    return batteryLevel
  }

getBatteryLevel()MethodChannelで呼び出すコードを追加。

MainActivity.kt
  private val CHANNEL = "samples.flutter.dev/battery"

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    GeneratedPluginRegistrant.registerWith(this)

    MethodChannel(flutterView, CHANNEL).setMethodCallHandler { call, result ->
      if (call.method == "getBatteryLevel") {
        val batteryLevel = getBatteryLevel()
        if (batteryLevel != -1) {
          result.success(batteryLevel)
        } else {
          result.error("UNAVAILABLE", "Battery level not available.", null)
        }
      } else {
        result.notImplemented()
      }
    }
  }

これでAndroid用Kotlinネイティブコードの呼び出し準備は完了です。

エミュレーター上で実行

上記コードをエミュレーター上でbuild runすると「Get Battery Level」ボタンと「Unknown battery level.」テキストが表示されます。

スクリーンショット 2019-11-05 15.02.42.png

ボタンタップで、端末のバッテリー残量を取得。テキスト反映されます。
スクリーンショット 2019-11-05 15.02.52.png

サクッと呼べましたね。

Swift側のネイティブコード(iOS)

続いてSwift(iOS)のコードを書いていきます。

XcodeでAppDelegate.swiftを開く

Xcodeを起動し、対象プロジェクト内のiOSフォルダを開きます。Runnerフォルダ配下にあるAppDelegate.swiftを開きます。
スクリーンショット 2019-11-05 15.33.01.png

getBatteryLevelを実装

Androidと同じくMethodChannelの実装を定義します。

AppDelegate.swift
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

    let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
    let batteryChannel = FlutterMethodChannel(name: "samples.flutter.dev/battery",
                                              binaryMessenger: controller.binaryMessenger)
    batteryChannel.setMethodCallHandler({
      (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
      // Note: this method is invoked on the UI thread.
      // Handle battery messages.
    })

    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

バッテリーレベルの取得メソッドreceiveBatteryLevelを追加。

AppDelegate.swift
private func receiveBatteryLevel(result: FlutterResult) {
  let device = UIDevice.current
  device.isBatteryMonitoringEnabled = true
  if device.batteryState == UIDevice.BatteryState.unknown {
    result(FlutterError(code: "UNAVAILABLE",
                        message: "Battery info unavailable",
                        details: nil))
  } else {
    result(Int(device.batteryLevel * 100))
  }
}

setMethodCallHandlerreceiveBatteryLevelを呼び出します。

AppDelegate.swift
batteryChannel.setMethodCallHandler({
  [weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in
  // Note: this method is invoked on the UI thread.
  guard call.method == "getBatteryLevel" else {
    result(FlutterMethodNotImplemented)
    return
  }
  self?.receiveBatteryLevel(result: result)
})

iOS Simulatorを起動すると、Android同様にボタンとテキストが表示されます。
スクリーンショット 2019-11-05 15.44.14.png

ボタンをタップするとMethodChannel経由でreceiveBatteryLevel()が呼び出されテキストが更新されます。
スクリーンショット 2019-11-05 15.45.45.png
残念ながらiOS Simulator上ではバッテリーAPIがサポートされていないようですが「Battery info unavailable」というSwift側で書いたコードが動いていることがわかりますね。

ネイテイブからFlutterへのコールバック通知について

ネイテイブからFlutter側へ状態変化等のコールバック通知を行うには、EventChennelという仕組みを利用するそうです。

EventChannel class - services library - Dart API

こちらはまだ試していないので、別途触ってみたいと思います。

54
43
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
54
43