Flutter
概要
Flutter是一个由谷歌开发的开源移动应用软件开发工具包。它是以dart为基础做出的一套SDK,支持在Android和iOS上构建APP。简单来说它与RN、Weex等众多混合开发平台框架一样,都是一套代码实现多平台发布的跨平台框架。
Flutter与其他跨平台的最大不同之处是它自建了一个2D渲染引擎.
- Flutter 框架
* Flutter框架可分为Framework层和Engine层;
- Flutter Framework: 整个框架层都是用Dart语言实现,该层提供一套基础库, 用于处理动画、绘图和手势等。并且基于绘图封装了一套 UI组件库,并且细分为两种风格的组件.
1. Materail : Android风格的Widget;
2. Cupertino: IOS风格的Widget;
- Flutter Engine: 这是一个纯 C++实现的框架层,包含了 Skia引擎(高性能渲染引擎)、Dart运行环境、文字排版引擎等。
1. 配置Flutter环境
参考:
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 channelsSamples
- 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 解决,但是当嵌套足够深的话,我们增加非常多可怕的垃圾代码。
随着功能的增加,你的应用程序将会有几十个甚至上百个状态。这个时候应用应该会像下图一样。
这时候,我们便迫切的需要一个架构来帮助我们理清这些关系,状态管理框架应运而生。
state24.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第一次创建类时,您会看到与下图类似的错误。
#一次性生成
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
推荐
网友评论