【Flutter】Flutter插件開發(fā)之創(chuàng)建iOS端插件

創(chuàng)建Flutter插件工程

Android Studio里點(diǎn)擊Flie - New - New Flutter Project,在左側(cè)里選中Flutter,然后點(diǎn)擊Next

創(chuàng)建工程.png

  • Project Name里輸入項(xiàng)目名,只能是小寫英文
  • Project type里選擇Plugin
  • Organization里寫包名,.Project Name會拼在包名的最后面成為包名的一部分

也可以使用命令行flutter create --org com.example --template=plugin plugin_name來創(chuàng)建插件,其中com.example就是Organizationplugin_name就是Project Name

點(diǎn)擊Finish后就成功創(chuàng)建一個(gè)插件工程了。
創(chuàng)建成功后可能默認(rèn)打開的是Android工程,點(diǎn)擊切換為Project

默認(rèn)工程.png

切換工程.png

切換后可以看到很多文件夾,我們需要關(guān)注的主要有以下4個(gè):

  • android目錄是用來開發(fā)Android端的插件功能
  • ios目錄是用來開發(fā)iOS端的插件功能
  • lib是實(shí)現(xiàn)Flutter插件接口的代碼
  • example目錄是測試項(xiàng)目,用來測試開發(fā)出來的插件的

打開iOS工程

ios目錄只是一些零散的文件,是沒有工程的,所以我們怎么打開它來編寫呢???
我們注意到,在example里也有一個(gè)iOS工程,沒錯(cuò),這個(gè)才是真正的工程!!!
但是我們打開發(fā)現(xiàn)會報(bào)錯(cuò),也沒有插件的文件,那怎么辦呢?

Xcode虛假的工程.png

在打開工程之前,我們需要在Android Studio里的命令行執(zhí)行以下命令:

cd example
flutter run  

等執(zhí)行完成之后,我們就可以打開Runner.xcworkspace文件了,這個(gè)時(shí)候我們發(fā)現(xiàn),多了一個(gè)Pods工程。這個(gè)工程其實(shí)就是插件工程。
我們在Pods工程的Development Pods目錄下,找到Project Name的文件夾,一直展開,最后就看到插件的文件了,我們就是在這個(gè)文件下編寫代碼。

Xcode真正的工程.png

編寫插件代碼

本文采用的語言是Swift
找到Swift項(xiàng)目名Plugin.swift這個(gè)文件,該文件就是插件的實(shí)現(xiàn)文件。
register方法里,我們注冊了一個(gè)通道(已經(jīng)默認(rèn)注冊了),通道名默認(rèn)就是項(xiàng)目名,該名字在通信里必須是唯一的,可以修改,一旦修改,需要把dartandroid里的名字也一并修改。
handle方法里,實(shí)現(xiàn)Flutter調(diào)用原生的API,其中call.method就是方法名,call.arguments就是Flutter傳遞過來的參數(shù)。使用result(返回值)可以把結(jié)果返回給Flutter
當(dāng)找不到方法名時(shí),可以返回FlutterMethodNotImplementedFlutter表示該方法還沒實(shí)現(xiàn),以此來做版本兼容。
具體實(shí)現(xiàn)如下:

public class SwiftNakiriPlugin: NSObject, FlutterPlugin {
  public static func register(with registrar: FlutterPluginRegistrar) {
    let channel = FlutterMethodChannel(name: "nakiri", binaryMessenger: registrar.messenger())
    let instance = SwiftNakiriPlugin()
    registrar.addMethodCallDelegate(instance, channel: channel)
  }

  public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
      if call.method == "stateString" { // 獲取網(wǎng)絡(luò)狀態(tài)的實(shí)現(xiàn)
          result(ZTNetworkStateManager.shared.stateString)
      } else if call.method == "bonusPoints" { // 使用參數(shù)的實(shí)現(xiàn)
          let array = call.arguments as! Array<Int>
          result(array[0] + array[1])
      } else if call.method == "getPlatformVersion" { // 默認(rèn)的實(shí)現(xiàn)
          result("iOS " + UIDevice.current.systemVersion)
      } else {
          // 找不到方法
          result(FlutterMethodNotImplemented)
      }
  }
}

Objective-C的插件文件名是項(xiàng)目名Plugin.m,注冊方法是registerWithRegistrar,實(shí)現(xiàn)插件內(nèi)容的方法是handleMethodCall

使用第三方庫

寫插件不可避免的會用到第三方庫,在使用第三方庫的時(shí)候,會遇到3種情況:

  • 僅原生端使用第三方庫
  • Flutter端使用第三方庫
  • 都使用同一個(gè)第三方庫

不同的情況有不同的處理方式。

僅原生端使用第三方庫

當(dāng)僅原生端需要依賴某些第三方庫時(shí),可以在項(xiàng)目名.podspec文件里加上s.dependency '第三方庫名',如:

s.dependency 'Alamofire'

然后打開命令行,跳轉(zhuǎn)到Runner.xcworkspace所在的目錄,然后pod install即可。

僅Flutter端使用第三方庫

當(dāng)僅Flutter端需要依賴某些第三方庫時(shí),可以在pubspec.yaml文件里的dependencies部分,如:

dependencies:
  flutter:
    sdk: flutter

  url_launcher: ^6.0.16

之后在Android Studio里執(zhí)行Pub get就行了。

都使用同一個(gè)第三方庫

假設(shè)Flutter里需要用到url_launcher,然后原生里也需要用到,那我們就得在Flutterpubspec.yaml文件里的dependencies部分添加依賴包,同時(shí)也要在iOS端的項(xiàng)目名.podspec文件里加上s.dependency 'url_launcher'

Flutter端實(shí)現(xiàn)

剛才我們已經(jīng)在插件里增加了一個(gè)名字叫stateString的方法,但是Flutter端還沒實(shí)現(xiàn),我們現(xiàn)在去把它實(shí)現(xiàn)。
找到lib文件夾下的項(xiàng)目名.dart文件,里面就有一個(gè)類,類名就是項(xiàng)目名,我們增加一個(gè)方法用來調(diào)用iOS端的stateString方法,方法名不需要和iOS端的保持一致,主要是通道里調(diào)用iOS端的方法名就行了,代碼如下:

class Nakiri {
  static const MethodChannel _channel = MethodChannel('nakiri'); /// 通道名,需和iOS、android端保持一致

  /// 默認(rèn)實(shí)現(xiàn)
  static Future<String?> get platformVersion async {
    final String? version = await _channel.invokeMethod('getPlatformVersion');
    return version;
  }

  /// 實(shí)現(xiàn)iOS端新增的方法
  static Future<String> stateString() async {
    final String state = await _channel.invokeMethod('stateString');
    return state;
  }

  /// 實(shí)現(xiàn)iOS端新增的方法
  static Future<int> add() async {
    final int result = await _channel.invokeMethod('bonusPoints', [5, 8]); /// 接收一個(gè)數(shù)組或者字典作為參數(shù)傳遞給原生端
    return result;
  }
}

Flutter端測試的實(shí)現(xiàn)

example目錄里的lib目錄,里面有一個(gè)main.dart文件,該文件就是測試使用的文件,我們在它本來的實(shí)現(xiàn)上修改一下,代碼如下:

class _MyAppState extends State<MyApp> {
  String _platformVersion = 'Unknown';
  String _stateString = 'Unknown';
  int _add = -2;

  @override
  void initState() {
    super.initState();
    initPlatformState();
  }

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

    try {
      platformVersion =
          await Nakiri.platformVersion ?? 'Unknown platform version';
      stateString =
          await Nakiri.stateString();
      add =
      await Nakiri.add();
    } on PlatformException {
      platformVersion = 'Failed to get platform version.';
      stateString = 'Failed to get stateString';
      add = -1;
    }

    if (!mounted) return;

    setState(() {
      _platformVersion = platformVersion;
      _stateString = stateString;
      _add = add;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Center(
              child: Text('Running on: $_platformVersion\n'),
            ),
            Center(
              child: Text('Network is: $_stateString\n'),
            ),
            Center(
              child: Text('Bonus points is: $_add\n'),
            ),
          ],
        ),
      ),
    );
  }
}

需要注意的是,Flutter和原生通信都是異步的,所以都需要使用awaitasync

Flutter測試.png

通信的數(shù)據(jù)類型

原生與Flutter互相通信時(shí)使用的數(shù)據(jù)類型是有限制的,以下是可用的數(shù)據(jù)類型:

Dart kotlin Java Objective-C Swift
null null null NSNull NSNull
bool Boolean java.lang.Boolean NSNumber numberWithBool: NSNumber(value: Bool)或者Bool
int 32位平臺 Int java.lang.Integer NSNumber numberWithInt: NSNumber(value: Int32)或者Int32
int Long java.lang.Long NSNumber numberWithLong: NSNumber(value: Int)或者Int
double Double java.lang.Double NSNumber numberWithDouble: NSNumber(value: Double)或者Double
String String java.lang.String NSString String或者NSString
Uint8List ByteArray byte[] FlutterStandardTypedData typedDataWithBytes: FlutterStandardTypedData(bytes: Data)
Int32List IntArray int[] FlutterStandardTypedData typedDataWithInt32: FlutterStandardTypedData(int32: Data)
Int64List LongArray long[] FlutterStandardTypedData typedDataWithInt64: FlutterStandardTypedData(int64: Data)
Float32List FloatArray float[] FlutterStandardTypedData typedDataWithFloat32: FlutterStandardTypedData(float32: Data)
Float64List DoubleArray double[] FlutterStandardTypedData typedDataWithFloat64: FlutterStandardTypedData(float64: Data)
List List java.util.ArrayList NSArray Array或者NSArray
Map HashMap java.util.HashMap NSDictionary Dictionary或者NSDictionary
  • 從表里可知,Swift的基礎(chǔ)類型可以用Objective-C的對象類型,集合類型可以兼容Objective-C的集合類型(不過這些都是Swift本身的特性
  • 在使用Swift時(shí),最好還是使用它本身的類型,如果使用Objective-C的類型,就無法判斷詳細(xì)類型,比如IntDouble,在使用Objective-C類型的時(shí)候,都是NSNumber

原生與Flutter通信

上面都是講Flutter怎么調(diào)原生的,那原生能不能主動去調(diào)Flutter呢?
我們先看看Flutter提供的3個(gè)通信類:

  • FlutterMethodChannel用于方法調(diào)用
  • FlutterBasicMessageChannel用于傳遞簡單數(shù)據(jù)
  • FlutterEventChannel用于監(jiān)聽數(shù)據(jù)流

每種Channel均有三個(gè)重要成員變量:

  • name:String類型,代表Channel的名字,也是其唯一標(biāo)識符
  • messager:BinaryMessenger類型,代表消息信使,是消息的發(fā)送與接收的工具
  • codec: MessageCodec類型或MethodCodec類型,代表消息的編解碼器

除了需要自定義name之外,其余變量用默認(rèn)值即可

前兩種Channel都提供了原生和Flutter互相通信的能力,而FlutterEventChannel不支持Flutter端發(fā)送數(shù)據(jù),由此可見,它們的應(yīng)用場合不太一樣,接下來我會講解它們每個(gè)的使用方法。

所有的Channel都需要名字,在一個(gè)項(xiàng)目中可能會有很多的Channel,每個(gè)Channel都應(yīng)該使用唯一的命名標(biāo)識,否則可能會被覆蓋。當(dāng)有消息從Flutter端發(fā)送到原生端時(shí),會根據(jù)其傳遞過來的名字找到該Channel對應(yīng)的Handler(消息處理器)
推薦的命名方式是組織名稱加插件的名稱,例如:com.nakiri.ayame/native_image_view,如果一個(gè)插件中包含了多個(gè)Channel可再根據(jù)功能模塊進(jìn)一步進(jìn)行區(qū)分

FlutterMethodChannel

通過前面我們已經(jīng)知道Flutter是通過FlutterMethodChannel去調(diào)原生方法的,接下來我們在原生端也使用FlutterMethodChannel去調(diào)Flutter的方法。
首先我們改造下原生端的插件類,在里面定義一個(gè)屬性,同時(shí)把register方法注冊的FlutterMethodChannel賦值給該屬性。

 private static var methodChannel: FlutterMethodChannel!
 methodChannel = FlutterMethodChannel(name: "nakiri", binaryMessenger: registrar.messenger())

然后我們就可以通過FlutterMethodChannelinvokeMethod方法調(diào)用Flutter的方法了。

// 調(diào)用Flutter方法
SwiftNakiriPlugin.methodChannel.invokeMethod("updateNumber", arguments: (number + 1)) { value in
    result(value) // 獲取Flutter方法的返回值,并返回給Flutter
}

然后在Flutter端插件代碼中增加監(jiān)聽方法:

static int number = 1;

/// 當(dāng)需要原生調(diào)用Flutter方法時(shí),請先調(diào)用下初始化方法來增加監(jiān)聽
static void init() {
    /// 設(shè)置原生調(diào)用Flutter時(shí)的回調(diào)
    _channel.setMethodCallHandler((call) async {
       switch(call.method) {
         case "updateNumber":
           return _updateNumber(call.arguments); /// 把結(jié)果返回給原生端

         default:
           break;
       }
    });
}
  
/// 實(shí)現(xiàn)原生調(diào)用Flutter方法
static int _updateNumber(int value) {
    return number + value;
}

FlutterBasicMessageChannel

相對于FlutterMethodChannel需要綁定代理,FlutterBasicMessageChannel在處理消息上更為方便靈活,并且能發(fā)送大內(nèi)存數(shù)據(jù)塊的數(shù)據(jù)。

Flutter端使用FlutterBasicMessageChannel發(fā)送數(shù)據(jù)給原生端

流程如下:

  • 原生端創(chuàng)建FlutterBasicMessageChannel
  • 原生端使用setMessageHandler方法,設(shè)置該ChannelMessageHandler回調(diào)
  • Flutter端創(chuàng)建該nameBasicMessageChannel
  • Flutter端使用該BasicMessageChannel通過send方法向原生端發(fā)送消息
  • 原生端的MessageHandler收到Flutter端發(fā)送的消息,在閉包中獲取到值,并且可以通過reply閉包給Flutter端回復(fù)
  • Flutter端處理該回復(fù)

原生端的插件類增加的代碼:

private static var basicMessageChannel: FlutterBasicMessageChannel!

// 原生和Flutter互相發(fā)送數(shù)據(jù)
basicMessageChannel = FlutterBasicMessageChannel(name: "flutter_plugin_basic_nakiri", binaryMessenger: registrar.messenger())
        
// 設(shè)置消息接收器,用來接收數(shù)據(jù);當(dāng)Flutter端發(fā)送消息過來的時(shí)候,會自動回調(diào);設(shè)置為nil時(shí)取消監(jiān)聽
basicMessageChannel.setMessageHandler { value, reply in
    reply(value as! Int + 1) // 使用reply給Flutter端回復(fù)消息
}

Flutter端插件只需要增加一個(gè)屬性即可:

static const BasicMessageChannel basicMessageChannel = BasicMessageChannel("flutter_plugin_basic_nakiri", StandardMessageCodec()); /// 定義一個(gè)渠道事件監(jiān)聽;名字需要唯一且各端保持一致

然后在Flutter測試工程里,對原生發(fā)送數(shù)據(jù):

Nakiri.basicMessageChannel.send(_basicNumber).then((value) {
    setState(() {
        _basicNumber = value;
    });
});

原生端使用FlutterBasicMessageChannel發(fā)送數(shù)據(jù)給Flutter端

流程如下:

  • Flutter端創(chuàng)建BasicMessageChannel
  • Flutter端使用setMessageHandler方法設(shè)置該ChannelHandler回調(diào)
  • 原生端創(chuàng)建該nameBasicMessageChannel
  • 原生端使用該BasicMessageChannel通過sendMessage方法向Flutter端發(fā)送消息
  • Flutter端的Handler收到發(fā)送的消息,并處理消息,然后通過return進(jìn)行回復(fù)
  • 原生端處理該回復(fù)

原生端的插件類增加的代碼:

// 給Flutter發(fā)送數(shù)據(jù),并等待Flutter端回復(fù)
SwiftNakiriPlugin.basicMessageChannel.sendMessage(number + 1) { value in
    result(value)
}

Flutter端插件增加的代碼:

/// 設(shè)置原生發(fā)送消息給Flutter時(shí)的回調(diào)
basicMessageChannel.setMessageHandler((message) async {
    return message; /// 收到消息后,可以通過return把值回復(fù)給原生
});

FlutterEventChannel

由于FlutterEventChannel只能原生給Flutter發(fā)送消息,并且無返回值,所以它只能用來傳輸一些實(shí)時(shí)數(shù)據(jù)。
使用FlutterEventChannel步驟如下:

  • Flutter端定義一個(gè)EventChannel
  • Flutter端使用receiveBroadcastStream里的listen監(jiān)聽該Channel
  • 原生端定義一個(gè)EventChannel
  • 原生端通過setStreamHandler方法設(shè)置代理
  • 原生端實(shí)現(xiàn)代理的onListenonCancel方法,并在onListen里獲取eventSink閉包,在onCancel方法里釋放eventSink閉包
  • 原生端使用eventSink閉包方法消息
  • Flutter端接收到消息并處理

原生端增加一個(gè)類,用來封裝相關(guān)邏輯:

class ZTEventChannel: NSObject {
    
    private var eventChannel: FlutterEventChannel?
    
    private var eventSink: FlutterEventSink?
    
    private var timer: Timer?
    
    private var number = 5
    
    override init() {
        super.init()
    }
    
    required convenience init(binaryMessenger messenger: FlutterBinaryMessenger) {
        self.init()
        eventChannel = FlutterEventChannel(name: "flutter_plugin_event_nakiri", binaryMessenger: messenger) // 通道名必須唯一且和各端保持一致
        eventChannel?.setStreamHandler(self)
    }
    
    private func removeTimer() {
        timer?.invalidate()
        timer = nil
        number = 5
    }
    
    private func createTimer() {
        if #available(iOS 10.0, *) {
            if timer == nil {
                timer = Timer.scheduledTimer(withTimeInterval: 5, repeats: true, block: { [weak self] (timer) in
                    self?.timerCall()
                })
            }
        }
    }
    
    @objc private func timerCall() {
        if let event = eventSink {
            event(number)
            
            number += 5
        }
    }
}

extension ZTEventChannel: FlutterStreamHandler {
    
    func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
        // 在這里獲取到eventSink
        self.eventSink = events
        createTimer()
        
        return nil
    }
    
    func onCancel(withArguments arguments: Any?) -> FlutterError? {
        // 在這里移除eventSink
        self.eventSink = nil
        removeTimer()
        
        return nil
    }
}

然后在原生端插件類里使用該類:

// 原生實(shí)時(shí)發(fā)送數(shù)據(jù)流給Flutter
eventChannel = ZTEventChannel(binaryMessenger: registrar.messenger())

Flutter端插件增加一個(gè)屬性:

static const EventChannel eventChannel = EventChannel("flutter_plugin_event_nakiri"); /// 定義一個(gè)渠道事件監(jiān)聽;名字需要唯一且各端保持一致

然后在別的地方可以使用該屬性來接收原生端發(fā)來的數(shù)據(jù)了:

_initStream() {
    /// 監(jiān)聽原生發(fā)來的消息
    _stream ??= Nakiri.eventChannel.receiveBroadcastStream().listen((data) {
        /// 這里的data就是原生端發(fā)送過來的數(shù)據(jù)
        setState(() {
            _eventValue = data;
        });
        }, onError: (error) { /// 錯(cuò)誤處理
        setState(() {
            _eventValue = -5;
        });
    });
}

此外,還可以暫停接收數(shù)據(jù)、重新接收數(shù)據(jù)和移除接收數(shù)據(jù):

/// 暫停數(shù)據(jù)接收
_stream?.pause();

/// 恢復(fù)數(shù)據(jù)流
_stream?.resume();

_removeStream() {
    if (_stream != null) {
        /// 移除監(jiān)聽
        _stream?.cancel();
        _stream = null;
    }
}

移除監(jiān)聽后,想要重新監(jiān)聽時(shí),只需要調(diào)用_initStream()方法即可,不需要重新創(chuàng)建eventChannel

插件上傳到Pub

上傳插件前,需要完善一些資料:

  • README.md介紹包的文件
  • CHANGELOG.md記錄每個(gè)版本中的更改
  • LICENSE包含軟件包許可條款的文件
  • pubspec.yaml的資料
  • 所有公共API的API文檔

首先是pubspec.yaml,對Flutter插件來說,pubspec.yaml里除了插件的依賴,還包含一些元信息,根據(jù)需要,把這些補(bǔ)上:

name: xxx # 要發(fā)布的項(xiàng)目名稱
description: xxxxxx. # 項(xiàng)目描述
version: 0.0.1  # 發(fā)布的版本
homepage: http://www.github.com/xxx  # 項(xiàng)目主頁
issue_tracker: http://www.github.com/xxx # issue,一般寫當(dāng)前插件源代碼的Github issue地址
repository: http://www.github.com/xxx.git # 一般寫當(dāng)前插件源代碼的Github地址  

另外,發(fā)布到Pub上的包需要包含一個(gè)LICENSE許可條款文件,不想麻煩的話,可以在GitHub創(chuàng)建倉庫的時(shí)候選中一個(gè)。

發(fā)布前檢查

我們打開命令行,跳轉(zhuǎn)到pubspec.yaml文件所在的目錄,在命令行使用以下命令來測試發(fā)布:

flutter packages pub publish --dry-run --server=https://pub.dartlang.org  

之所以使用--server來指定服務(wù)器,是因?yàn)槲覀冊谂渲铆h(huán)境的時(shí)候,一般都配置了這2個(gè)變量:PUB_HOSTED_URL=https://pub.flutter-io.cnFLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn,直接上傳會出現(xiàn)問題

如果沒有發(fā)現(xiàn)問題,如圖所示:


無報(bào)錯(cuò).png

然后還需要做的就是上傳前的需要清理插件,避免插件過大無法上傳:

flutter clean

使用以下命令來發(fā)布插件:

flutter packages pub publish --server=https://pub.dartlang.org 

因?yàn)槭前l(fā)布到谷歌的平臺,所以需要登錄谷歌賬號進(jìn)行認(rèn)證。
在我們輸入flutter packages pub publish命令之后,我們會收到一條認(rèn)證鏈接,使用瀏覽器打開鏈接就可以驗(yàn)證了。

驗(yàn)證.png

我們選擇自己的賬戶,即可開始驗(yàn)證,命令行會自行同步狀態(tài),無須我們自己處理的。

開始驗(yàn)證.png

網(wǎng)頁出現(xiàn)以下提示,就說明驗(yàn)證成功。

驗(yàn)證成功.png

之后我們只需要等待即可,命令行會自行上傳插件到Pub的。
但是,如果遇到這種情況,說明是被墻了,需要使用代理。

失敗.png

特別說明下,在代理客戶端上開啟了代理,并不等于命令行就開啟了代理,命令行需要額外開啟,具體方法可自行查找。
上傳成功后,會出現(xiàn)如下提示:

成功.png

上傳成功后,并不會馬上能看到,請耐心等待。

插件的使用方式

插件有4種使用方式:

  • pub
  • git
  • 本地
  • 私有pub庫

pub依賴

這種是最常見的方式,直接在工程的pubspec.yaml中寫上你需要的插件名和版本,之后執(zhí)行Pub get就行了。

dependencies:
  flutter:
    sdk: flutter
  
  nakiri: ^0.0.1 # 添加庫

git依賴

如果我們不想發(fā)布到pub,但又想團(tuán)隊(duì)共享插件,那么我們可以把庫上傳到git倉庫里面,然后在pubspec.yaml中配置,之后執(zhí)行Pub get就行了。

dependencies:
  flutter:
    sdk: flutter
    
  nakiri:
    git:
      url: https://github.com/xxx/nakiri.git
      ref: nakiri_fixes_issue_520
      path: packages/nakiri_2
  • url:git地址
  • ref:表示git引用,可以是commit hashtag或者分支
  • path:如果git倉庫中有多個(gè)軟件包,則可以使用此屬性指定軟件包

本地依賴

上面的方法都需要上傳到服務(wù)器,較為麻煩,如果只是自己用或者調(diào)試插件,那么最好的方式就是本地依賴,只需要在pubspec.yaml中配置路徑,之后執(zhí)行Pub get就行了。

dependencies:
  flutter:
    sdk: flutter

nakiri:
    path: ../xxx/nakiri/

path可以是相對路徑,也可以是絕對路徑

私有pub倉庫依賴

一般而言,pub管理插件比git管理方便,所以一般大公司都會搭建自己的私有pub倉庫,依賴私有pub倉庫也很簡單,只需要在pubspec.yaml中配置完成后,之后執(zhí)行Pub get就行了。

dependencies:
  flutter:
    sdk: flutter

  nakiri: 
    hosted:
      name: nakiri
      url: http://your-package-server.com
    version: ^0.0.1

依賴覆蓋

當(dāng)2個(gè)以上的插件依賴于另一個(gè)插件,并且他們所依賴的版本不一致的時(shí)候,就可能出現(xiàn)版本沖突,要解決這個(gè)沖突,我們可以使用dependency_overrides強(qiáng)制某個(gè)插件使用某個(gè)版本,如:

dependencies:
  nakiri_2: ^1.0.1
  nakiri: ^0.0.1
dependency_overrides:
  url_launcher: ^5.4.0

這里假設(shè)nakiri_2nakiri都依賴url_launcher,但所依賴的版本不一樣,通過這種方式,可以讓它們都依賴成5.4.0版本。
雖然這種方式可以解決依賴報(bào)錯(cuò),但可能會由于版本的改動使得API接口可能不一樣,最終還是可能會出問題,所以,慎用。

相關(guān)鏈接

整個(gè)項(xiàng)目已經(jīng)發(fā)到GitHub:demo地址
插件也已經(jīng)發(fā)布到Pub倉庫:插件地址
參考資料:Flutter中文網(wǎng) - Flutter插件教程

iOS OC Swift Flutter開發(fā)群 139322447

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,983評論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,978評論 2 374

推薦閱讀更多精彩內(nèi)容