Tourismプロジェクトのブログ

活動の様子やメンバーの興味があることを書いていきます!

FlutterでAndroid/iOSの機能を使う

Flutterの課題

blog.hatena.ne.jp

前回の記事でFlutterの特徴は、クロスプラットフォームが最大の特徴と伝えましたが、クロスプラットフォームゆえ様々な課題を抱えています。

  1. プラットフォーム(Android/iOS)の機能を最大限活かすことができない
  2. 独自のUIでレンダリング(描画)するため、プラットフォーム毎にUIを変更することができない
  3. Dartで開発するため、学習コストが必要

今回はMethodChannelを活用し、ネイティブの機能を実装する方法をご紹介します。

MethodChannel

MethodChannelとは、Dartからネイティブ(Java,Kotlin,Objective-C,Swift)の機能を呼び出すもしくは、プラットフォームからDartの機能を呼び出すことができる方法です。

iPhoneのバッテリー状況を取得してみる

SwiftのUIDevice classDartで呼び出し、Flutterで作成したアプリにバッテリー状況を表示するデモアプリをご紹介します。

Flutter(Dart)のプログラム

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/cupertino.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  static const platform = const MethodChannel('samples.flutter.dev/battery');
  String _batteryLevel = 'Battery Level';

  Future<void> _getBatteryLevel() async {
    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: [
            ElevatedButton(
              child: Text('Get Battery Level'),
              onPressed: _getBatteryLevel,
            ),
            Text(_batteryLevel),
          ],
        ),
      ),
    );
  }
}

Swiftのプログラム

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 batteryChannel = FlutterMethodChannel(name: "samples.flutter.dev/battery",
                                               binaryMessenger: controller.binaryMessenger)
                                               
      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)
        })
        
        GeneratedPluginRegistrant.register(with: self) 

     return super.application(application, didFinishLaunchingWithOptions: launchOptions) 
  } 
  
  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))
    }
  }
}

実行結果

f:id:kit_tourism:20210828100033j:plain f:id:kit_tourism:20210828095954j:plain

まとめ

MethodChannelを活用することにより、プラットフォームの機能を活かすことができ、効率よく開発することができます。

執筆者:RyoNishi(@nishiryo_)