Flutter學習指南:封裝 API 外掛

玉剛說發表於2018-12-13

Flutter學習指南
6.檔案儲存和網路
5.互動、手勢和動畫
4.UI佈局和控制元件
3.熟悉Dart語言
2.編寫第一個應用
1.開發環境搭建

本文是學習指南系列的第7篇文章,建議大家收藏起來,歡迎分享給他人。

本篇文章我們先一起學習 Flutter 外掛的使用,然後通過開發一個 toast 外掛來學習它的開發,最後釋出到 Pub 上。

外掛的使用

Flutter 的庫是以 package 的方式來管理。Package 分為兩種,Dart package(也叫 library package) 和 plugin package。當我們說 Fluter 包的時候,指的其實也是 Dart 包,它只能使用 Dart 和 Flutter 提供的 API;而當我們說 Flutter 外掛時指的是後者,也就是 plugin package。Flutter 外掛通常會包含平臺特定的程式碼。對包的使用者來說,兩者沒有區別。

新增依賴

為了使用一個庫,我們首先在 pubspec.yaml 裡宣告一個依賴:

dependencies:
  shared_preferences: ^0.4.2
複製程式碼

^0.4.2 表示與 0.4.2 相容的版本。我們也可以指定依賴庫的為特定的版本:

  • any:任意版本
  • 1.2.3:特定的版本
  • <1.2.3:小於 1.2.3 的版本,此外還有 <=、>、>= 可以使用
  • '>=1.2.3 <2.0.0':指定一個範圍

接下來,在專案的根目錄執行 flutter packages get。如果你使用 Android Studio 進行開發,也可以直接在 pubspec.yaml 的編輯頁面上面點選 Packages get 按鈕。

上面例子的是釋出在 pub.dartlang.org 上的庫,除此之外,我們也可以使用其他的源:

dependencies:
  transmogrify:
    hosted:
      name: transmogrify
      url: http://your-package-server.com
    version: ^1.4.0

  kittens:
    git:
      url: git://github.com/munificent/cats.git
      ref: some-branch  # 可選的

  kittens:
    git:
      url: git://github.com/munificent/cats.git
      path: path/to/kittens  # 指定路徑

    # 甚至可以指定一個本地路徑
    transmogrify:
      path: /Users/me/transmogrify
複製程式碼

如果你看過 Flutter 的 pubspec,應該會注意到 flutter 是這樣宣告的:

dependencies:
  flutter:
    sdk: flutter
複製程式碼

sdk 用於匯入隨 Flutter 一起釋出的包,目前只有 flutter。

使用

匯入相關的包後,我們就可以使用它的 API 了:

import 'package:shared_preferences/shared_preferences.dart';

void foo() async {
  var prefs = await SharedPreferences.getInstance();
  var used = prefs.getBool('used');
  if (!used) {
    prefs.setBool('used'true);
  }
}
複製程式碼

這種匯入方式的問題在於,他把庫裡所有的符號到匯入到了全域性的名稱空間裡面(比方說,在上面的例子裡,我們可以直接使用 SharedPreferences)。有時為了防止名稱空間的汙染,我們可以使用 as 給匯入的庫一個名字(當然,對 SharedPreferences 其實沒有必要使用限定名就是了):

void foo() async {
  var prefs = await sp.SharedPreferences.getInstance();
  var used = prefs.getBool('used');
  if (!used) {
    prefs.setBool('used'true);
  }
}
複製程式碼

瞭解了 Flutter 包的使用後,下面我們自己來開發一個 flutter 外掛。

開發一個外掛

學習 Flutter 的過程中,不知道是你是否注意到 Flutter 並沒有提供一個 Toast API。為了彌補這個遺憾,在這一節裡我們就來開發一個外掛,讓它支援 Toast。

在開始開發前,我們先來了解一下 Flutter 如何跟平臺相關的程式碼進行通訊。

MethodChannel

Flutter 跟平臺相關程式碼可以通過 MethodChannel 進行通訊。客戶端通過 MethodChannel 將方法呼叫和引數發生給服務端,服務端也通過 MethodChannel 接收相關的資料。

PlatformChannels
PlatformChannels

需要注意的是,上圖中的箭頭是雙向的。也就是說,我們不僅可以從 Flutter 呼叫 Android/iOS 的程式碼,也可以從 Android/iOS 呼叫 Flutter。呼叫時相關的引數對應如下:

Dart Android iOS
null null nil (NSNull when nested)
bool java.lang.Boolean NSNumber numberWithBool:
int java.lang.Integer NSNumber numberWithInt:
int, if 32 bits not enough java.lang.Long NSNumber numberWithLong:
double java.lang.Double NSNumber numberWithDouble:
String java.lang.String NSString
Uint8List byte[] FlutterStandardTypedData typedDataWithBytes:
Int32List int[] FlutterStandardTypedData typedDataWithInt32:
Int64List long[] FlutterStandardTypedData typedDataWithInt64:
Float64List double[] FlutterStandardTypedData typedDataWithFloat64:
List java.util.ArrayList NSArray
Map java.util.HashMap NSDictionary

建立專案

這裡假設讀者使用 Android Studio 開發。

  1. 在選單上選擇 File -> New -> New Flutter Project
  2. 在彈出的皮膚裡選擇 Flutter Plugin,點選 next
  3. Project name 我們填入 flutter_toast2018,其他資訊讀者根據自身需要填寫

之所以叫 flutter_toast2018 是因為 Pub 上已經有一個 flutter_toast,所以加上 2018 防止名字衝突。

生成的專案有 4 個主要的目錄:

  • android:外掛原生程式碼的 Android 端實現
  • ios:iOS 端的實現
  • lib:Dart 程式碼。外掛的客戶將會使用這裡實現的介面
  • example:外掛的使用示例

外掛開發

Android 端程式碼實現

其實在上一步我們生成專案的時候,專案裡就已經包含了一個實現了 platformVersion 的 Flutter 外掛 demo,有興趣的讀者可以看看學習一下。下面,我們來開發自己的 Toast 外掛(注意,我們的實現只支援 Android)。

首先我們來了解一下介面 MethodCallHandler:

public interface MethodCallHandler {
  void onMethodCall(MethodCall call, Result result);
}
複製程式碼

這個介面用於處理 Flutter 的本地方法呼叫請求。也就是說,我們需要實現這個介面,當 Flutter 呼叫我們的時候,彈出一個 toast。

實現這個介面的是 FlutterToast2018Plugin(位於 android 目錄下):

public class FlutterToast2018Plugin implements MethodCallHandler {
  public static void registerWith(Registrar registrar) {
    // "example.com/flutter_toast2018" 是我們 method channel 的名字,Dart 程式碼裡還需要用到它。
    // 為了防止命名衝突,可以在它的前面加上域名
    final channel = new MethodChannel(registrar.messenger(), "example.com/flutter_toast2018");
    channel.setMethodCallHandler(new FlutterToast2018Plugin());
  }

  @Override
  public void onMethodCall(MethodCall call, Result result) {
    // TODO
  }
}
複製程式碼

為了彈出 Toast,我們給 FlutterToast2018Plugin 的建構函式新增一個 Context 引數:

public class FlutterToast2018Plugin implements MethodCallHandler {
  private final Context mContext;

  public FlutterToast2018Plugin(Context context) {
    mContext = context;
  }

  // 註冊 MethodCallHandler
  public static void registerWith(Registrar registrar) {
    final channel = new MethodChannel(registrar.messenger(), "example.com/flutter_toast2018");
    // context 可以從 Registrar 拿到
    channel.setMethodCallHandler(new FlutterToast2018Plugin(registrar.context());
  }

  // ...
}
複製程式碼

現在,實現 onMethodCall 方法:

public class FlutterToast2018Plugin implements MethodCallHandler {
  // ...

  @Override
  public void onMethodCall(MethodCall call, Result result) {
    // call.method 是方法名,這裡我們就叫它 toast
    if (call.method.equals("toast")) {
      // 呼叫原生程式碼的時候,只能傳遞一個引數。為了傳遞多個,可以把引數放在一個 map 裡面。
      // call.arguemnt() 方法支援 Map 和 JSONObject
      String content = call.argument("content");
      String duration = call.argument("duration");
      Toast.makeText(mContext, content,
                     "short".equals(duration) ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG)
              .show();
      // 執行成功
      result.success(true);
    } else {
      result.notImplemented();
    }
  }
}
複製程式碼

Flutter 端

Flutter 端需要做的,就是生成一個 MethodChannel,然後通過這個 MethodChannel 呼叫 toast 方法:

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

enum ToastDuration {
  short, long
}

class FlutterToast {
  // 這裡的名字要跟 Java 端的對應
  static const MethodChannel _channel =
      const MethodChannel('example.com/flutter_toast2018');

  static Future<bool> toast(String msg, ToastDuration duration) async {
    var argument = {
      'content': msg,
      'duration': duration.toString()
    };
    // 本地方法是一個非同步呼叫。'toast' 對應我們在前面 Java 程式碼的 onMethodCall
    // 方法裡面處理的方法名
    var success = await _channel.invokeMethod('toast', argument);
    return success;
  }
}
複製程式碼

使用外掛

在這一節我們修改工程裡 example 目錄下的示例,用它來演示外掛的使用:

import 'package:flutter/material.dart';

// 首先匯入我們的包
import 'package:flutter_toast2018/flutter_toast2018.dart';

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

class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new Scaffold(
        appBar: new AppBar(
          title: const Text('Plugin example app'),
        ),
        body: new Center(
          child: RaisedButton(
            child: Text('toast'),
            // 外掛的使用跟其他庫沒有什麼區別,直接呼叫即可
            onPressed: () => FlutterToast2018.toast(
              'Toast from Flutter', ToastDuration.short
            ),
          ),
        ),
      ),
    );
  }
}
複製程式碼

釋出外掛

前面我們說過,pubspec 支援通過本地路徑和 Git 匯入依賴,但為了更好的管理版本依賴,還是推薦釋出外掛到 pub.dartlang.org/。在這一節,我們就把前面開發的 toast 外掛釋出到 Pub 上。

需要注意的是,由於某些眾所周知的原因,pub.dartlang.org 需要一把梯子才能上去。雖然我們也可以通過 flutter-io.cn 來發布,但上傳的時候需要登入 Google 賬號,梯子還是少不了的。

檢查配置

首先是 pubspec.yaml。對 Flutter 外掛來說,pubspec 裡除了外掛的依賴,還包含一些元資訊,讀者可以根據需要,把這些補上:

name: flutter_toast2018
description: A new Flutter plugin for Android Toast.
version: 0.0.1
author: Jekton <ljtong64@gmail.com>
homepage: https://jekton.github.io/
複製程式碼

另外,釋出到 Pub 上的包需要包含一個 LICENSE,關於 LICENSE 檔案,最簡單的方法就是在 GitHub 建立倉庫的時候選中一個。

檢查外掛

現在,我們在工程的根目錄執行以下命令,檢測一下外掛有沒有什麼問題:

flutter packages pub publish --dry-run
複製程式碼

如果一切正常,將會輸出:

...

Package has 0 warnings.
複製程式碼

釋出外掛

釋出外掛和上一步一樣,只是少了 --dry-run 引數:

flutter packages pub publish
複製程式碼

如果是第一次釋出,會提示驗證 Google 賬號。授權後便可以繼續上傳,如果成功的話,會提示“Successful uploaded package”:

Looks great! Are you ready to upload your package (y/n)? y
Pub needs your authorization to upload packages on your behalf.
In a web browser, go to https://accounts
.google.com/o/oauth2/auth?access_type=offline&approval_prompt=force&response_type=code&client_id=xxxxxxxxxxxxxx-8grd2eg9tj9f38os6f1urbcvsq399u8n.apps.googleusercontent.com&redirect_uri=http%3A%2F%2Flocalhost%3A52589&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email
Then click "Allow access".

Waiting for your authorization...
Successfully authorized.
Uploading...
Successful uploaded package.
複製程式碼

前面我們釋出的包可以在 pub.dartlang.org/packages/fl…pub.flutter-io.cn/packages/fl… 找到。

相關的程式碼則是放到了 GitHub 上:

git clone https://github.com/Jekton/flutter_toast2018.git
複製程式碼

最後再提一提我們沒有講到的 Flutter package。為了開發一個 Flutter 包,我們在建立專案的時候可以選擇 Flutter package。它和 Flutter 外掛唯一的區別是Flutter package 不能包含平臺特定的程式碼(只能使用 Dart 和 Flutter API)。除此之外,開發、釋出和使用都跟 Flutter 外掛沒有什麼區別。


程式設計·思維·職場
歡迎掃碼關注

Flutter學習指南:封裝 API 外掛

相關文章