[-Flutter外掛篇 1-] 從自定義外掛開始說起

張風捷特烈發表於2019-08-05

Flutter可以為你提供一個強大華麗簡潔高效的跨平臺UI介面,
但無論外表多麼絢麗美女,沒有內在也只是空殼,你會喜歡她嗎?(還用問,當然會)
使用外掛可以讓Flutter輕鬆與當前平臺進行聯絡,呼叫平臺中的方法。
這篇先不虛頭巴腦的介紹一堆MethodChannel的概念,先看怎麼用。本文你將瞭解:

[1].如何建立一個Flutter外掛的
[2].Flutter中如何和Android以及iOS互動(本文使用Kotlin和Swift)
[3].Flutter外掛的使用
複製程式碼

1.Flutter外掛建立與結構簡析

1.1:建立一個Flutter外掛

File-->new-->new Flutter Project...

[-Flutter外掛篇 1-] 從自定義外掛開始說起

  • 填寫資訊

[-Flutter外掛篇 1-] 從自定義外掛開始說起

  • 包名及語言選擇

[-Flutter外掛篇 1-] 從自定義外掛開始說起


1.2:Flutter外掛專案結構

寫程式碼的地方有三塊:

android下面寫Android原生程式碼,使用Java或Kotlin,如果用JNI還可能涉及C++
ios資料夾下面寫iOS原生程式碼,使用Object-c或Swift
lib資料夾下面寫Flutter程式碼,使用Dart語言
複製程式碼

也就是說一個外掛可能涉及到6種語言,哈哈,顫抖吧人類...

[-Flutter外掛篇 1-] 從自定義外掛開始說起


1.3:執行外掛示例

雖然複雜,但是簡單必有簡單的成本,複雜必有複雜的價值。

[-Flutter外掛篇 1-] 從自定義外掛開始說起

注意有個坑點:mac上需要裝cocoapods

---->[本機資訊]----
toly:~ mac$ ruby -v
ruby 2.3.7p456 (2018-03-28 revision 63024) [universal.x86_64-darwin18]
toly:~ mac$ gem -v
2.5.2.3
toly:~ mac$ gem sources -l 
*** CURRENT SOURCES ***
https://rubygems.org/

---->[替換ruby源]----
gem sources --add https://gems.ruby-china.com/ --remove https://rubygems.org/

---->[替換ruby源完成]----
toly:~ mac$ gem sources -l
*** CURRENT SOURCES ***
https://gems.ruby-china.com

---->[安裝cocoapods]----
toly:~ mac$ sudo gem install -n /usr/local/bin cocoapods
toly:~ mac$ pod setup
複製程式碼

2.第一個外掛程式碼分析

這裡建立一個ia_version的專案專門看看示例的外掛是如何完成的。

2.1:Flutter程式碼:

可以看到ia_version.dart中定義了一個類IaVersion,其中有一個MethodChannel型別靜態常量_channel,接受一個字串,在靜態方法platformVersion中使用非同步呼叫_channel的getPlatformVersion方法獲取版本進行返回。

---->[lib/ia_version.dart]----
class IaVersion {
  static const MethodChannel _channel =
      const MethodChannel('ia_version');

  static Future<String> get platformVersion async {
    final String version = await _channel.invokeMethod('getPlatformVersion');
    return version;
  }
}
複製程式碼

2.2:Android方Kotlin程式碼:
[1].定義IaVersionPlugin類繼承自MethodCallHandler。
[2].建立靜態方法registerWith,傳入一個Registrar型別變數registrar。  
[3].通過registrar的messenger和識別符號建立MethodChannel物件,  
[4].將IaVersionPlugin物件設定給MethodChannel進行回撥處理。  
[5].覆寫了onMethodCall方法,回撥MethodCall和Result物件,
在方法體中根據方法名`getPlatformVersion`來用result物件執行方法傳入Android版本資訊。

---->[com.toly1994.ia_version.IaVersionPlugin]----
class IaVersionPlugin: MethodCallHandler {
  companion object {
    @JvmStatic
    fun registerWith(registrar: Registrar) {
      val channel = MethodChannel(registrar.messenger(), "ia_version")
      channel.setMethodCallHandler(IaVersionPlugin())
    }
  }

  override fun onMethodCall(call: MethodCall, result: Result) {
    if (call.method == "getPlatformVersion") {
      result.success("Android ${android.os.Build.VERSION.RELEASE}")
    } else {
      result.notImplemented()
    }
  }
}
複製程式碼

2.3:iOS方Swift程式碼
[1].定義SwiftIaVersionPlugin類繼承自NSObject, FlutterPlugin  
[2].建立靜態方法register,傳入一個FlutterPluginRegistrar型別變數registrar。
[3].通過registrar的messenger和識別符號建立FlutterMethodChannel物件,  
[4].將SwiftIaVersionPlugin物件設定給MethodChannel進行回撥處理。  
[5].handle方法,回撥FlutterMethodCall和FlutterResult物件,
用result物件執行方法傳入iOS版本資訊。

---->[ios/Classes/SwiftIaVersionPlugin.swift]----
public class SwiftIaVersionPlugin: NSObject, FlutterPlugin {
  public static func register(with registrar: FlutterPluginRegistrar) {
    let channel = FlutterMethodChannel(name: "ia_version", binaryMessenger: registrar.messenger())
    let instance = SwiftIaVersionPlugin()
    registrar.addMethodCallDelegate(instance, channel: channel)
  }

  public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
    result("iOS " + UIDevice.current.systemVersion)
  }
}
複製程式碼

還有兩個檔案使用OC寫的,關於OC我不是太懂,下面是Flutter群裡一位朋友的介紹

[-Flutter外掛篇 1-] 從自定義外掛開始說起

---->[ios/Classes/IaVersionPlugin.h]----
@interface IaVersionPlugin : NSObject<FlutterPlugin>
@end

---->[ios/Classes/IaVersionPlugin.m]----
@implementation IaVersionPlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
  [SwiftIaVersionPlugin registerWithRegistrar:registrar];
}
@end
複製程式碼

2.4:使用外掛

使用的時候就很方便了,呼叫一下就ok。

import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:ia_version/ia_version.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
  String _platformVersion = 'Unknown';
  @override
  void initState() {
    super.initState();
    initPlatformState();
  }
  Future<void> initPlatformState() async {//非同步初始化平臺狀態
    String platformVersion;
    try {//捕捉PlatformException.
      platformVersion = await IaVersion.platformVersion;//通過外掛獲取平臺版本
    } on PlatformException {
      platformVersion = 'Failed to get platform version.';
    }
    // 如果在非同步平臺訊息執行期間widget從樹中刪除,
    // 我們希望丟棄響應,而不是呼叫setState來更新不存在的外觀。
    if (!mounted) return;
    setState(() {//更新狀態
      _platformVersion = platformVersion;
    });
  }
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: Center(
          child: Text('Running on: $_platformVersion\n'),
        ),
      ),
    );
  }
}
複製程式碼

3.獲取快取資料夾

相信大家都用過path_provider,感覺很方便就可以在Flutter中獲取檔案路徑
下面我們看一下如何讓一個外掛獲取快取資料夾,如果前面看明白了,應該so easy

[-Flutter外掛篇 1-] 從自定義外掛開始說起


3.1:dart外掛檔案
import 'dart:async';
import 'dart:io';

import 'package:flutter/services.dart';

class IaPath {
  static const MethodChannel _channel =//外掛的渠道標識名
      const MethodChannel('com.toly1994.ia_path');

  Future<Directory> getTemporaryDirectory() async {
    final String path =
    await _channel.invokeMethod<String>('getTemporaryDirectory');
    if (path == null) {
      return null;
    }
    return Directory(path);
  }
}
複製程式碼

3.2:Android檔案
class IaPathPlugin(registrar: Registrar) : MethodCallHandler {
  private var mRegistrar: Registrar? = registrar
  
  companion object {
    @JvmStatic
    fun registerWith(registrar: Registrar) {
      val channel = MethodChannel(registrar.messenger(), "com.toly1994.ia_path")
      val instance = IaPathPlugin(registrar)
      channel.setMethodCallHandler(instance)
    }
  }
  
  override fun onMethodCall(call: MethodCall, result: Result) {
    when(call.method){
      "getTemporaryDirectory"->{
        result.success(geTemporaryDirectory())
      }
    }
  }
  private fun geTemporaryDirectory(): String {
   return mRegistrar!!.context().cacheDir.path;
  }
}
複製程式碼

3.3:iOS的檔案
public class SwiftIaPathPlugin: NSObject, FlutterPlugin {
  public static func register(with registrar: FlutterPluginRegistrar) {
    let channel = FlutterMethodChannel(name: "com.toly1994.ia_path", binaryMessenger: registrar.messenger())
    let instance = SwiftIaPathPlugin()
    registrar.addMethodCallDelegate(instance, channel: channel)
  }

  public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
    switch call.method {
       case "getTemporaryDirectory"  :
          result(NSTemporaryDirectory())
       default :
          result("UnKnown")
    }
  }
}
複製程式碼

3.4:使用
import 'package:flutter/material.dart';
import 'package:ia_path/ia_path.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
  String _str = 'CacheDir:\n';
  @override
  void initState() {
    super.initState();
    IaPath().getTemporaryDirectory().then((dir){
      setState(() {
        _str+=dir.path;
      });
    });
  }
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: Container(
          child: Text('Running on: $_str\n'),
        ),
      ),
    );
  }
}
複製程式碼

4.其他工程引用外掛

4.1:釋出到公網

有道牆隔著,發不發得了就看你自己了。

---->[釋出]----
flutter packages pub publish

---->[使用]----
dependencies:
  ia_path: ^0.0.1
複製程式碼

4.2:本地使用

經測試,使用無誤

dependencies:
  ia_path:
    path: /Volumes/coder/Project/Flutter/plugins/ia_path
複製程式碼

當然你也可以直接在本專案中呼叫Android和iOS方法,就像外掛裡做的那樣。
本文講了一下外掛的自定義和在兩個平臺上的程式碼處理,
下一篇將詳細講述MethodChannel,讓你在Flutter中無後顧之憂。


結語

本文到此接近尾聲了,如果想快速嚐鮮Flutter,《Flutter七日》會是你的必備佳品;如果想細細探究它,那就跟隨我的腳步,完成一次Flutter之旅。
另外本人有一個Flutter微信交流群,歡迎小夥伴加入,共同探討Flutter的問題,本人微訊號:zdl1994328,期待與你的交流與切磋。

相關文章