Flutter 学习之事件循环机制、数据库、网络请求,kotlin 开源项目实战
Future.wait([//3 秒后返回结果 Future.delayed(new Duration(seconds: 3),(){return "Android";}),//4 秒后返回结果 Future.delayed(new Duration(seconds: 4),(){return " And Future";})]).then((data){//成功逻辑 print(data[0] + data[1]);}).catchError((e){//捕捉错误 print(e);});}
输出结果如下:
Android And Future
可以看到当两个异步任务完成才会回调then
函数。
2.Async/await
使用Async/await
也是可以实现异步操作,下面直接上例子:
main() {create();}void create(){String data = getData();print(data);print("I love Future");}getData() async{return await "I love Android";}
运行上面代码,报错了:
type 'Future<dynamic>' is not a subtype of type 'String'
报的是类型不匹配?为什么呢?经过一番搜查,发现getData
是一个异步操作函数,它的返回值是一个await
延迟执行的结果。在Dart
中,有await
标记的运算,其结果值是一个Future
对象,Future
并不是 String 类型,就报错了。那么怎么才正确获得异步的结果呢?Dart 规定 async 标记的函数,只能由 await 来调用,下面改成这样:
main() {create();}
void create() async{String data = await getData();print(data);print("I love Future");
}getData() async{return await "I love Android";}
下面直接去掉async
函数包装,直接在getData
方法里对data
进行赋值:
String data;
main() {create();}
void create(){getData();print("I love Future");
}getData() async{data = await "I love Android";print(data);}
上面输出结果是:
I love FutureI love Android
可以发现,先输出的是I love Future
后面再输出I love Android
,可以发现当函数被async
修饰时,会先去执行下面的操作,当下面的操作执行完,然后再执行被async
修饰的方法。async
用来表示函数是异步的,定义的函数会返回一个Future
对象,await
后面是一个Future
,表示等待该异步任务完成,异步完成后才会往下走。要注意以下几点:
await 关键字必须在 async 函数内部使用,也就是加 await 不加 async 会报错。
调用 async 函数必须使用 await 关键字,如果加 async 不加 await 会顺序执行代码。
下面再上例子:
main() {_startMethod();_method_C();
}
_startMethod() async{_method_A();await _method_B();print("start 结束");}_method_A(){print("A 开始执行这个方法~");
}
_method_B() async {print("B 开始执行这个方法~");await print("后面执行这句话~");print("继续执行这句哈 11111~");}
_method_C(){print("C 开始");}
结果如下:
A 开始执行这个方法~B 开始执行这个方法~后面执行这句话~C 开始继续执行这句哈 11111~start 结束
当使用 async 作为方法名后缀声明时,说明这个方法的返回值是一个 Future;
当执行到该方法代码用 await 关键字标注时,会暂停该方法其他部分执行;
当 await 关键字引用的 Future 执行完成,下一行代码会立即执行。
也就是首先执行_startMethod
这个方法用 async 声明了,因为方法里调用了_method_A
,所以先输出 print("A 开始执行这个方法~");,后面执行_method_B()
,这个方法用 await 关键字声明,所以会暂停 print("start 结束");的执行,然后继续执行_method_B()
将 print("B 开始执行这个方法~");输出,下一行遇到 await 关键字,会暂停其他代码的执行。当 await 关键字引用的 Future 执行完成(也就是执行 print("后面执行这句话~"),_method_C()
方法会立即执行,然后执行继续执行这句哈 11111~,最后执行 print("start 结束");
3.Stream
Stram
是接收异步事件数据,和Future
不同的是,它可以接收多个异步操作的结果,那么Stram
常用于在多次读取数据的异步任务场景,直接上例子:
void create(){
Stream.fromFutures([//2 秒后返回结果 Future.delayed(new Duration(seconds: 2),(){return "Android";}),
//3 秒后抛出一个异常 Future.delayed(new Duration(seconds: 3),(){return AssertionError("error");}),
//4 秒后返回结果 Future.delayed(new Duration(seconds: 4),(){return "Flutter";})
]).listen((result){//打印接收的结果 print(result);},onError: (e){//错误回调 print(e.message);
},onDone: (){
});
}
上面可以发现Stream
可以通过触发成功或者失败传递结果或者错误。
四、文件操作
有很多时候需要将文件保存到本地,这时候就需要用文件读写接口来实现,PathProvider
插件提供一种平台透明的方式来访问设备文件系统上的常用位置。该类当前支持两个文件系统位置:
临时目录:系统可随时清除的临时目录(缓存)。在 iOS 上,这对应于
NSTemporaryDirectory()
返回的值。在 Android 上,这是getCacheDir()
返回的值。文档目录:应用程序的目录,用于存储只有自己可以访问的文件,只有当应用程序被卸载时,系统才会清除目录。在 iOS 上,这对应于
NSDocumentDirectory
。在 Android 上,这是AppData
目录。
在Flutter
里实现文件读写,需要使用path_provider
和Dart
里的I/O
模块,两者的职责并不一样,path_provider
是负责查找 iOS 或者 Android 下的目录文件,而I/O
是负责文件的读写操作。
1.获取本地路径
下面使用path_provider
来查找本地的路径,首先在pubspec.xml
文件添加依赖:
dependencies:flutter:sdk: flutter
The following adds the Cupertino Icons font to your application.
Use with the CupertinoIcons class for iOS style icons.
path_provider: ^0.4.1 -->添加依赖
或者临时目录,文档目录,sd 卡目录如下:
import 'dart:io';import 'dart:async';import 'package:flutter/material.dart';import 'package:path_provider/path_provider.dart';....class _LoadFileState extends State<LoadFile>{@overridevoid initState(){super.initState();}
@overrideWidget build(BuildContext context){return new Scaffold(appBar: new AppBar(title: new Text("LoadFile"),),body: new Center(child: RaisedButton(child: Text("获取文件路径"),//点击调用获取文件路径方法 onPressed: loadPath,),),);}}
loadPath() async{try{//临时目录 var _tempDir = await getTemporaryDirectory();//获取具体路径 String tempDirPath = _tempDir.path;//文档目录 var _document = await getApplicationDocumentsDirectory();String documentPath = _document.path;//sd 卡目录 var _sdCard = await getExternalStorageDirectory();String sdCardPath = _sdCard.path;
//打印路径 print("临时目录:"+ tempDirPath);print("文档目录:"+ documentPath);print("sd 卡目录:"+ sdCardPath);
}catch(err){print(err);}}
输出结果(Android)如下:
I/flutter (19375): 临时目录:/data/user/0/com.example.loadflie/cacheI/flutter (19375): 文档目录:/data/user/0/com.example.loadflie/app_flutterI/flutter (19375): sd 卡目录:/storage/emulated/0
2.读取本地文件内容
读取文件少不了权限的问题,在[Dart Packages](
)可以找到simple_permissions
这个库来简化申请权限的步骤,按照上面说明跟着操作就可以:
上面的意思是在AndroidManifest
和Info.plist
文件下添加权限,身为 Android coder 对AndroidManifest
这个文件很熟悉,这个文件是对 Android 而言,而Info.plist
应该是对于iOS
而言,那下面先在 Android 上试试看,首先,在pubspec.yaml
上添加依赖:
simple_permissions: ^0.1.9
记得点击Packages get
命令。 接着在AndroidManifest
清单文件上添加对文件的读写权限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
下面在手机 sd card 内部存储新建一个 txt 文件,尝试获取其内容:
import 'package:simple_permissions/simple_permissions.dart';//记得加上这句话...//读取文件方法 readData() async {try {//申请读文件的权限 var permission =SimplePermissions.requestPermission(Permission.ReadExternalStorage);var sdCardPath = getExternalStorageDirectory();//当获取到路径的时候 sdCardPath.then((filePath) {//获得读取文件权限 permission.then((permission_status) async {//获取文件内容 var data = await File(filePath.path + "/flutter.txt").readAsString();print(data);});});} catch (e) {print(e);}}
按钮点击方法改为readData
:
child: RaisedButton(child: Text("获取文件路径"),onPressed: readData),
点击按钮结果运行:
选择始终运行: 输出就是flutter.txt
文件内容:
I/flutter (24038): flutter is very good.
注意如果不加读写权限,会抛出异常:
I/flutter (25428): FileSystemException: Cannot open file, path = '/storage/emulated/0/flutter.txt' (OS Error: Permission denied, errno = 13)
3.写入文件操作
//把内容写入文件操作 writeData() async{try {//申请读文件的权限 var permission =SimplePermissions.requestPermission(Permission.WriteExternalStorage);var sdCardPath = getExternalStorageDirectory();//当获取到路径的时候 sdCardPath.then((filePath) {//获得读取文件权限 permission.then((permission_status) async {//把内容写进文件 var data = await File(filePath.path + "/flutter.txt").writeAsString("点滴之行,看世界");print(data);});});} catch (e) {print(e);}}
打开 sd card 的flutter.txt
文件看看内容:
发现,把之前的内容覆盖了!那么如何实现所写入的内容不覆盖原来文件的内容呢?这时候需要用到append
模式,很简单,把默认的FileMode mode: FileMode.write
方式改为FileMode mode: FileMode.append
,代码如下:
//把内容写进文件 现在以追加的方式 var data = await File(filePath.path + "/flutter.txt").writeAsString("Flutter is very good",mode: FileMode.append);
运行结果:
好了,简单的读写文件就实现了。
五、sqflite 数据库
Android
和iOS
中都会有SQLite
,那么Flutter
有没有呢?答案是肯定有的。Flutter
中的SQLite
数据库是同时支持Android
和iOS
的,它的名字叫sqflite
,支持事务和批量操作,支持插入/查询/更新/删除操作等,是轻量级的关系型数据库。 下面先简单实现一个登录界面,进行简单的数据操作:
//用无状态控件显示 class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return MaterialApp(//主题色 theme: ThemeData(//设置为蓝色 primarySwatch: Colors.red),//这是一个 Widget 对象,用来定义当前应用打开的时候,所显示的界面 home: DataBaseWidget(),);}}
//主框架 class DataBaseWidget extends StatefulWidget {@overrideState<StatefulWidget> createState() {return new _DataBaseState();}}
class _DataBaseState extends State<DataBaseWidget> {@overrideWidget build(BuildContext context) {return new Scaffold(//appBarappBar: AppBar(title: Text("Sqlite 简单操作"),//标题居中 centerTitle: true,),body: new ListView(children: <Widget>[//用户输入用户信息 widgetPadding(padding: const EdgeInsets.only(left: 16, right: 16),child: InputMessageWidget(),),//数据库表的一些基本操作,增,删,改,查 Padding(padding: const EdgeInsets.all(16),child: SqliteHandleWidget(),),],),);}}
用户输入信息的Widget
如何:
//用户名和密码 class InputMessageWidget extends StatelessWidget {@overrideWidget build(BuildContext context) {//这个是为了用户输入结束后,让密码输入框获取到焦点 FocusNode secondTextFieldNode = FocusNode();return Column(children: <Widget>[TextField(//文字内容改变触发 onChanged: (user) {//获取用户名 username = user;},//输入法装饰器 decoration: InputDecoration(//标签 labelText: '名字',//hint 提示用户输入什么 hintText: '请输入英文或者数字'),//最大为一行 maxLines: 1,//文字提交触发 onSubmitted: (result) {FocusScope.of(context).reparentIfNeeded(secondTextFieldNode);},),TextField(onChanged: (pwd) {//获取用户密码 password = pwd;},//是否隐藏输入 false 表示不隐藏,true 表示隐藏 obscureText: true,maxLines: 1,decoration: InputDecoration(labelText: '密码',hintText: '请输入密码',),//键盘输入类型 keyboardType: TextInputType.text,onSubmitted: (data) {},),],);}}
对数据库表操作的按钮布局如下:
//数据库组件操作 class SqliteHandleWidget extends StatefulWidget {@overrideState<StatefulWidget> createState() {return new _SqliteHandleWidgetState();}}
class _SqliteHandleWidgetState extends State<SqliteHandleWidget> {//数据库名称 String myDataBase = "usermessage.db";
//数据库路径 String myDataBasePath = "";
//数据库中的表 简单一点,就创建三个字段,分别是主键,用户名,密码 String sql_createUserTable = "CREATE TABLE user(""id INTEGER PRIMARY KEY,""username TEXT,""password TEXT)";
//查找数据库表的数目 String sql_queryCount = 'SELECT COUNT(*) FROM user';
//具体查找数据库表的所有信息 String sql_queryMessage = 'SELECT * FROM user';
//这是从数据库表返回数据 var _data;
@overrideWidget build(BuildContext context) {return Column(//交叉轴设置中间 crossAxisAlignment: CrossAxisAlignment.center,children: <Widget>[Container(height: 40.0,child: RaisedButton(textColor: Colors.black,child: Text("创建数据库表"),onPressed: null,),),Row(//主轴方向中心对齐 mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[new RaisedButton(textColor: Colors.black,child: new Text('增'),onPressed: null),new RaisedButton(textColor: Colors.black,child: new Text('删'),onPressed: null),new RaisedButton(textColor: Colors.black,child: new Text('改'),onPressed: null),],),Row(//主轴方向中心对齐 mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[new RaisedButton(textColor: Colors.black,child: new Text('查条数'),onPressed: null),new RaisedButton(textColor: Colors.black,child: new Text('查信息'),onPressed: null),],),Padding(padding: const EdgeInsets.all(16.0),child: new Text('具体结果是:$_data'),),],);}}
在上面_SqliteHandleWidgetState
赋值数据库名字为usermessage.db
,创建数据库表user
语句很简单,就三个字段,分别是主键,用户名,用户密码,界面如下:
界面弄好了,下面就一步一步来。
1.创建数据库和数据表
首先添加依赖:可以到 Dart 包管理网站去查找 sqlite 依赖最新版本。
sqflite: ^1.1.0
并在文件引入:
import 'package:path_provider/path_provider.dart';import 'dart:io';import 'package:sqflite/sqflite.dart';import 'package:path/path.dart';
注意:对于数据库的操作都是耗时操作,都要通过异步来处理。
//创建数据库 Future<String> createDataBase(String db_name) async {//在文档目录建立 var document = await getApplicationDocumentsDirectory();//获取路径 join 是 path 包下的方法,就是将两者路径连接起来 String path = join(document.path, db_name);//逻辑是如果数据库存在就把它删除然后创建 var _directory = new Directory(dirname(path));bool exists = await _directory.exists();if (exists) {//必存在 这里是为了每次创建数据库表先表删除则删除数据库表 await deleteDatabase(path);} else {try {//不存在则创建目录 如果[recursive]为 false,则只有路径中的最后一个目录是//创建。如果[recursive]为真,则所有不存在的路径//被创建。如果目录已经存在,则不执行任何操作。await new Directory(dirname(path)).create(recursive: true);} catch (e) {print(e);}}return path;}
//创建数据库表方法 cratedb_table() async {//得到数据库的路径 myDataBasePath = await createDataBase(myDataBase);//打开数据库 Database my_db = await openDatabase(myDataBasePath);//创建数据库表 await my_db.execute(sql_createUserTable);//关闭数据库 await my_db.close();setState(() {_data = "创建 usermessage.db 成功,创建 user 表成功~";});}
给按钮添加点击方法:
child: RaisedButton(textColor: Colors.black,child: Text("创建数据库表"),onPressed: cratedb_table,),
运行,安装完 apk,用Device File Exploder
来看看内部存储文件:
下面点击创建数据库,后synchronize
来刷新一下:
发现在app_flutter
下多了usermessage.db
文件,确实数据库创建成功了,那继续下面的操作。
2.增加数据
下面实现增加数据,可以用rawInsert
或者db.insert
方式对数据库表数据进行增加(插入),实际上都是通过insert into
方式来插入数据表,下面就用rawInsert
方式来增加一条数据:
//增加方法 addData() async {//首先打开数据库 Database my_db = await openDatabase(myDataBasePath);//插入数据 String add_sql = "INSERT INTO user(username,password) VALUES('password')";await my_db.transaction((tran) async{await tran.rawInsert(add_sql);});//关闭数据库 await my_db.close();setState(() {_data = "增加一条数据成功,名字是:password";});}
3.查询具体数据
为了配合增加数据,把查询数据库表的功能实现:
//查询具体数值 queryDetail() async{//打开数据库 Database my_db = await openDatabase(myDataBasePath);//将数据放到集合里面显示 List<Map> dataList = await my_db.rawQuery(sql_queryMessage);await my_db.close();setState(() {_data = "具体数据详情如下:$dataList";});}
查询数据表很简单,实际上只用rawQuery
这个方法,把增加和查询方法绑定到按钮点击上:
new RaisedButton(textColor: Colors.black, child: new Text('改'), onPressed: null),....new RaisedButton(textColor: Colors.black,child: new Text('查信息'),onPressed: queryDetail),
验证结果,流程是:
先输入用户名和密码
点击增加
点击查信息: 运行结果如下:
4.删除数据
下面实现删除数据:
//删除一条数据 delete() async {Database my_db = await openDatabase(myDataBasePath);//根据 id 来删除 也可以根据其他信息来删除 例如名字 String delete_ssql = "DELETE FROM user WHERE id = ?";//返回所更改的数目 int delete_count = await my_db.rawDelete(delete_ssql,['1']);//关闭数据库 await my_db.close();//状态更新 setState(() {if(delete_count == 1){_data = "删除成功~";} else {_data = "删除失败,请看错误日志~";}});}
记得给删除按钮绑定方法,运行结果就不贴了。
5.修改数据
修改数据我相信在平时开发中是用的最频繁的操作了,直接上实现例子:
//修改数据方法 update() async{//数据库 Database my_db = await openDatabase(myDataBasePath);String update_sql = "UPDATE user SET username = ? WHERE id = ?";await my_db.rawUpdate(update_sql,['paul','1']);await my_db.close();
setState(() {_data = "数据修改成功,请查阅~";});
}
上面用了rawUpdate
对数据库表进行内容数据更新,也可以用db.update
来更新,自己可以根据需求变更去修改固定字段或者整条数据。上面我是根据id
这个条件来修改一条数据,将id
为 1 的数据的名字改为paul
。
6.查询条数
//查询有几条 query_num() async{//数据库 Database my_db = await openDatabase(myDataBasePath);//用 sqflite 包的方法 firstInValueint data_count = Sqflite.firstIntValue(await my_db.rawQuery(sql_queryCount));await my_db.close();setState(() {_data = "数据条数:$data_count";});}
对本地数据库的基本操作实现了一遍,下面学习网络请求操作。
六、网络请求操作
Flutter
的请求网络有多种方式,一种是使用dart io
中的HttpClient
发起的请求,一种是使用dio
库,另一种是使用http
库,先学一下get
和post
,put
、delete
就等后面用到在学。下面就实践:
1.dart io 发起的请求
1.1.get 请求
import 'dart:io';//导 IO 包 import 'dart:convert';//解码和编码 JSONvoid main() {_get();}
_get() async{var responseBody;//1.创建 HttpClientvar httpClient = new HttpClient();//2.构造 Urivar requset = await httpClient.getUrl(Uri.parse("http://gank.io/api/data/%E7%A6%8F%E5%88%A9/10/1"));//3.关闭请求,等待响应 var response = await requset.close();//4.进行解码,获取数据 if(response.statusCode == 200){//拿到请求的数据 responseBody = await response.transform(utf8.decoder).join();//先不解析打印数据 print(responseBody);}else{print("error");}
}
结果如下:
1.2.post 请求
_post() async{var responseBody;//1.创建 HttpClientvar httpClient = new HttpClient();//2.构造 Urivar requset = await httpClient.postUrl(Uri.parse("http://www.wanandroid.com/user/login?username=1&password=123456"));//3.关闭请求,等待响应 var response = await requset.close();//4.进行解码,获取数据 if(response.statusCode == 200){//拿到请求的数据 responseBody = await response.transform(utf8.decoder).join();//先不解析打印数据 print(responseBody);}else{print("error");}
}
返回结果如下:
2.dio 请求
[dio](
)是一个强大的Dart Http
请求库,支持Restful API
、FormData
、拦截器、错误处理、转换器、设置 Http 代理、请求取消、Cookie
管理、文件上传和下载、超时等。在[pub.flutter-io.cn/packages](
)搜最新的依赖包,这个网址太好用,你想搜一些三方库里面都有:
在pubspec.yaml
添加依赖:
dio: ^2.0.14
导入依赖:
import 'package:dio/dio.dart';
2.1.get 请求
//dio get 请求 dio_get() async{try{Response response;//等待返回 responseresponse = await Dio().get("http://gank.io/api/data/%E7%A6%8F%E5%88%A9/10/1");if(response.statusCode == 200){print(response);}else{print("error");}}catch(e){print(e);
}
}
2.2.post 请求
dio_post() async{try{Response response;response = await Dio().post("http://www.wanandroid.com/user/login?username=1&password=123456");if(response.statusCode == 200){print(response);}else{print("error");}}catch(e){print(e);}}
效果同样是 ok 的。
3.http 库
继续去上面链接搜最新的包,是http 0.12.0+1
,在pubspec.yaml
下添加依赖,在文件导入包:
import 'package:http/http.dart' as my_http;
上面这次导入库的方式有一点点区别,多了as
这个关键字,这是什么意思呢?通过as
是为了解决变量名冲突的方法,因为导入不同的库有可能遇到不同库之间因为导入变量名冲突的问题。
3.1.get 请求
//http 库的 get 请求方式 http_get() async{try{//因为导入 http 用了 as xxx 方式,所以对象请求都用 xxx.get 方式 var response = await my_http.get("http://gank.io/api/data/%E7%A6%8F%E5%88%A9/10/1");if(response.statusCode == 200){//打印返回的数据 print(response.body);}else{print("error");}}catch(e){print(e);}}
3.2.post 请求
//http 库的 post 请求方式 http_post() async{try{//因为导入 http 用了 as xxx 方式,所以对象请求都用 xxx.get 方式 var response = await my_http.post("http://www.wanandroid.com/user/login?username=1&password=123456");if(response.statusCode == 200){//打印返回的数据 print(response.body);}else{print("error");}}catch(e){print(e);}}
以上三种库的get
和psot
方式都实践了一遍,在平时开发中最好用dio
库和http
库,因为dart io
中是使用HttpClient
发起的请求,HttpClient
本身功能较弱,很多常用功能不支持。
七、JSON
现在很难想象移动应用程序不需要与后台交互或者存储结构化数据。现在开发,数据传输方式基本都是用JSON
,在Flutter
中是没有GSON/Jackson/Moshi
这些库,因为这些库需要运行时反射,在Flutter
是禁用的。运行时反射会干扰Dart
的_tree shaking_。使用_tree shaking_,可以在发版时"去除"未使用的代码,来优化软件的大小。由于反射会默认使用所有代码,因此_tree shaking_会很难工作,这些工具无法知道哪些widget
在运行时未被使用,因此冗余代码很难剥离,使用反射时,应用尺寸无法轻松进行优化,虽然不能在Flutter
使用运行时反射,但有些库提供了类型简单易用的API
,但它们是基于代码生成的。下面学学在Flutter
中如何操作JSON
数据的使用JSON
有两个常规策略:
手动序列化和反序列化
通过代码生成自动序列化和反序列化 不同的项目有不同的复杂度和场景,针对于小的项目,使用代码生成器可能会杀猪用牛刀了。对于具有多个
JSON model
的复杂应用程序,手动序列化可能会比较繁琐,且容易出错。
1.手动序列化 JSON
Flutter
中基本的 JSON 序列化非常简单,Flutter
有一个内置的dart:convert
库,其中包含一个简单的 JSON 解码器和编码器。下面简单实现一下:
1.1.内连序列化 JSON
首先记得导库:
import 'dart:convert';
然后根据字符串解析:
//内连序列化 JSONdecodeJson() {var data= '{"name": "Knight","email": "Knight@163.com"}';Map<String,dynamic> user = json.decode(data);//输出名字 print("Hello,my name is {user['email']}");}
结果输出:
I/flutter ( 5866): Hello,my name is KnightI/flutter ( 5866): Hello,This is my email Knight@163.com
这样,可以获得我们想要的数据了,我觉得这种方法很实用又能简单理解,但是不幸的是,JSON.decode()
仅返回一个Map<String,dynamci>
,这意味着当直到运行才知道值的类型,这种方法会失去大部分静态类型语言特性:类型安全、自动补全和编译时异常。这样的话,代码变得非常容易出错,就好像上面我们访问name
字段,打字打错了,打成namr
。但是这个JSON
在 map 结构中,编译器不知道这个错误的字段名(编译时不会报错)。为了解决所说的问题,模型类中序列化 JSON 的作用出来了。
1.2.模型类中序列化 JSON
通过引入一个简单的模型类(model class)来解决前面提到的问题,建立一个User
类,在类内部有两个方法:
User.fromJson
构造函数,用于从一个 map 构造出一个User
实例 map structuretoJson
方法,将User
实例化一个 map 这样调用的代码就具有类型安全、自动补全和编译时异常,当拼写错误或字段类型视为其他类型,程序不会通过编译,那就避免运行时崩溃。
1.2.1.user.dart
新建一个 model 文件夹,用来放实体,在其文件下新建User.dart
:
class User {final String name;final String email;
User(this.name, this.email);
User.fromJson(Map<String, dynamic> json): name = json['name'],email = json['email'];
Map<String, dynamic> toJson() =>{'name': name,'email': email,};}
评论