Flutter 入門使用

Flutter

概要

Flutter是一個(gè)由谷歌開發(fā)的開源移動(dòng)應(yīng)用軟件開發(fā)工具包书在。它是以dart為基礎(chǔ)做出的一套SDK,支持在Android和iOS上構(gòu)建APP案腺。簡(jiǎn)單來(lái)說(shuō)它與RN另凌、Weex等眾多混合開發(fā)平臺(tái)框架一樣,都是一套代碼實(shí)現(xiàn)多平臺(tái)發(fā)布的跨平臺(tái)框架孙乖。

Flutter與其他跨平臺(tái)的最大不同之處是它自建了一個(gè)2D渲染引擎.

  • Flutter 框架
flutter框架
 * Flutter框架可分為Framework層和Engine層浙炼;

    - Flutter Framework: 整個(gè)框架層都是用Dart語(yǔ)言實(shí)現(xiàn),該層提供一套基礎(chǔ)庫(kù)唯袄, 用于處理動(dòng)畫弯屈、繪圖和手勢(shì)等。并且基于繪圖封裝了一套 UI組件庫(kù)恋拷,并且細(xì)分為兩種風(fēng)格的組件.
    
        1. Materail : Android風(fēng)格的Widget;
        2. Cupertino: IOS風(fēng)格的Widget;

    - Flutter Engine: 這是一個(gè)純 C++實(shí)現(xiàn)的框架層资厉,包含了 Skia引擎(高性能渲染引擎)、Dart運(yùn)行環(huán)境蔬顾、文字排版引擎等宴偿。

1. 配置Flutter環(huán)境

參考:

flutter 中文網(wǎng)

flutter English

 flutter doctor

1.1 初始化項(xiàng)目

  • 純flutter工程d
    flutter create myapp 
    cd myapp
    flutter devices
    flutter run -d <deviceID>
  • flutter module
   flutter create -t module smarthome_flutter
  • 默認(rèn)情況下湘捎,模板支持使用Java編寫Android代碼、用Objective-C編寫iOS代碼窄刘;要使用Kotlin或Swift窥妇,請(qǐng)使用-i和/或-a標(biāo)志:
flutter create -i swift -a kotlin myapp

SmartHome集成flutter 目前采用的是flutter module的模式

1.2 Flutter 版本

#查看當(dāng)前使用的分支
flutter channel 

#切換分支 stable為flutter的穩(wěn)定版分支
flutter channel stable
flutter channel master
flutter channel beta
flutter channel dev

1.3 升級(jí) Flutter channel 和 packages

  • 要同時(shí)更新Flutter SDK和你的依賴包,在你的應(yīng)用程序根目錄(包含pubspec.yaml文件的目錄)中運(yùn)行flutter upgrade 命令
flutter upgrade
  • 如果您修改了pubspec.yaml文件娩践,或者只想更新應(yīng)用依賴的包(不包括Flutter SDK)活翩,請(qǐng)使用以下命令:
#獲取pubspec.yaml文件中列出的所有依賴包
flutter packages get
#獲取pubspec.yaml文件中列出的所有依賴包的最新版本
flutter packages upgrade

2. 如何集成Flutter

2.1 確保所有的submodule已下載

Native工程下 feature-flutter分支 執(zhí)行 git submodule update

2.2 生成flutter module的個(gè)人本地配置

cd [Your SmartHome Project Path]/smarthome_flutter

flutter run

2.3 cocoapods 集成flutter

回到Native工程的路徑下 pod install

2.4 Q&A

  • 集成flutter后,xcode 編譯運(yùn)行APP時(shí) 翻伺,報(bào):
    error: Multiple commands produce .......
*   打開.ios/Runner.xcworkspace -> TARGETS -> Runner      -> Build Phases -> Embed Frameworks  然后刪除       flutter.frameworks   
  • flutter 中webView打不開材泄,log: Trying to embed a platform view but the PaintContext does not support embedding
    • 在.iOS/ 目錄下Runner.xcworkspace工程中 的info.plist 文件中添加配置
    • key = io.flutter.embedded_views_preview
    • value = YES
  • Lost connection to device

    • brew upgrade --fetch-HEAD usbmuxd

3. 原生和Flutter相互通信 platform channels

通過platform channels 在flutter 和宿主(ios/andriod)之間傳遞消息,如下圖所示:

platform channels

Samples

  • flutter端 關(guān)鍵代碼


import 'package:flutter/services.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:smarthome_flutter/common/tools/SHEventBus.dart';
part 'platfrom.g.dart';

var sh_event_bus = EventBus();
class SHPlatform{
  static final MethodChannel channel =  _setupChannel();



  static MethodChannel _setupChannel(){
    var channel = MethodChannel("com.jd.smarthome",StandardMethodCodec());
    
    channel.setMethodCallHandler((MethodCall call) async {
      String method = call.method;
      dynamic arguments = call.arguments;
      switch(method){
        case 'updateInfo':
          sh_event_bus.emit('updateHomePage');
        break;

        default:
          throw new PlatformException(code:"");
      }
    });

    return channel;
  }

  static DeviceInfo _deviceInfo;
  static Future<DeviceInfo> getDeviceInfo() async{
    if(_deviceInfo == null){
      return SHPlatform.channel.invokeMethod("deviceInfo")
      .then((onValue){
        _deviceInfo = DeviceInfo.fromJson(Map<String, dynamic>.from(onValue));
        return _deviceInfo;
      });
    }else{
      return Future.value(_deviceInfo);
    }
  }
}

@JsonSerializable()
class DeviceInfo{
  String platform;
  String hardPlatform;
  String systemVersion;
  String appVersion;
  String channel;
  String deviceId;
  String tgt;
  String pin;
  DeviceInfo();
  factory DeviceInfo.fromJson(Map<String, dynamic> json) => _$DeviceInfoFromJson(json);
}

  • iOS 端關(guān)鍵代碼
-(void)showFlutterView{
    FlutterEngine *flutterEngine = [(AppDelegate *)[[UIApplication sharedApplication] delegate] flutterEngine];
    
    __weak __typeof(self) weakSelf = self;
    
    // 要與main.dart中一致
    NSString *channelName = @"com.jd.smarthome";
    
    //FlutterMethodChannel *messageChannel;
    self.messageChannel = [FlutterMethodChannel methodChannelWithName:channelName
                                                                       binaryMessenger:flutterEngine];
    [self.messageChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {
        
        if ([call.method isEqualToString:@"toNativeSomething"]) {
            UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"flutter回調(diào)" message:[NSString stringWithFormat:@"%@",call.arguments] delegate:self cancelButtonTitle:@"確定" otherButtonTitles:nil];
            [alertView show];
            
            // 回調(diào)給flutter
            if (result) {
                result(@10);
            }
        } else if ([call.method isEqualToString:@"toNativePush"]) {
            
            //[GLOBAL_Nav pushViewController:vc animated:YES andLastName:getClassName];
            
        } else if ([call.method isEqualToString:@"toNativePop"]) {
            [GLOBAL_Nav popViewControllerAnimated:YES];
        }else if([call.method isEqualToString:@"deviceInfo"]){
            NSDictionary* data = @{
                                   @"platform":kPlatValue,
                                   @"hardPlatform":[CommonMethod getPlatformName],
                                   @"systemVersion":[CommonMethod getSystemVersion],
                                   @"appVersion":[CommonMethod getAppVersion],
                                   @"channel":kChannelValue,
                                   @"deviceId":[OpenUDID value],
                                   @"tgt":[LoginObjectClass sharedLoginObjectClass].A2String,
                                   @"pin":[LoginObjectClass sharedLoginObjectClass].pin,
                                   };
            result(data);
        }else{
            result(FlutterMethodNotImplemented);
        }
    }];
    
    FlutterViewController *flutterViewController = [[FlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil];
    flutterViewController.navigationItem.title = @"Flutter Demo";
    
    [GLOBAL_Nav pushViewController:flutterViewController animated:YES andLastName:getClassName];
    
    [self updateFlutterHomePage];
}

-(void)updateFlutterHomePage{
    
    [self.messageChannel invokeMethod:@"updateInfo" arguments:@{@"page": @"1"} result:^(id  _Nullable result) {
        
        if([result isKindOfClass:[FlutterError class]]){
    
            FlutterError *error = (FlutterError *)result;
            SHLogError(kLogFlutter, @"updateInfo with error message: %@", error.message);
            
        }else  if (result == FlutterMethodNotImplemented) {
            SHLogError(kLogFlutter, @"updateInfo was unexepectedly not implemented ");
            
        }else{
            SHLogInfo(kLogFlutter, @"updateInfo success");
            
        }
        
    }];
}

4 Flutter 狀態(tài)管理 ----Provider

4.1 為什么需要狀態(tài)管理

state1

在 State 屬于某一個(gè)特定的 Widget吨岭,在多個(gè) Widget 之間進(jìn)行交流的時(shí)候脸爱,雖然你可以使用 callback 解決,但是當(dāng)嵌套足夠深的話未妹,我們?cè)黾臃浅6嗫膳碌睦a簿废。

隨著功能的增加,你的應(yīng)用程序?qū)?huì)有幾十個(gè)甚至上百個(gè)狀態(tài)络它。這個(gè)時(shí)候應(yīng)用應(yīng)該會(huì)像下圖一樣族檬。

這時(shí)候,我們便迫切的需要一個(gè)架構(gòu)來(lái)幫助我們理清這些關(guān)系化戳,狀態(tài)管理框架應(yīng)運(yùn)而生单料。

state2

4.2 怎么用provider

4.2.1、添加依賴

dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^0.1.2
  webview_flutter: ^0.3.9+1
  dio: 2.1.10
  json_annotation: ^2.0.0
  provider: ^3.0.0
  intl: ^0.15.7
  crypto: ^2.0.6
  logging:
  cached_network_image: ^1.1.1
  flutter_easyrefresh: ^1.2.7

4.2.2点楼、創(chuàng)建數(shù)據(jù)Model

//這里使用了 mixin 混入了 ChangeNotifier扫尖,這個(gè)類能夠幫助我們自動(dòng)管理所有l(wèi)isteners。當(dāng)調(diào)用 notifyListeners() 時(shí)掠廓,它會(huì)通知所有聽眾進(jìn)行刷新换怖。

class CounterModel with ChangeNotifier {
  int _count = 0;
  int get value => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}

4.2.3、創(chuàng)建頂層共享數(shù)據(jù)

class GlobalConfig{
  static get version => '0.0.3';
  static get counter => CounterModel();

  static ThemeData get themeData => ThemeData(
    primaryColor: Color(0xff181B34),
    textTheme: TextTheme(
      title: TextStyle(
        fontSize: 14,
        color: Colors.white
      )
    )
  );

  static List<SingleChildCloneableWidget> get providers => [
    Provider<String>.value(value:GlobalConfig.version),
    ChangeNotifierProvider<CounterModel>.value(
      value: GlobalConfig.counter,
    )
  ];
}
class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: GlobalConfig.providers,
      child: MaterialApp(
        title: 'SmartHome demo',
        theme: GlobalConfig.themeData,
        home: MyHomePage(
          title: "APP",
        ),
        debugShowCheckedModeBanner: false,
      ),
    );
  }
}

4.2.4蟀瞧、在子頁(yè)面中獲取狀態(tài)

  • Provider.of(context)
class FirstScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final _counter = Provider.of<CounterModel>(context);

    return Scaffold(
      appBar: AppBar(
        title: Text('FirstPage'),
      ),
      body: Center(
        child: Text(
          'Value: ${_counter.value}',
          style: TextStyle(fontSize: 11),
        ),
      ),    
    );
  }
}

  • Consumer
class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
  
  
    return Scaffold(
      appBar: AppBar(
        title: Text('Second Page'),
      ),
      body: Consumer2<CounterModel,string>(
        builder: (context, CounterModel counter, string version, _) => Center(
              child: Text(
                'Value: ${counter.value}' + version,
                style: TextStyle(fontSize: 11,
               ),
            ),
         ),
      ),
    );
  }
}
  • Different ?
    • 通過閱讀源碼可以發(fā)現(xiàn)沉颂,Consumer 就是通過 Provider.of<T>(context) 來(lái)實(shí)現(xiàn)的。但是從實(shí)現(xiàn)來(lái)講 Provider.of<T>(context) 比 Consumer 簡(jiǎn)單好用悦污,但是我們推薦優(yōu)先使用Consumer铸屉,好處在于能夠在復(fù)雜項(xiàng)目中,極大地縮小你的控件刷新范圍切端。Provider.of<T>(context) 將會(huì)把調(diào)用了該方法的 context 作為聽眾彻坛,并在 notifyListeners 的時(shí)候通知其刷新。

5 Map -> model

介紹一下官方推薦的json_serializable package包。 它是一個(gè)自動(dòng)化的源代碼生成器昌屉,可以在開發(fā)階段為我們生成JSON序列化模板.

5.1钙蒙、在項(xiàng)目中設(shè)置json_serializable

dependencies:
  # Your other regular dependencies here
  json_annotation: ^2.0.0

dev_dependencies:
  # Your other dev_dependencies here
  build_runner: ^1.0.0
  json_serializable: ^3.0.0

5.2 以json_serializable的方式創(chuàng)建model類

import 'package:json_annotation/json_annotation.dart';
part 'skill_model.g.dart';

@JsonSerializable()
class SkillList {
  SkillList();
  int page_size;
  int sum_size;
  int current_page;

  List<SkillListItem> skills;
  factory SkillList.fromJson(Map<String, dynamic> json) => _$SkillListFromJson(json);
  Map<String, dynamic> toJson() => _$SkillListToJson(this);  
}
//一個(gè)SkillList.fromJson 構(gòu)造函數(shù), 用于從一個(gè)map構(gòu)造出一個(gè) User實(shí)例 map structure
//一個(gè)toJson 方法, 將 User 實(shí)例轉(zhuǎn)化為一個(gè)map.

  • json_serializable第一次創(chuàng)建類時(shí),您會(huì)看到與下圖類似的錯(cuò)誤怠益。
jsonError
#一次性生成
flutter packages pub run build_runner build

#持續(xù)生成
flutter packages pub run build_runner watch
  • 使用

request.parseFunction = (json){
      SkillList tmp = SkillList.fromJson(json);
      Map<String, dynamic> newMap = tmp.toJson();
      return SkillList.fromJson(json);
    };

6 異步async、await和Future

Flutter中瘾婿,雖然Dart是基于單線程模型的蜻牢,但是這并不意味著我們沒法完成異步操作。在Dart中我們可以通過async關(guān)鍵字來(lái)聲明一個(gè)異步方法偏陪,在異步方法中可以使用await表達(dá)式掛起該異步方法中的某些步驟,從而實(shí)現(xiàn)等待某步驟完成的目的; Future是Dart中提供的一個(gè)類抢呆,它用于封裝一段在將來(lái)會(huì)被執(zhí)行的代碼邏輯。

Note

  • async修飾的異步方法需要聲明返回一個(gè)Future類型笛谦,如果方法體內(nèi)沒有主動(dòng)的返回一個(gè)Future類型抱虐,系統(tǒng)會(huì)將返回值包含到一個(gè)Future中返回。

  • await表達(dá)式的表達(dá)式部分需要返回一個(gè)Future對(duì)象饥脑。

  • await表達(dá)式需要在一個(gè)async修飾的方法中使用才會(huì)生效恳邀。 關(guān)于async和await的更多詳情可以參閱官方文檔

end

推薦

flutter 中文

flutter English

flutter github

Dart Packages

An open list of apps built with Flutter

完整項(xiàng)目 Flutter / RN / Kotlin / Weex

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末灶轰,一起剝皮案震驚了整個(gè)濱河市谣沸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌笋颤,老刑警劉巖乳附,帶你破解...
    沈念sama閱讀 211,561評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異伴澄,居然都是意外死亡赋除,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門非凌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)举农,“玉大人,你說(shuō)我怎么就攤上這事敞嗡〔⒒龋” “怎么了?”我有些...
    開封第一講書人閱讀 157,162評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵秸妥,是天一觀的道長(zhǎng)滚停。 經(jīng)常有香客問我,道長(zhǎng)粥惧,這世上最難降的妖魔是什么键畴? 我笑而不...
    開封第一講書人閱讀 56,470評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上起惕,老公的妹妹穿的比我還像新娘涡贱。我一直安慰自己,他們只是感情好惹想,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,550評(píng)論 6 385
  • 文/花漫 我一把揭開白布问词。 她就那樣靜靜地躺著,像睡著了一般嘀粱。 火紅的嫁衣襯著肌膚如雪激挪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,806評(píng)論 1 290
  • 那天锋叨,我揣著相機(jī)與錄音垄分,去河邊找鬼。 笑死娃磺,一個(gè)胖子當(dāng)著我的面吹牛薄湿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播偷卧,決...
    沈念sama閱讀 38,951評(píng)論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼豺瘤,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了听诸?” 一聲冷哼從身側(cè)響起炉奴,我...
    開封第一講書人閱讀 37,712評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蛇更,沒想到半個(gè)月后瞻赶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,166評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡派任,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,510評(píng)論 2 327
  • 正文 我和宋清朗相戀三年砸逊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片掌逛。...
    茶點(diǎn)故事閱讀 38,643評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡师逸,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出豆混,到底是詐尸還是另有隱情篓像,我是刑警寧澤,帶...
    沈念sama閱讀 34,306評(píng)論 4 330
  • 正文 年R本政府宣布皿伺,位于F島的核電站员辩,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏鸵鸥。R本人自食惡果不足惜奠滑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,930評(píng)論 3 313
  • 文/蒙蒙 一丹皱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧宋税,春花似錦摊崭、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,745評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至乏屯,卻和暖如春根时,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背瓶珊。 一陣腳步聲響...
    開封第一講書人閱讀 31,983評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工啸箫, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留耸彪,地道東北人伞芹。 一個(gè)月前我還...
    沈念sama閱讀 46,351評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像蝉娜,于是被迫代替她去往敵國(guó)和親唱较。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,509評(píng)論 2 348