iT邦幫忙

2021 iThome 鐵人賽

DAY 20
0
Mobile Development

Flutter - 複製貼上到開發套件之旅系列 第 20

【第二十天 - Flutter 與 Android、iOS 溝通方式 - 官方範例講解】

  • 分享至 

  • xImage
  •  

前言

這篇,想要介紹一下 Flutter 如何把某些功能打包給原生的 Android、Ios 寫。將會介紹官方的 Sample。
https://flutter.dev/docs/development/platform-integration/platform-channels?tab=android-channel-java-tab 文件
這篇將會透過取得電池電量為範例。

架構

https://flutter.dev/assets/images/docs/PlatformChannels.png
圖片擷取來自官網 => https://flutter.dev/assets/images/docs/PlatformChannels.png

可以看到 Flutter 和 IOS、Android,都是透過 MethodChannel 來交流、交換資料。MethodChannel 是一個溝通的橋樑。invokeMethod 則是可以讓 android、ios 原生的程式碼知道 flutter 要呼叫的方法是哪一個。

Flutter

可以看到這邊 Flutter 在 state 裡面宣告了一個 MethodChannel。

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

  // Get battery level.
}

可以看到,這邊我們定義了一個 result 用來接收電量資訊,我們透過 platform(MethodChannel),去取得 invokeMethod == getBatteryLevel 的 android 原生方法。

  // Get battery level.
  String _batteryLevel = 'Unknown 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;
    });
  }

UI

  @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),
        ],
      ),
    ),
  );
}

Android

這邊建議撰寫時,另外開一個 Android Sutdio Project,這樣 Android Studio 會比較好編輯。
這邊我們可以宣告一個 Channel 會等於 Flutter 的 Channel。
接下來我們在初始化 MethodChannel(這邊要和 flutter 寫的一樣),並在 configureFlutterEngine 裡面設置一個 MethodCallHandler 的 interface。

這邊,如果你有用另外開一個 Android 的 project,就可以很快速 import 了。

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

public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "samples.flutter.dev/battery";

@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
super.configureFlutterEngine(flutterEngine);
  new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
      .setMethodCallHandler(
        (call, result) -> {
          // Note: this method is invoked on the main thread.
          // TODO
        }
      );
}
}

寫一個讀取店員資運的方法

  private int getBatteryLevel() {
    int batteryLevel = -1;
    if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
      BatteryManager batteryManager = (BatteryManager) getSystemService(BATTERY_SERVICE);
      batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
    } else {
      Intent intent = new ContextWrapper(getApplicationContext()).
          registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
      batteryLevel = (intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100) /
          intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
    }

    return batteryLevel;
  }

接著我們可以把程式碼寫在 (call, result) ->{} 裡面,這個 call back 會有一個 call 的參數,
這個 call 的參數對應的就是 Flutter 裡面的 MethodChannel.invokeMethod('call 的參數')。
result 則會有一個 success 和 error。success 的話就會回傳給 flutter 正確資料。如果是 error 的話就會回傳給 flutter PlatformException。

          (call, result) -> {
          // Note: this method is invoked on the main thread.
          if (call.method.equals("getBatteryLevel")) {
            int batteryLevel = getBatteryLevel();

            if (batteryLevel != -1) {
              result.success(batteryLevel);
            } else {
              result.error("UNAVAILABLE", "Battery level not available.", null);
            }
          } else {
            result.notImplemented();
          }
        }

IOS

Ios 則是透過 FlutterViewController 和 FlutterMethodChannel,並一樣會有一個 setMethodCallHandler 的 interface,一樣會有 call 和 result 的參數。

附上完整程式碼

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

}


上一篇
【第十九天 - Flutter Firebase Dynamic Links】
下一篇
【第二一天 - Flutter Blue 藍芽文件說明】
系列文
Flutter - 複製貼上到開發套件之旅30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言