Flutter入門進階之旅(二十)Flutter外掛開發

謝棟發表於2020-01-20

前言

鑑於現階段Flutter技術棧還不是太成熟,在使用Flutter做移動端開發時我們經常需要藉助Native平臺的力量來補充Flutter在這方面的缺陷,前面兩章我們通過學習把Flutter專案打包成AAR整合到原生平Flutter與原生平臺互動掌握了Flutter與原生平臺互動的兩種方式,但是有些場景下,我們希望我們Flutter跟原生互動的程式碼可以一次開發,多處使用,類似於庫檔案一樣,可以給其他專案或者其他開發著使用,這就是我們本篇文章要介紹的主題Flutter外掛開發以及外掛如何引用到專案中

課程目標

  • 學會如何新建Flutter外掛,並瞭解外掛專案結構
  • 掌握如何把外掛引入到現有專案中

1.新建Flutter外掛專案

新建Flutter外掛專案跟新建Flutter專案的步驟一樣,無非是在新建專案的時候選擇的工程型別略有不同。

1.1新建專案

在這裡插入圖片描述

1.2 選擇Flutter Plugin

之後跟正常新建Flutter Applicition的操作一樣,正常給專案起名字,選擇工程路徑等一些列的初始化配置一直next到外掛專案初始化完畢。之後的操作讀者一看便知,也沒有什麼需要特別注意的地方,我就不逐個貼圖了。

在這裡插入圖片描述

1.3 外掛專案結構

從下面外掛工程專案結構圖中我們可以看出,Flutter外掛專案跟普通的Flutter專案結構上幾乎一樣,但是多出了一個example目錄,讀者開啟example目錄後,會發現這個example目錄下面其實就是一個完整的Flutter專案,沒錯這個example就是為了方便我們在開發外掛方便我們除錯開發的功能是否正常可用,沒問題的話就可以釋出出去或者給其他專案正常使用了。

在這裡插入圖片描述

外掛開發其實用到的知識點就是通過利用我們上節課中講的Flutter跟原生平臺互動的方式來完成的,讓Flutter藉助Native的功能來完成某種操作,外掛化只不過是把呼叫平臺操作的程式碼模組化,便於後期其他專案或者別人引入,讓程式碼一次開發,多處使用,由於涉及到的知識點在上一篇文章中我們都已經講過了,所以這裡就不在細講外掛裡的功能程式碼實現邏輯了,下面我們來簡單分析一下這次課程中用到的用Flutter外掛工程。

先看效果圖:

在這裡插入圖片描述

在上圖的外掛工程中我們實現了,獲取系統版本號跟一個簡單的計算器的功能。下面看一下在外掛工程中具體配置。

在外掛工程的android端的業務實現邏輯:


class FlutterCalcPlugin : MethodCallHandler {
    companion object {
        @JvmStatic
        fun registerWith(registrar: Registrar) {
            val channel = MethodChannel(registrar.messenger(), "flutter_calc_plugin")
            channel.setMethodCallHandler(FlutterCalcPlugin())
        }
    }

    override fun onMethodCall(call: MethodCall, result: Result) {
        if (call.method == "getPlatformVersion") {
            result.success("Android ${android.os.Build.VERSION.RELEASE}")
        } else if (call.method == "getResult") {
            var a = call.argument<Int>("a")
            var b = call.argument<Int>("b")
            result.success((a!! + b!!).toString())
        } else {
            result.notImplemented()
        }
    }
}
複製程式碼

由於外掛開發完成之後是要在flutter端使用的,換句話說是要給dart檔案引用的,所以下面的dart檔案中定義的方法宣告才是我們開發的外掛對呼叫者提供的方法。如下我們定義了 getplatformVersion:獲取系統版本號 getResult(int a, int b):計算兩個數的和

而兩個方法又通過methodChannel與平臺互動,藉助native端來完成某些具體邏輯,然後把執行完成的結果返回給呼叫方。外掛定義完成並且成功匯入到我們的專案中之後,我們就可以在專案中匯入相關類以及方法引用,正常去使用我們自己開發的元件了。

外掛工程的flutter端程式碼:


class FlutterCalcPlugin {
  static const MethodChannel _channel =
      const MethodChannel('flutter_calc_plugin');

  static Future<String> getplatformVersion async {
    final String version = await _channel.invokeMethod('getPlatformVersion');
    return version;
  }

  /**
   *計算兩個數的和
   */
  static Future<String>  getResult(int a, int b) async {
    Map<String, dynamic> map = {"a": a, "b": b};
    String result = await _channel.invokeMethod("getResult", map);
    print(result+"----------aa--");
    return result;
  }
}

複製程式碼

這裡涉及到跟平臺互動的部分我沒有具體展開講解,因為在上一篇文章中我們已經講過相關的知識點了,如果讀者在這裡不太明白平臺互動相關的邏輯,建議先去讀一下上一篇文章Flutter入門進階之旅(十九)Flutter與原生平臺互動 上述外掛完整程式碼地址:https://github.com/xiedong11/flutter_calc_plugin.git

2.外掛引入到現有專案中

把我們開發完成的外掛專案匯入到現有專案中使用,我們可以通過github倉庫引入,或者本地引入,當然也可以把開發完成的外掛工程上傳到flutter的dart packages上然後通過版本號用pubspec.ymal檔案引入,上傳dart packages的配置相對麻煩,限於篇幅,這裡我就先只介紹前兩種方式,讀者如果對上傳dart packages感興趣的話可以私下裡找我交流或者我會在後續的部落格單獨整理出一篇博文來具體講解。

2.1 本地引入

在這裡插入圖片描述

如圖,我把外掛工程放在專案跟目錄下的plugin檔案下,外掛專案名我們自己可以自己隨便定義,我這裡把它定義成flutter_calc_plugin,那在我們要引入外掛的專案中yaml檔案裡我們通過外掛名,加路徑的方式把外掛匯入之後就可以正常使用外掛裡的功能了。

#本地外掛引入
  flutter_calc_plugin:
    path: plugin/flutter_calc_plugin
複製程式碼
2.2通過github倉庫地址引入

通過github倉庫地址引入相對簡單一些,就不用把外掛拷貝到本地了,只需要在工程的yaml檔案中正確配置外掛的地址就可以匯入了,兩種方式配置完成之後都別忘了執行flutter packages get讓工程依賴同步一下。

  #從github上引入外掛依賴
  flutter_calc_plugin:
    git:
     url:
      https://github.com/xiedong11/flutter_calc_plugin.git

複製程式碼

這裡順便說一下小細節吧,由於yaml檔案對縮排格式要求特別嚴格,讀者在配置外掛引用或者其他第三方的庫時,一定要注意縮排。

還有就是具體採用上述兩種外掛的哪一種方式,這個沒有固定的答案完全看你個人喜好跟外掛的需求吧,舉個例子,如果你的外掛開發出來幾乎不需要修改,那筆者建議通過github或者上傳到dart packages的方式引用,這樣不僅讓你的工程結構更新清晰而且專案也好管理,但是如果先階段你開發的外掛還不太成熟,或者經常需要改動的話,建議使用本地的方式匯入,這樣修改後除錯程式碼也方便,而且也省去了頻繁上傳外掛的版本到dart packages或者github上去。

還有一個場景就是,比如使用的外掛不是自己開發的,而是從github或者dart packages上找的別人開發好的,但恰恰他開發的外掛不能完全滿足你的業務需求,或者你需要在此外掛的基礎上重新定製UI或者補充邏輯,那這個時候你也可以把別人開發好的外掛下載到本地,然後通過本地的方式引入到你的專案中再去針對你的業務去修改這個外掛,直到修改到你滿意為止,然後再通過本地方式匯入到專案中。

在文章的最後,貼上一下,上述gif圖上示例的具體程式碼實現供讀者參考:

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

import 'package:flutter/services.dart';
import 'package:flutter_calc_plugin/flutter_calc_plugin.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String _platformVersion = 'Unknown';
  String addResult = '';
  TextEditingController _addNumber1Controller,_addNumber2Controller;

  @override
  void initState() {
    super.initState();
    _addNumber1Controller = TextEditingController();
    _addNumber2Controller = TextEditingController();
  }

  Future<void> getAddResult() async {
    int addNumber1= int.parse(_addNumber1Controller.value.text);
    int addNumber2=int.parse(_addNumber2Controller.value.text);

    String result = '';
    try {
      result = await FlutterCalcPlugin.getResult(addNumber2, addNumber1);
    } on PlatformException {
      result = '未知錯誤';
    }
    setState(() {
      addResult = result;
    });
  }

  Future<void> initPlatformState() async {
    String platformVersion;

    try {
      platformVersion = await FlutterCalcPlugin.platformVersion;
    } on PlatformException {
      platformVersion = 'Failed to get platform version.';
    }

    if (!mounted) return;

    setState(() {
      _platformVersion = platformVersion;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('外掛示例'),
        ),
        body: Center(
            child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            MaterialButton(
              color: Colors.amber,
              child: Text("獲取系統版本"),
              onPressed: () {
                initPlatformState();
              },
            ),
            Text('當前系統版本 : $_platformVersion\n'),
            SizedBox(height: 30),
            Text("加法計算器"),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,

              children: <Widget>[
                SizedBox(
                  width: 80,
                  child: TextField(
                    controller: _addNumber1Controller,
                    keyboardType: TextInputType.number,
                  ),
                ),
                Text("  +  ",style: TextStyle(fontSize: 26),),
                SizedBox(
                  width: 80,
                  child: TextField(
                    controller: _addNumber2Controller,
                    keyboardType: TextInputType.number,
                  ),
                ),
                Text("  = ",style: TextStyle(fontSize: 26),),
              ],
            ),
            SizedBox(height: 30),
            MaterialButton(
              color: Colors.amber,
              child: Text("結果等於"),
              onPressed: () {
                getAddResult();
              },
            ),
            Text(addResult),
          ],
        )),
      ),
    );
  }
}
複製程式碼

最後本章節以及專欄的所有完整程式碼如下,讀者如果還不太明白,可以下載程式碼自己跑一篇專案,慢慢的琢磨下具體的實現細節: 專欄程式碼倉庫:https://github.com/xiedong11/flutter_app.git

相關文章