很簡單的Flutter填小坑

1. 咱們須要用Flutter麼?

FlutterRN不一樣的是前者和原生iOS/Android組件好比UIButton其實沒半毛線關係,全部元素都是Flutter本身畫的,而RN則只是作了個橋調用而已,明白這個可能對你下決定/忽悠老闆有很是大的意義android

如下開始,咱們假設你已經有了必定的Flutter基礎概念, 好比Release/Debug版本,好比如何跑起來一個HelloWordios

2. 混合棧:

爸爸的指南戳這裏git

谷歌寫的很清楚了你們找着1234567就能夠二者混合了,固然你還能夠谷歌搜【flutter混合】github

配置中有一些爸爸們沒告訴你的事,仍是以iOS工程爲主,畢竟Android不熟:json

0.flutter create -t module xxx 中的 -t

有不少實例裏是沒有-t的,簡單來講不加-t,你的iOS/android的工程將會隨git一塊兒提交,而加了後他們永遠不會被提交,別人拉新代碼都須要flutter create來正常跑起來。xcode

咋看一下-t會更高端一點,你不須要改任何原生工程全用flutter寫就行,並且也不會由於sdk改動或者xcode改動影響到你的iOS文件夾,可是請誠實點面對這個社會:微信

  • 默認生成的iOS文件夾的podfile是沒有!use_framwork
  • 你肯定不須要改plist來添加白名單什麼嘛
  • 你肯定appdelegate裏不須要加些配置來協助你的第三方庫好比微信嘛

因此若是-t了,做爲架構師/研究者的你請自覺作好寫腳本去修改上面這些文件的準備。架構

同時請妥善運行flutter packages get,由於他會從新搞一下你的iOS文件夾的內容, 而flutter build --release會默認幫你flutter packages get,因此若是你有本身的初始化腳本,那執行順序應該是:app

  • flutter packages get
  • 你的腳本
  • flutter build --release -no-pub -no-pub會忽略掉build時的那次get

1. Build Phases中的script其實不是每次都須要run的

https://user-gold-cdn.xitu.io/2019/5/30/16b08538ebc2104c?w=1370&h=456&f=jpeg&s=65695
上面這個勾推薦你勾上後再提交,由於這個腳本其實只會對 Flutter Release版本的構建有影響,

  • 說白了,若是你只是普通調試,跑不跑這個腳本,結果是同樣的都是Flutter Debug模式
  • 這個腳本會增長編譯時間,因此無需flutter更新的工程師不須要關心他是否是跑了
  • 若是你是Debug的主工程想跑ReleaseFlutter,不跑這個Script是會crash的,這就是由於Flutter Release的配置須要這個腳原本完成

2. 請老老實實按照谷歌推薦的方式集成,剛玩時候你的目標是跑起來而後迅速去熟悉dart以及flutter佈局,並非研究原生與flutter的耦合以及框架解藕

3. 打包腳本中Flutter run是不合理的

可能你也會用jenkins或者fastlane去給QA打包,這時候若是要生成產物請走Flutter build --release命令,由於Flutter run會直接把進程卡住而後你就沒法持續了,雖然你能夠選擇後臺運行run,可是你無法保證同步~框架

4. 跳轉仍是老老實實走 flutter_boost

至於爲何,能夠翻翻鹹魚寫的文章,仍是從簡化的說就是:

  • 谷歌給的flutter混合棧中,FlutterViewController肚子裏就是Flutter引擎,因此若是純flutter項目你會發現,其實vc就只有一個,他是在單個vc中畫新頁面來完成push操做的
  • 可是混合棧中你必定會FlutterViewController 去push下一個FlutterViewController ,這樣無限增長的引擎會讓你的應用在短期內就崩潰~
  • 鹹魚的方案就是保留一個引擎,而後在push的時候把引擎單例從前一個vc拿到後一個,而後經過截圖等操做重現滑動返回或者pop等操做

固然若是你是很厲害的那種必定要本身玩,那也能夠,不然請看2裏說的在這裏同樣適用~

用過的孩子必定對query這個key很憤怒,確實你也不明白爲何鹹魚官方並無說這個key,但事實上安卓側原生接到的參數都是經過這個包着的,即 { "query": { "name" : xxx }}, 因此iOS的也能夠注意下

5. MethodChannel系列交互

普通的交互註冊你點這裏就行,很簡單

可是其實這樣並很差看~雖然官方推薦,可是官方還有個很優雅的姿式叫FlutterPlugin

我是個優雅的🌰

說穿了MethodChannel只是須要原生有個時機註冊進Flutter引擎而已,至於何時,隨時都行,因此咱們找到了FlutterPlugin的時機,全部第三方的flutter插件(這些插件多數其實也是經過channel調用原生來解決的),其實也是經過註冊的方式注入引擎的,若是你想看看怎麼來的,你能夠在你的工程裏找GeneratedPluginRegistrant.m這個文件,這是個系統生成的文件,他會幫你註冊全部你引入flutter的插件:

@implementation GeneratedPluginRegistrant

+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry {
  [FLTDeviceInfoPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTDeviceInfoPlugin"]];
  ...
}

@end
複製代碼

因此咱們有個大膽的想法,若是咱們本身也搞個plugin,裏面塞個channel,用來作全部flutter與咱們現有主工程的進行交互,不就能夠裝的很優雅了嘛? 可是問題是:系統生成的GeneratedPluginRegistrant我怎麼在裏面加上我本身的插件? 由於我本身的插件在主工程,並非在flutter裏引入的鴨...

這時候聰明的你可能已經想到了那在Swift時代被遺忘的黑魔法:

- (instancetype)init
{
    if (self = [super init]) {
        _viewController = [FLBFlutterViewControllerAdaptor new];
        [_viewController view];
        Class clazz = NSClassFromString(@"GeneratedPluginRegistrant");
        if (clazz) {
            if ([clazz respondsToSelector:NSSelectorFromString(@"registerWithRegistry:")]) {
                [clazz performSelector:NSSelectorFromString(@"registerWithRegistry:")
                            withObject:_viewController];
            }
        }
    }
    
    return self;
}
複製代碼

上面這段是從flutter_boost中找到的,因此你應該明白其實你也能夠進行偷換GeneratedPluginRegistrant中的registerWithRegistry方法來先註冊flutter本身的插件,再註冊主工程插件已保持優雅的姿態了吧~

6. 與原生通訊該傳些什麼

FlutterResult類裏規定了記得傳String,固然傳人能讀得懂的字符串了,請重複這句【傳人能讀得懂的字符串】,因此千萬不要耍心機把一個data搞成字符串而後傳給flutter讓他變回data甚至變回image,記得,你讀不懂image字符串,flutter也不懂。而json字符串你看得懂,因此flutter也看得懂

因此複雜的文件,你只要知道,flutter雖然讀不懂data,可是他也能訪問NSTemporaryDirectory, 因此你能夠經過暫時文件夾路徑的方式來支持雙方共享同一個數據,雖然也不太推薦,可是你好像也只能這麼幹了

private func saveToFile(image: UIImage) -> String? {
    guard let data = image.jpg(compressionQuality: 1.0) else {
        return nil
    }
    let tempDir = NSTemporaryDirectory()
    let imageName = "image_picker_\(ProcessInfo().globallyUniqueString).jpg"
    let filePath = tempDir.appending(imageName)
    if FileManager.default.createFile(atPath: filePath, contents: data, attributes: nil) {
        return filePath
    } else {
        return nil
    }
}
複製代碼

好比像樓上這樣

3. Dart裏我要準備些啥

1. 抽象底層服務

若是一開始規劃是有原生的基礎服務就調原生的,好比請求登陸拍照等等,那理想中其實你能夠以爲flutter自己代碼你只須要作業務就好了。

嗯,回想一下那個RN中編碼5分鐘,聯調5小時的你嗎?因此若是有時間,請爲flutter搭建全部用獲得的基礎設施,來大幅度加快調試速度:

下面這個模式可供你們參考,他能夠最大限度保證代碼可擴展性

咱們經過

上帝模式:isSonMode= falseflutter本身跑的模式

兒子模式:isSonMode= true即在主工程裏跑的模式

上述的區分來判斷咱們須要調用哪一種基礎服務

好比這是咱們的dart端請求調用:

class RequestService {
  static RequestService shared = RequestService();
  factory RequestService() =>
      GlobalConfig.isSonMode ? SonRequestService() : GodRequestService();

  Future<Map> post(String url, Map para) async {
    throw UnimplementedError("saveElement 方法木有實現哦");
  }

  Future<UploadItem> upload(UploadItem file) async {
    throw UnimplementedError("saveElement 方法木有實現哦");
  }
}


class GodRequestService implements RequestService  {
	Future<Map> post(String url, Map para) async {
		// 你能夠在這裏調用dio或者http請求
	}
	
	Future<UploadItem> upload(UploadItem file) async {	
		...
	}
}
複製代碼

Son就是調用原生的channel,因此不貼了,因此咱們根據GlobalConfig.isSonMode來切換使用哪一個具體的RequestService實現,來隔離每一個RequestService不會因爲判斷而形成的污染,

固然還有更花哨的判斷:

static Router _route =
      GlobalConfig.isAndroid ? AndroidRouteBox() : GlobalConfig.isSonMode ? FlutterBoostRouteBox() : GodRouteBox();
複製代碼

好比路由咱們在flutter上帝模式下用的是flutter本身跳轉, iOS 主工程下是flutter_boost, 安卓主工程下依舊是本身的跳轉

而在dart業務代碼中你只須要作 RequestService.shared.post...的調用,就能夠正常請求,不須要關心切換的問題了。

(固然請求抽象會在下期着重講,敬請期待~)

2. Model.fromJSON

工具處處都有 稍微推薦下這個,固然極其讚美手寫毅力擔當~可是注意點:

  • dart相比Swift來講對類型的嚴格性更高
  • int/double若是定義反了會炸,好比把1.0傳給int型屬性,在Swift中毫無波瀾,在dart中就是波濤洶涌,dart中妥善點能夠用num來修飾全部數字
  • String類型推薦都加上toString()

額外加個優雅的,Swift中的

var isUser : Bool {
	return xxx == 1 && xxxx = 2
}
這樣的計算屬性,在dart中能夠寫成:
bool get isUser => xxx == 1 && xxxx = 2 
或者
bool get isUser {
	return xxx == 1 && xxxx = 2 
} 
複製代碼

這樣能夠下降你的Widget中過多的業務計算,這些定義仍是讓Model來作吧

3. 忘記所謂的頁面生命週期

因爲引擎的繪製不一樣,致使flutter的頁面生命週期並無合適的回調/代理等來觸發,不要想着didUpdateWidget或者Dependency裏來作些奇怪的請求刷新,那地方不是讓你用來作這個事情的~ 惟一你能夠作的好像就只有在initState裏作完全部事情;

這裏就講點虛的,因此全部的操做須要嚴格從動做觸發,而不要是再從頁面級別觸發了,

舉個例子

Swift:button點擊 -> 跳轉 -> 返回 -> ViewWillAppear 刷新全頁

Flutter: button點擊 -> 刷新對應的Widget -> 跳轉

其實Swift的例子也很差,可是其實咱們由於懶基本都這樣幹了,可是在dart中咱們無法知道頁面生命週期,因此請用正確的時間作正確的事,固然這裏就容忍了滿屏幕的setState,確定會有人告訴你這樣是不合理的了~怎麼纔算合理,這裏就先不說了,畢竟這裏的目的是爲了讓你們把應用跑起來能上線,至於優雅不優雅後面熟悉了天然就有感受了。

4. 資源圖片

首先這樣你確定沒問題,可是記得外層的1x的圖必定要放的,不然認不出來~

pubspec.yaml中這樣就好了

assets:
  - assets/images/
  - assets/images/2.0x/
  - assets/images/3.0x/	
複製代碼

有時候你會發現你新加了個圖結果位置都放對告終果沒出來,沒事你debug多點幾回他就有了,若是確認名字沒問題的話,確實是可能有時候不會及時顯示出來的

5. 字體

比較有意思的是對於main入口:

return MaterialApp(
  title: '我是個demo',
  theme: ThemeData(
      fontFamily: Platform.isIOS ? 'PingFang SC' : null,)
}
複製代碼

你須要這樣設置後,在iOS手機上你的字體纔會好看,不然會出現各類奇奇怪怪的樣子,不過又有個隱藏坑就是:

你的全部頁面的頂層widget必須是material系列的這個纔會生效,這些是Material系列

因此若是你的頁面裏直接是:

class DemoPage extends StatelessWidget {
	@override
  Widget build(BuildContext context) {
  		return Container(
	        child: 頁面元素
		);
  }
}
複製代碼

你會發現你的字體依舊很奇怪,雖然頁面長得沒問題

是否是很簡單,看到這裏,坑確定還有,但至少大問題應該沒有了,你應該已經能夠愉快的跑起來你的應用了~

相關文章
相關標籤/搜索