学习了布局实例和交互后,算是对Flutter
入门了,基本能够实现一些普通页面搭建和交互效果了。可是这远远还不够,如今App
都是须要网络访问的,而今天的目标就是学习IO
和网络这一块。java
Dart
是单线程模型,什么是单线程模型呢?单线程就是在程序执行时,所走的程序路径按照连续顺序排列下来,前面的必须处理好,后面的才会执行(就是同一个时刻只能执行一个操做)。生活中举个例子,在早上上班时,须要指纹打卡,正要打卡的时候,忽然来电话,这时候你接电话,接完电话再打卡,从接电话到打卡这操做就是单线程,也就是说,在接电话的时候,打卡这个操做是阻塞的,得要接完电话才能打卡。什么是异步?在计算机领域中,异步指的是线程不须要一直等待下去,而是继续执行下面的操做,无论其余线程的状态,当由消息返回时系统会通知线程进行处理。就好像在平时生活中,我如今须要煮饭,我并非等着饭煮熟才能去作其余事,而是把电饭锅煮饭按钮按下,而后就能够去看电视,看书等等,等饭好了电饭锅的按钮会跳到保温状态,这时候你就能够吃饭了。这时候我也想到了再Android
中OkHttp
的同步和异步请求:android
可是Flutter
中的异步有些不同,下面慢慢讲述。git
Dart
是单线程模型,并无主线程/子线程之分,Dart
是Event loops
和Event Queue
模型,而EventLooper
将全部的事件依次执行,直接上图:github
Dart
中的
Event
处理模式,当产生一个
Event
以后,会进入
Event queue
,而
Event loop
从
EventQueue
中获取
Event
而且处理。
当一个Dart
函数开始执行,那么它就会执行到这个函数结束,也就是函数不会被其余代码所打断。这里首先解释一下什么是Dart
中的isolate
。isolate
自己是隔离的意思,有本身的内存和单线程控制的实体,由于isolate
之间的内存在逻辑是隔离的,isolate
的代码是按顺序执行的。在Dart
中并发可使用用isolate
,isolate
和Thread
很像,可是isolate
之间没有共享内存。一个Dart
程序是在Main isolate
的Main函数开始,咱们平时开发中,默认环境就是Main isolate
,App的启动入口main
函数就是一个isolate
,在Main函数结束后,Main isolate
线程开始一个一个处理Event Queue
中的每个Event
。web
一个Dart
Main isolate只有一个消息循环(Event Looper)和两个消息队列:Event队列和MicroTask队列。sql
Dart
中是颇有必要的,由于有时候事件处理想要在稍后完成一些任务但又但愿是在执行下一个事件消息以前。Event队列包含Dart
和系统中其余位置的事件,MicroTask只包含Dart
的代码,那么Event Looper处理两个队列的顺序是以下图,当main
方法退出后,Event Looper就开始它的工做,首先会以FIFO的顺序执行MicroTask(先执行简短的异步任务),当全部的microtask执行完就会从Event队列去提取事件执行,这样反复,直到两个队列都是空。数据库
new Future(() => futureTask) //异步任务的函数
.then((d) => "execute value:$d") //任务执行完后的子任务
.then((d) => d.length) //其中d为上个任务执行完后的返回的结果
.then((d) => printLength(d))
.whenComplete(() => whenTaskCompelete); //当全部任务完成后的回调函数
}
复制代码
能够看到,上述代码明确表示先后的依赖关系,可使用then()()
来代表要使用变量就必需要等设置完这个变量。还可使用whenComplete()
,异步完成时的回调。json
使用new Future
或者new Future.delayed()
来向Event队列添加事件,也就是说Future操做是经过Event队列来处理,以下面代码:api
//向event队列中添加一个任务
new Future(() {
//具体任务
});
复制代码
想要在两秒后将任务添加到Event队列数组
// 两秒之后将任务添加至event队列
new Future.delayed(const Duration(seconds:2), () {
//任务具体代码
});
复制代码
由于上面说过,上面这个任务想要执行必须知足main
方法执行完,Misrotask队列是空的,这个任务以前的任务须要执行完,因此这个任务被执行有可能大于2秒。
scheduleMicrotask(() {
// 具体逻辑
});
复制代码
上面就是将一个任务加到MicroTask队列中去。
import 'dart:async';
main() {
print('main #1 of 2');
scheduleMicrotask(() => print('microtask #1 of 2'));
new Future.delayed(new Duration(seconds:1),
() => print('future #1 (delayed)'));
new Future(() => print('future #2 of 3'));
new Future(() => print('future #3 of 3'));
scheduleMicrotask(() => print('microtask #2 of 2'));
print('main #2 of 2');
}
复制代码
输出结果:
main #1 of 2
main #2 of 2
microtask #1 of 2
microtask #2 of 2
future #2 of 3
future #3 of 3
future #1 (delayed)
复制代码
上面执行顺序:main方法 ->Microtask队列->Event队列(先 new Future 后new Future.delay),下面直接拿官方的例子实践一下:
import 'dart:async';
main() {
print('main #1 of 2');
scheduleMicrotask(() => print('microtask #1 of 3'));
new Future.delayed(new Duration(seconds:1),
() => print('future #1 (delayed)'));
new Future(() => print('future #2 of 4'))
.then((_) => print('future #2a'))
.then((_) {
print('future #2b');
scheduleMicrotask(() => print('microtask #0 (from future #2b)'));
})
.then((_) => print('future #2c'));
scheduleMicrotask(() => print('microtask #2 of 3'));
new Future(() => print('future #3 of 4'))
.then((_) => new Future(
() => print('future #3a (a new future)')))
.then((_) => print('future #3b'));
new Future(() => print('future #4 of 4'));
scheduleMicrotask(() => print('microtask #3 of 3'));
print('main #2 of 2');
}
复制代码
输出结果:
main #1 of 2
main #2 of 2
microtask #1 of 3
microtask #2 of 3
microtask #3 of 3
future #2 of 4
future #2a
future #2b
future #2c
microtask #0 (from future #2b)
future #3 of 4
future #4 of 4
future #3a (a new future)
future #3b
future #1 (delayed)
复制代码
上面两个小例子会加深对事件消息的理解。
由于Dart
是单线程语言,当遇到延迟的运算(I/O操做),线程中顺序执行的运算就会阻塞,那就app上,用户操做就会感到卡顿,因而一般用异步处理来解决这个问题,当遇到须要延迟的运算时,就会放入延迟运算的队列中,先把不须要延迟的运算先执行,最后再来处理延迟运算。Dart
类库有很是多的返回Future
或者Stream
对象的函数,这些函数被称为异步函数;它们会在设置好一些须要消耗必定时间的操做以后返回,好比I/O操做,而不是等到这个操做完成。
什么是Future
,顾名思义,表示一件未来会发生的事情(也就是不会当即执行),未来能够从Future
中取到一个值,当一个方法返回一个Future
的事情,发生两件事:
Future
。Future
的状态会变成已经完成,这个时候能够取到这件事情的返回值。Future
表示一个异步操做的最终完成(或失败)及其结果值的表示,简单来讲,它就是用来处理异步操做的,异步处理成功就执行成功的操做,异步处理失败就捕获错误或者中止后续操做,一个Future
只会对应一个结果,要么成功,要么失败。
main() {
create();
}
//模执行延时任务
void create(){
//延迟三秒执行
Future.delayed(new Duration(seconds: 3),(){
return "This is data";
}).then((data){
print(data);
});
}
复制代码
输出结果以下:
This is data
复制代码
上面能够发现,使用Future.delayed
建立一个延时任务,当三秒后经过then
data接收了这个所返回的This is data
这个字符串的值。下面读取一个文件,先建立一个文件:
main() {
create();
}
void create(){
//延迟三秒执行
var file = File("/Users/luguian/Downloads/flutter第五天/flutter.rtf");
//定义了返回结果值为String类型
Future<String> data = file.readAsString();
//返回文件内容
data.then((text){
//打印文件内容
print(text);
});
print("I love Android");
}
复制代码
I love Android -->先打印I love Android
{\rtf1\ansi\ansicpg936\cocoartf1561\cocoasubrtf600
{\fonttbl\f0\fswiss\fcharset0 Helvetica;\f1\fnil\fcharset134 PingFangSC-Regular;}
{\colortbl;\red255\green255\blue255;}
{\*\expandedcolortbl;;}
\paperw11900\paperh16840\margl1440\margr1440\vieww10800\viewh8400\viewkind0
\pard\tx566\tx1133\tx1700\tx2083\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural\partightenfactor0
\f0\fs24 \cf0 I love flutter --->文字内容
\f1 ;}
复制代码
能够发现,先输出I love Android
,而后读取文件这种超时的操做会后执行,也就是读取文件内容是未来执行的,then
接收异步并打印出结果。注意:Future并非并行执行的。
当异步任务发生错误,能够在catchError
中捕获错误,例子以下:
Future.delayed(new Duration(seconds: 3),(){
throw AssertionError("This is a Error");
}).then((data){
//这是成功的逻辑
print("success");
}).catchError((e){
//失败会走到这里
print(e);
});
复制代码
输出结果以下:
Assertion failed
复制代码
能够发现,在异步任务中抛出了一个异常,then
的回调函数不会执行,反而catchError
函数被调用,固然并非只有catchError
才能捕获错误,then
方法有一个可选的参数onError
,能够用它来捕获异常:
Future.delayed(new Duration(seconds: 3),(){
throw AssertionError("This is a Error");
}).then((data){
//这是成功的逻辑
print("success");
},onError:(e){
print(e);
});
复制代码
输出结果:
Assertion failed
复制代码
有不少时候,当异步任务不管成功或者失败都须要作一些事的场景,如在网络请求前弹出加载进度框,在请求结束后关闭进度框,下面用whenComplete
进行回调,例子以下:
Future.delayed(new Duration(seconds: 3),(){
throw AssertionError("This is a Error");
}).then((data){
//这是成功的逻辑
print("success");
},onError:(e){
//这是失败的逻辑
print(e);
}).whenComplete((){
print("不管失败,或者成功都会走到这");
});
}
复制代码
输出结果以下:
Assertion failed
不管失败,或者成功都会走到这
复制代码
有时候,须要等待多个异步任务都执行结束后才进行一些操做,若有一个界面,须要从两个接口获取数据,获取成功后,将两个数据进行处理后显示在UI界面上,这时候,Future.wait
派出用上了,它接收一个Future
数组参数,只有数组中全部的Future
执行成功后,就会触发then
回调,固然,只要有一个Future
执行失败就会触发错误回调,下面实现一下当两个异步任务都执行成功时,将结果打印出来:
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
函数。
使用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 Future
I love Android
复制代码
能够发现,先输出的是I love Future
后面再输出I love Android
,能够发现当函数被async
修饰时,会先去执行下面的操做,当下面的操做执行完,而后再执行被async
修饰的方法。async
用来表示函数是异步的,定义的函数会返回一个Future
对象,await
后面是一个Future
,表示等待该异步任务完成,异步完成后才会往下走。要注意如下几点:
下面再上例子:
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结束
复制代码
也就是首先执行_startMethod
这个方法用async声明了,由于方法里调用了_method_A
,因此先输出print("A开始执行这个方法~");,后面执行_method_B()
,这个方法用await关键字声明,因此会暂停print("start结束");的执行,而后继续执行_method_B()
将 print("B开始执行这个方法~");输出,下一行遇到await关键字,会暂停其余代码的执行。当await关键字引用的Future执行完成(也就是执行print("后面执行这句话~"),_method_C()
方法会当即执行,而后执行继续执行这句哈11111~,最后执行print("start结束");
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
插件提供一种平台透明的方式来访问设备文件系统上的经常使用位置。该类当前支持两个文件系统位置:
NSTemporaryDirectory()
返回的值。在Android上,这是getCacheDir()
返回的值。NSDocumentDirectory
。在Android上,这是AppData
目录。在Flutter
里实现文件读写,须要使用path_provider
和Dart
里的I/O
模块,二者的职责并不同,path_provider
是负责查找iOS或者Android下的目录文件,而I/O
是负责文件的读写操做。
下面使用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>{
@override
void initState(){
super.initState();
}
@override
Widget 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/cache
I/flutter (19375): 文档目录:/data/user/0/com.example.loadflie/app_flutter
I/flutter (19375): sd卡目录:/storage/emulated/0
复制代码
读取文件少不了权限的问题,在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)
复制代码
//把内容写入文件操做
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);
复制代码
运行结果:
Android
和iOS
中都会有SQLite
,那么Flutter
有没有呢?答案是确定有的。Flutter
中的SQLite
数据库是同时支持Android
和iOS
的,它的名字叫sqflite
,支持事务和批量操做,支持插入/查询/更新/删除操做等,是轻量级的关系型数据库。 下面先简单实现一个登陆界面,进行简单的数据操做:
//用无状态控件显示
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
//主题色
theme: ThemeData(
//设置为蓝色
primarySwatch: Colors.red),
//这是一个Widget对象,用来定义当前应用打开的时候,所显示的界面
home: DataBaseWidget(),
);
}
}
//主框架
class DataBaseWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return new _DataBaseState();
}
}
class _DataBaseState extends State<DataBaseWidget> {
@override
Widget build(BuildContext context) {
return new Scaffold(
//appBar
appBar: AppBar(
title: Text("Sqlite简单操做"),
//标题居中
centerTitle: true,
),
body: new ListView(
children: <Widget>[
//用户输入用户信息widget
Padding(
padding: const EdgeInsets.only(left: 16, right: 16),
child: InputMessageWidget(),
),
//数据库表的一些基本操做,增,删,改,查
Padding(
padding: const EdgeInsets.all(16),
child: SqliteHandleWidget(),
),
],
),
);
}
}
复制代码
用户输入信息的Widget
如何:
//用户名和密码
class InputMessageWidget extends StatelessWidget {
@override
Widget 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 {
@override
State<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;
@override
Widget 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
语句很简单,就三个字段,分别是主键,用户名,用户密码,界面以下:
首先添加依赖:能够到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
文件,确实数据库建立成功了,那继续下面的操做。
下面实现增长数据,能够用rawInsert
或者db.insert
方式对数据库表数据进行增长(插入),实际上都是经过insert into
方式来插入数据表,下面就用rawInsert
方式来增长一条数据:
//增长方法
addData() async {
//首先打开数据库
Database my_db = await openDatabase(myDataBasePath);
//插入数据
String add_sql = "INSERT INTO user(username,password) VALUES('$username','$password')";
await my_db.transaction((tran) async{
await tran.rawInsert(add_sql);
});
//关闭数据库
await my_db.close();
setState(() {
_data = "增长一条数据成功,名字是:$username,密码是:$password";
});
}
复制代码
为了配合增长数据,把查询数据库表的功能实现:
//查询具体数值
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),
复制代码
验证结果,流程是:
下面实现删除数据:
//删除一条数据
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 = "删除失败,请看错误日志~";
}
});
}
复制代码
记得给删除按钮绑定方法,运行结果就不贴了。
修改数据我相信在平时开发中是用的最频繁的操做了,直接上实现例子:
//修改数据方法
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
。
//查询有几条
query_num() async{
//数据库
Database my_db = await openDatabase(myDataBasePath);
//用sqflite包的方法firstInValue
int 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
就等后面用到在学。下面就实践:
import 'dart:io';//导IO包
import 'dart:convert';//解码和编码JSON
void main() {
_get();
}
_get() async{
var responseBody;
//1.建立HttpClient
var httpClient = new HttpClient();
//2.构造Uri
var 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");
}
}
复制代码
结果以下:
_post() async{
var responseBody;
//1.建立HttpClient
var httpClient = new HttpClient();
//2.构造Uri
var 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");
}
}
复制代码
返回结果以下:
dio是一个强大的Dart Http
请求库,支持Restful API
、FormData
、拦截器、错误处理、转换器、设置Http代理、请求取消、Cookie
管理、文件上传和下载、超时等。在pub.flutter-io.cn/packages搜最新的依赖包,这个网址太好用,你想搜一些三方库里面都有:
pubspec.yaml
添加依赖:
dio: ^2.0.14
复制代码
导入依赖:
import 'package:dio/dio.dart';
复制代码
//dio get请求
dio_get() async{
try{
Response response;
//等待返回response
response = 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);
}
}
复制代码
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的。
继续去上面连接搜最新的包,是http 0.12.0+1
,在pubspec.yaml
下添加依赖,在文件导入包:
import 'package:http/http.dart' as my_http;
复制代码
上面此次导入库的方式有一点点区别,多了as
这个关键字,这是什么意思呢?经过as
是为了解决变量名冲突的方法,由于导入不一样的库有可能遇到不一样库之间由于导入变量名冲突的问题。
//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);
}
}
复制代码
//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
,在Flutter
中是没有GSON/Jackson/Moshi
这些库,由于这些库须要运行时反射,在Flutter
是禁用的。运行时反射会干扰Dart
的_tree shaking_。使用_tree shaking_,能够在发版时"去除"未使用的代码,来优化软件的大小。因为反射会默认使用全部代码,所以_tree shaking_会很难工做,这些工具没法知道哪些widget
在运行时未被使用,所以冗余代码很难剥离,使用反射时,应用尺寸没法轻松进行优化,虽然不能在Flutter
使用运行时反射,但有些库提供了类型简单易用的API
,但它们是基于代码生成的。下面学学在Flutter
中如何操做JSON
数据的使用JSON
有两个常规策略:
JSON model
的复杂应用程序,手动序列化可能会比较繁琐,且容易出错。Flutter
中基本的JSON序列化很是简单,Flutter
有一个内置的dart:convert
库,其中包含一个简单的JSON解码器和编码器。下面简单实现一下:
首先记得导库:
import 'dart:convert';
复制代码
而后根据字符串解析:
//内连序列化JSON
decodeJson() {
var data= '{"name": "Knight","email": "Knight@163.com"}';
Map<String,dynamic> user = json.decode(data);
//输出名字
print("Hello,my name is ${user['name']}");
//输出邮箱
print("Hello,This is my email ${user['email']}");
}
复制代码
结果输出:
I/flutter ( 5866): Hello,my name is Knight
I/flutter ( 5866): Hello,This is my email Knight@163.com
复制代码
这样,能够得到咱们想要的数据了,我以为这种方法很实用又能简单理解,可是不幸的是,JSON.decode()
仅返回一个Map<String,dynamci>
,这意味着当直到运行才知道值的类型,这种方法会失去大部分静态类型语言特性:类型安全、自动补全和编译时异常。这样的话,代码变得很是容易出错,就好像上面咱们访问name
字段,打字打错了,打成namr
。可是这个JSON
在map结构中,编译器不知道这个错误的字段名(编译时不会报错)。为了解决所说的问题,模型类中序列化JSON的做用出来了。
经过引入一个简单的模型类(model class)来解决前面提到的问题,创建一个User
类,在类内部有两个方法:
User.fromJson
构造函数,用于从一个map构造出一个User
实例map structuretoJson
方法,将User
实例化一个map 这样调用的代码就具备类型安全、自动补全和编译时异常,当拼写错误或字段类型视为其余类型,程序不会经过编译,那就避免运行时崩溃。新建一个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,
};
}
复制代码
调用以下:
import 'model/User.dart';//记得添加
....
//使用模型类反序列化
decodeModelJson(){
var data= '{"name": "Knight","email": "Knight@163.com"}';
Map userMap = json.decode(data);
var user = new User.fromJson(userMap);
//打印出名字
print("Hello,my name is ${user.name}");
//打印出邮箱
print("Hello,my name is ${user.email}");
}
复制代码
把序列化逻辑到移到模型自己内部,采用这种方法,反序列化数据就很简单了。序列化一个user,只是将User
对象传递给该JSON.encode
方法:
//序列化一个user
encodeModelJson(){
var user = new User("Knight","Knight163.com");
String user_json = json.encode(user);
print(user_json);
}
复制代码
结果输出:
I/flutter ( 6684): {"name":"Knight","email":"Knight163.com"}
复制代码
下面使用json_serializable package
包,它是一个自动化的源代码生成器,能够为开发者生成JSON序列化模板。
要包含json_serializable
到项目中,须要一个常规和两个开发依赖项,开发依赖项是不包含在应用程序源代码中的依赖项:
dependencies:
# Your other regular dependencies here
json_annotation: ^2.0.0
dev_dependencies:-->开发依赖项
# Your other dev_dependencies here
build_runner: ^1.1.3 -->最新版本1.2.8 由于我sdk版本比较低 因此用低版本
json_serializable: ^2.0.2
复制代码
有两种运行代码生成器的方法:
flutter packages pub run build_runner build
,能够在须要为咱们的model
生成json
序列化代码。这触发一次性构建,它经过源文件,挑选相关的并为它们生成必要的序列化代码。这个很是方便,可是若是咱们不须要每次在model类中进行更改都要手动运行构建命令的话会更好。flutter packages pub run build_runner watch
在项目根目录运行启动_watcher_,只需启动一次观察器,而后并让它在后台运行,这是安全的。将上面的User.dart
修改为下面:
import 'package:json_annotation/json_annotation.dart';
part 'User.g.dart';-->一开始爆红
//这个标注是告诉生成器,这个类是须要生成Model类的
@JsonSerializable()
class User{
User(this.name, this.email);
String name;
String email;
factory User.fromJson(Map<String, dynamic> json){--->一开始爆红
return _$UserFromJson(json);
}
Map<String, dynamic> toJson() { --->一开始爆红
return _$UserToJson(this);
}
}
复制代码
下面就用一次性生成命令,在项目根目录打开命令行执行:
User.g.dart
文件:
User.g.dart
执行多几回命令便可。 最后经过
json_serializable
方式反序列化
JSON
字符串,不须要对先前代码修改:
var data= '{"name": "Knight","email": "Knight@163.com"}';
Map userMap = json.decode(data);
var user = new User.fromJson(userMap);
//打印出名字
print("Hello,my name is ${user.name}");
//打印出邮箱
print("Hello,my name is ${user.email}");
复制代码
var user = new User("Knight","Knight163.com");
String user_json = json.encode(user);
print(user_json);
复制代码
结果是跟上面同样,不过这种方式额外多了生成一个文件...
下面实现一个简单例子,效果图以下:
{
"error": false,
"results": [{
"_id": "5c6a4ae99d212226776d3256",
"createdAt": "2019-02-18T06:04:25.571Z",
"desc": "2019-02-18",
"publishedAt": "2019-02-18T06:05:41.975Z",
"source": "web",
"type": "\u798f\u5229",
"url": "https://ws1.sinaimg.cn/large/0065oQSqly1g0ajj4h6ndj30sg11xdmj.jpg",
"used": true,
"who": "lijinshanmx"
}, {
"_id": "5c6385b39d21225dd7a417ce",
"createdAt": "2019-02-13T02:49:23.946Z",
"desc": "2019-02-13",
"publishedAt": "2019-02-13T02:49:33.16Z",
"source": "web",
"type": "\u798f\u5229",
"url": "https://ws1.sinaimg.cn/large/0065oQSqly1g04lsmmadlj31221vowz7.jpg",
"used": true,
"who": "lijinshanmx"
}]
}
复制代码
上面是一个内嵌数组,须要增长两个实体类,以下: ViewResult类以下:
import 'ResultModel.dart';
class ViewResult{
bool error;
List<ResultModel> list;
ViewResult(joinData){
//得到返回的error值
error = joinData['error'];
list = [];
print(joinData['results']);
//得到"results"里的内容
if(joinData['results'] != null){
for(var dataItem in joinData['results']){
list.add(new ResultModel(dataItem));
}
}
}
复制代码
ResultModel类以下:
class ResultModel{
String _id;
String createdAt;
String desc;
String publishedAt;
String source;
String type;
String url;
bool used;
String who;
ResultModel(jsonData){
_id = jsonData['_id'];
createdAt = jsonData['createdAt'];
desc = jsonData['desc'];
publishedAt = jsonData['publishedAt'];
source = jsonData['source'];
type = jsonData['type'];
url = jsonData['url'];
used = jsonData['used'];
who = jsonData['who'];
}
}
复制代码
ListView的Item布局:
//须要传list 和对应下标
Widget photoWidget(List<ResultModel> resultLists,int index){
return Card(
child: Container(
height: 300,
child: Row(
children: <Widget>[
Expanded(
child: ClipRRect(
borderRadius: BorderRadius.circular(4.0),
child: Image.network(resultLists[index].url,
fit:BoxFit.fitWidth,
//scale: 2.5,
),
),
),
],
),
),
);
}
复制代码
全部代码以下:
import 'package:flutter/material.dart';
import 'dart:convert';//解码和编码JSON
import 'package:http/http.dart' as my_http;
import 'model/ViewResult.dart';
import 'model/ResultModel.dart';
//app入口
void main() {
runApp(MyApp());
}
//用无状态控件显示
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
//主题色
theme: ThemeData(
//设置为蓝色
primarySwatch: Colors.red),
//这是一个Widget对象,用来定义当前应用打开的时候,所显示的界面
home: BigPhotoWidget(),
);
}
}
//主框架
class BigPhotoWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return new _BigPhotoState();
}
}
class _BigPhotoState extends State<BigPhotoWidget> {
ViewResult viewresult;
//具体的数据集合
List<ResultModel> resultLists = [];
@override
void initState(){
super.initState();
getData();
}
getData() async{
try{
//由于导入http 用了as xxx方式,因此对象请求都用xxx.get方式
//方式一
// await my_http.get("http://gank.io/api/data/福利/10/1")
// .then((response){
// if(response.statusCode == 200){
// var ViewData = json.decode(response.body);
// viewresult = ViewResult(ViewData);
// if(!viewresult.error){
// //继续解析
// for(int i = 0;i < viewresult.list.length;i++){
// resultLists.add(viewresult.list[i]);
// }
// //记得调用刷新
// setState(() {
//
// });
// }
// }else{
// print("error");
// }
// });
//方式二 请求
var response = await my_http.get("http://gank.io/api/data/福利/10/1");
//判断状态
if(response.statusCode == 200){
//解析
var ViewData = json.decode(response.body);
viewresult = ViewResult(ViewData);
if(!viewresult.error){
//继续解析
for(int i = 0;i < viewresult.list.length;i++){
resultLists.add(viewresult.list[i]);
}
//记得调用刷新
setState(() {
});
}
}else{
print("error");
}
}catch(e){
print(e);
}
}
@override
Widget build(BuildContext context) {
return new Scaffold(
//appBar
appBar: AppBar(
title: Text("妹子图"),
//标题居中
centerTitle: true,
),
body: ListView.builder(
itemCount: resultLists.length,
itemBuilder: (BuildContext context,int index){
return Column(
children: <Widget>[
photoWidget(resultLists,index),
],
);
},
),
);
}
}
//须要传list 和对应下标
Widget photoWidget(List<ResultModel> resultLists,int index){
return Card(
child: Container(
height: 300,
child: Row(
children: <Widget>[
Expanded(
child: ClipRRect(
borderRadius: BorderRadius.circular(4.0),
child: Image.network(resultLists[index].url,
fit:BoxFit.fitWidth,
//scale: 2.5,
),
),
),
],
),
),
);
}
复制代码
上面获取数据有两种方式。
做为萌新,确定有不少技术没学到位,若有错误,欢迎指出指正,谢谢~