學習了佈局實例和交互後,算是對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);
複製代碼
結果是跟上面同樣,不過這種方式額外多了生成一個文件...
下面實現一個簡單例子,效果圖以下:
返回的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,
),
),
),
],
),
),
);
}
複製代碼
上面獲取數據有兩種方式。
做爲萌新,確定有不少技術沒學到位,若有錯誤,歡迎指出指正,謝謝~