Flutter 入门使用

Flutter

概要

Flutter是一个由谷歌开发的开源移动应用软件开发工具包。它是以dart为基础做出的一套SDK,支持在Android和iOS上构建APP。简单来说它与RN、Weex等众多混合开发平台框架一样,都是一套代码实现多平台发布的跨平台框架。

Flutter与其他跨平台的最大不同之处是它自建了一个2D渲染引擎.

  • Flutter 框架
flutter框架
 * Flutter框架可分为Framework层和Engine层;

    - Flutter Framework: 整个框架层都是用Dart语言实现,该层提供一套基础库, 用于处理动画、绘图和手势等。并且基于绘图封装了一套 UI组件库,并且细分为两种风格的组件.
    
        1. Materail : Android风格的Widget;
        2. Cupertino: IOS风格的Widget;

    - Flutter Engine: 这是一个纯 C++实现的框架层,包含了 Skia引擎(高性能渲染引擎)、Dart运行环境、文字排版引擎等。

1. 配置Flutter环境

参考:

flutter 中文网

flutter English

 flutter doctor

1.1 初始化项目

  • 纯flutter工程d
    flutter create myapp 
    cd myapp
    flutter devices
    flutter run -d <deviceID>
  • flutter module
   flutter create -t module smarthome_flutter
  • 默认情况下,模板支持使用Java编写Android代码、用Objective-C编写iOS代码;要使用Kotlin或Swift,请使用-i和/或-a标志:
flutter create -i swift -a kotlin myapp

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

1.2 Flutter 版本

#查看当前使用的分支
flutter channel 

#切换分支 stable为flutter的稳定版分支
flutter channel stable
flutter channel master
flutter channel beta
flutter channel dev

1.3 升级 Flutter channel 和 packages

  • 要同时更新Flutter SDK和你的依赖包,在你的应用程序根目录(包含pubspec.yaml文件的目录)中运行flutter upgrade 命令
flutter upgrade
  • 如果您修改了pubspec.yaml文件,或者只想更新应用依赖的包(不包括Flutter SDK),请使用以下命令:
#获取pubspec.yaml文件中列出的所有依赖包
flutter packages get
#获取pubspec.yaml文件中列出的所有依赖包的最新版本
flutter packages upgrade

2. 如何集成Flutter

2.1 确保所有的submodule已下载

Native工程下 feature-flutter分支 执行 git submodule update

2.2 生成flutter module的个人本地配置

cd [Your SmartHome Project Path]/smarthome_flutter

flutter run

2.3 cocoapods 集成flutter

回到Native工程的路径下 pod install

2.4 Q&A

  • 集成flutter后,xcode 编译运行APP时 ,报:
    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端 关键代码


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 端关键代码
-(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回调" message:[NSString stringWithFormat:@"%@",call.arguments] delegate:self cancelButtonTitle:@"确定" otherButtonTitles:nil];
            [alertView show];
            
            // 回调给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 状态管理 ----Provider

4.1 为什么需要状态管理

state1

在 State 属于某一个特定的 Widget,在多个 Widget 之间进行交流的时候,虽然你可以使用 callback 解决,但是当嵌套足够深的话,我们增加非常多可怕的垃圾代码。

随着功能的增加,你的应用程序将会有几十个甚至上百个状态。这个时候应用应该会像下图一样。

这时候,我们便迫切的需要一个架构来帮助我们理清这些关系,状态管理框架应运而生。

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、创建数据Model

//这里使用了 mixin 混入了 ChangeNotifier,这个类能够帮助我们自动管理所有listeners。当调用 notifyListeners() 时,它会通知所有听众进行刷新。

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

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

4.2.3、创建顶层共享数据

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、在子页面中获取状态

  • 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 ?
    • 通过阅读源码可以发现,Consumer 就是通过 Provider.of<T>(context) 来实现的。但是从实现来讲 Provider.of<T>(context) 比 Consumer 简单好用,但是我们推荐优先使用Consumer,好处在于能够在复杂项目中,极大地缩小你的控件刷新范围。Provider.of<T>(context) 将会把调用了该方法的 context 作为听众,并在 notifyListeners 的时候通知其刷新。

5 Map -> model

介绍一下官方推荐的json_serializable package包。 它是一个自动化的源代码生成器,可以在开发阶段为我们生成JSON序列化模板.

5.1、在项目中设置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的方式创建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);  
}
//一个SkillList.fromJson 构造函数, 用于从一个map构造出一个 User实例 map structure
//一个toJson 方法, 将 User 实例转化为一个map.

  • json_serializable第一次创建类时,您会看到与下图类似的错误。
jsonError
#一次性生成
flutter packages pub run build_runner build

#持续生成
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关键字来声明一个异步方法,在异步方法中可以使用await表达式挂起该异步方法中的某些步骤,从而实现等待某步骤完成的目的; Future是Dart中提供的一个类,它用于封装一段在将来会被执行的代码逻辑。

Note

  • async修饰的异步方法需要声明返回一个Future类型,如果方法体内没有主动的返回一个Future类型,系统会将返回值包含到一个Future中返回。

  • await表达式的表达式部分需要返回一个Future对象。

  • await表达式需要在一个async修饰的方法中使用才会生效。 关于async和await的更多详情可以参阅官方文档

end

推荐

flutter 中文

flutter English

flutter github

Dart Packages

An open list of apps built with Flutter

完整项目 Flutter / RN / Kotlin / Weex

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272