Flutter學習之事件循環機制、數據庫、網絡請求

1、前言

學習了佈局實例和交互後,算是對Flutter入門了,基本能夠實現一些普通頁面搭建和交互效果了。可是這遠遠還不夠,如今App都是須要網絡訪問的,而今天的目標就是學習IO和網絡這一塊。java

2、Dart中的異步任務消息循環機制

Dart是單線程模型,什麼是單線程模型呢?單線程就是在程序執行時,所走的程序路徑按照連續順序排列下來,前面的必須處理好,後面的纔會執行(就是同一個時刻只能執行一個操做)。生活中舉個例子,在早上上班時,須要指紋打卡,正要打卡的時候,忽然來電話,這時候你接電話,接完電話再打卡,從接電話到打卡這操做就是單線程,也就是說,在接電話的時候,打卡這個操做是阻塞的,得要接完電話才能打卡。什麼是異步?在計算機領域中,異步指的是線程不須要一直等待下去,而是繼續執行下面的操做,無論其餘線程的狀態,當由消息返回時系統會通知線程進行處理。就好像在平時生活中,我如今須要煮飯,我並非等着飯煮熟才能去作其餘事,而是把電飯鍋煮飯按鈕按下,而後就能夠去看電視,看書等等,等飯好了電飯鍋的按鈕會跳到保溫狀態,這時候你就能夠吃飯了。這時候我也想到了再AndroidOkHttp的同步和異步請求:android

  • 同步:向後臺發送一個網絡請求,等待後臺返回數據結果,而後再發送下一個網絡請求
  • 異步:向後臺發送一個網絡請求,不須要等待後臺返回數據,隨時能夠發送下一個網絡請求

可是Flutter中的異步有些不同,下面慢慢講述。git

1.事件循環體系

1.1.Event-Looper

Dart是單線程模型,並無主線程/子線程之分,DartEvent loopsEvent Queue模型,而EventLooper將全部的事件依次執行,直接上圖:github

Events
上圖中很直觀的反映了 Dart中的 Event處理模式,當產生一個 Event以後,會進入 Event queue,而 Event loopEventQueue中獲取 Event而且處理。

1.2.單線程模型

當一個Dart函數開始執行,那麼它就會執行到這個函數結束,也就是函數不會被其餘代碼所打斷。這裏首先解釋一下什麼是Dart中的isolateisolate自己是隔離的意思,有本身的內存和單線程控制的實體,由於isolate之間的內存在邏輯是隔離的,isolate的代碼是按順序執行的。在Dart併發可使用用isolateisolateThread很像,可是isolate之間沒有共享內存。一個Dart程序是在Main isolate的Main函數開始,咱們平時開發中,默認環境就是Main isolate,App的啓動入口main函數就是一個isolate,在Main函數結束後,Main isolate線程開始一個一個處理Event Queue中的每個Eventweb

mian處理方法

1.3.Dart的消息循環和消息隊列

一個DartMain isolate只有一個消息循環(Event Looper)和兩個消息隊列:Event隊列和MicroTask隊列。sql

  1. Event隊列包含全部外來事件:I/O,mouse events(鼠標事件),drawing events(繪圖),timers(計時器),isolate之間的message等
  2. microTask隊列在Dart中是頗有必要的,由於有時候事件處理想要在稍後完成一些任務但又但願是在執行下一個事件消息以前。

Event隊列包含Dart和系統中其餘位置的事件,MicroTask只包含Dart的代碼,那麼Event Looper處理兩個隊列的順序是以下圖,當main方法退出後,Event Looper就開始它的工做,首先會以FIFO的順序執行MicroTask(先執行簡短的異步任務),當全部的microtask執行完就會從Event隊列去提取事件執行,這樣反覆,直到兩個隊列都是空。數據庫

隊列執行順序
這裏要注意:雖然能夠預測知道任務執行順序,可是沒法準確預測事件循環何時處理指望的任務。就好像建立了一個延時2s的任務,可是排在你以前的任務結束前事件處理是不會處理這個延時2s任務,也就是執行這個延時任務有可能大於2s。

1.4.經過連接方式指定任務順序

new Future(() => futureTask)  //異步任務的函數
        .then((d) => "execute value:$d")  //任務執行完後的子任務
        .then((d) => d.length)  //其中d爲上個任務執行完後的返回的結果
        .then((d) => printLength(d))
        .whenComplete(() => whenTaskCompelete);  //當全部任務完成後的回調函數
}
複製代碼

能夠看到,上述代碼明確表示先後的依賴關係,可使用then()()來代表要使用變量就必需要等設置完這個變量。還可使用whenComplete(),異步完成時的回調。json

1.5.Event隊列

使用new Future或者new Future.delayed()來向Event隊列添加事件,也就是說Future操做是經過Event隊列來處理,以下面代碼:api

//向event隊列中添加一個任務
new Future(() {
  //具體任務
});
複製代碼

想要在兩秒後將任務添加到Event隊列數組

// 兩秒之後將任務添加至event隊列
new Future.delayed(const Duration(seconds:2), () {
  //任務具體代碼
});
複製代碼

由於上面說過,上面這個任務想要執行必須知足main方法執行完,Misrotask隊列是空的,這個任務以前的任務須要執行完,因此這個任務被執行有可能大於2秒。

1.6.MicroTask隊列

scheduleMicrotask(() {
  // 具體邏輯
});
複製代碼

上面就是將一個任務加到MicroTask隊列中去。

1.7.例子1

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),下面直接拿官方的例子實踐一下:

1.8.例子2

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)
複製代碼

上面兩個小例子會加深對事件消息的理解。

3、Dart中的異步支持

由於Dart是單線程語言,當遇到延遲的運算(I/O操做),線程中順序執行的運算就會阻塞,那就app上,用戶操做就會感到卡頓,因而一般用異步處理來解決這個問題,當遇到須要延遲的運算時,就會放入延遲運算的隊列中,先把不須要延遲的運算先執行,最後再來處理延遲運算。Dart類庫有很是多的返回Future或者Stream對象的函數,這些函數被稱爲異步函數;它們會在設置好一些須要消耗必定時間的操做以後返回,好比I/O操做,而不是等到這個操做完成。

1.Future

什麼是Future,顧名思義,表示一件未來會發生的事情(也就是不會當即執行),未來能夠從Future中取到一個值,當一個方法返回一個Future的事情,發生兩件事:

  1. 這個方法將某件事情排隊,返回一個未完成的Future
  2. 這個方法事情完畢後,Future的狀態會變成已經完成,這個時候能夠取到這件事情的返回值。

Future表示一個異步操做的最終完成(或失敗)及其結果值的表示,簡單來講,它就是用來處理異步操做的,異步處理成功就執行成功的操做,異步處理失敗就捕獲錯誤或者中止後續操做,一個Future只會對應一個結果,要麼成功,要麼失敗。

1.1.Future.then

main() {
  create();
}

//模執行延時任務
void create(){
   //延遲三秒執行
   Future.delayed(new Duration(seconds: 3),(){
     return "This is data";
   }).then((data){
     print(data);
   });
}
複製代碼

輸出結果以下:

This is data
複製代碼

上面能夠發現,使用Future.delayed建立一個延時任務,當三秒後經過thendata接收了這個所返回的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並非並行執行的

1.2.Future.catchError

當異步任務發生錯誤,能夠在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
複製代碼

1.3.Future.whenComplete

有不少時候,當異步任務不管成功或者失敗都須要作一些事的場景,如在網絡請求前彈出加載進度框,在請求結束後關閉進度框,下面用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
不管失敗,或者成功都會走到這
複製代碼

1.4.Future.wait

有時候,須要等待多個異步任務都執行結束後才進行一些操做,若有一個界面,須要從兩個接口獲取數據,獲取成功後,將兩個數據進行處理後顯示在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函數。

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 Future
I love Android
複製代碼

能夠發現,先輸出的是I love Future後面再輸出I love Android,能夠發現當函數被async修飾時,會先去執行下面的操做,當下面的操做執行完,而後再執行被async修飾的方法。async用來表示函數是異步的,定義的函數會返回一個Future對象,await後面是一個Future,表示等待該異步任務完成,異步完成後纔會往下走。要注意如下幾點:

  1. await關鍵字必須在async函數內部使用,也就是加await不加async會報錯。
  2. 調用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結束
複製代碼
  1. 當使用async做爲方法名後綴聲明時,說明這個方法的返回值是一個Future
  2. 當執行到該方法代碼用await關鍵字標註時,會暫停該方法其餘部分執行;
  3. 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能夠經過觸發成功或者失敗傳遞結果或者錯誤。

4、文件操做

有不少時候須要將文件保存到本地,這時候就須要用文件讀寫接口來實現,PathProvider插件提供一種平臺透明的方式來訪問設備文件系統上的經常使用位置。該類當前支持兩個文件系統位置:

  • 臨時目錄:系統可隨時清除的臨時目錄(緩存)。在iOS上,這對應於NSTemporaryDirectory()返回的值。在Android上,這是getCacheDir()返回的值。
  • 文檔目錄:應用程序的目錄,用於存儲只有本身能夠訪問的文件,只有當應用程序被卸載時,系統纔會清除目錄。在iOS上,這對應於NSDocumentDirectory。在Android上,這是AppData目錄。

Flutter裏實現文件讀寫,須要使用path_providerDart裏的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>{
   @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
複製代碼

2.讀取本地文件內容

讀取文件少不了權限的問題,在Dart Packages能夠找到simple_permissions這個庫來簡化申請權限的步驟,按照上面說明跟着操做就能夠:

權限庫操做
上面的意思是在 AndroidManifestInfo.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);
複製代碼

運行結果:

追加方式填寫內容
好了,簡單的讀寫文件就實現了。

5、sqflite數據庫

AndroidiOS中都會有SQLite,那麼Flutter有沒有呢?答案是確定有的。Flutter中的SQLite數據庫是同時支持AndroidiOS的,它的名字叫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語句很簡單,就三個字段,分別是主鍵,用戶名,用戶密碼,界面以下:

數據庫表的界面
界面弄好了,下面就一步一步來。

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('$username','$password')";
    await my_db.transaction((tran) async{
       await tran.rawInsert(add_sql);
    });
    //關閉數據庫
    await my_db.close();
    setState(() {
      _data = "增長一條數據成功,名字是:$username,密碼是:$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),
複製代碼

驗證結果,流程是:

  1. 先輸入用戶名和密碼
  2. 點擊增長
  3. 點擊查信息: 運行結果以下:

增長的結果

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包的方法firstInValue
  int data_count = Sqflite.firstIntValue(await my_db.rawQuery(sql_queryCount));
  await my_db.close();
  setState(() {
    _data = "數據條數:$data_count";
  });
}
複製代碼

對本地數據庫的基本操做實現了一遍,下面學習網絡請求操做。

6、網絡請求操做

Flutter的請求網絡有多種方式,一種是使用dart io中的HttpClient發起的請求,一種是使用dio庫,另外一種是使用http庫,先學一下getpostputdelete就等後面用到在學。下面就實踐:

1.dart io發起的請求

1.1.get請求

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");
  }
  
}
複製代碼

結果以下:

請求一返回的數據

1.2.post請求

_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");
  }

}
複製代碼

返回結果以下:

post請求

2.dio請求

dio是一個強大的Dart Http請求庫,支持Restful APIFormData、攔截器、錯誤處理、轉換器、設置Http代理、請求取消、Cookie管理、文件上傳和下載、超時等。在pub.flutter-io.cn/packages搜最新的依賴包,這個網址太好用,你想搜一些三方庫裏面都有:

dio最新依賴
pubspec.yaml添加依賴:

dio: ^2.0.14
複製代碼

導入依賴:

import 'package:dio/dio.dart';
複製代碼

2.1.get請求

//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);

  }

}
複製代碼

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);
  }
}
複製代碼

以上三種庫的getpsot方式都實踐了一遍,在平時開發中最好用dio庫和http庫,由於dart io中是使用HttpClient發起的請求,HttpClient自己功能較弱,不少經常使用功能不支持。

7、JSON

如今很難想象移動應用程序不須要與後臺交互或者存儲結構化數據。如今開發,數據傳輸方式基本都是用JSON,在Flutter中是沒有GSON/Jackson/Moshi這些庫,由於這些庫須要運行時反射,在Flutter是禁用的。運行時反射會干擾Dart的_tree shaking_。使用_tree shaking_,能夠在發版時"去除"未使用的代碼,來優化軟件的大小。因爲反射會默認使用全部代碼,所以_tree shaking_會很難工做,這些工具沒法知道哪些widget在運行時未被使用,所以冗餘代碼很難剝離,使用反射時,應用尺寸沒法輕鬆進行優化,雖然不能在Flutter使用運行時反射,但有些庫提供了類型簡單易用的API,但它們是基於代碼生成的。下面學學在Flutter中如何操做JSON數據的使用JSON有兩個常規策略:

  1. 手動序列化和反序列化
  2. 經過代碼生成自動序列化和反序列化 不一樣的項目有不一樣的複雜度和場景,針對於小的項目,使用代碼生成器可能會殺豬用牛刀了。對於具備多個JSON model的複雜應用程序,手動序列化可能會比較繁瑣,且容易出錯。

1.手動序列化JSON

Flutter中基本的JSON序列化很是簡單,Flutter有一個內置的dart:convert庫,其中包含一個簡單的JSON解碼器和編碼器。下面簡單實現一下:

1.1.內連序列化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的做用出來了。

1.2.模型類中序列化JSON

經過引入一個簡單的模型類(model class)來解決前面提到的問題,創建一個User類,在類內部有兩個方法:

  1. User.fromJson構造函數,用於從一個map構造出一個User實例map structure
  2. toJson方法,將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,
    };
}
複製代碼

調用以下:

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"}
複製代碼

2.使用代碼生產庫序列化JSON

下面使用json_serializable package包,它是一個自動化的源代碼生成器,能夠爲開發者生成JSON序列化模板。

2.1.添加依賴

要包含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
複製代碼

2.2.代碼生成

有兩種運行代碼生成器的方法:

  1. 一次性生成,在項目根目錄運行flutter packages pub run build_runner build,能夠在須要爲咱們的model生成json序列化代碼。這觸發一次性構建,它經過源文件,挑選相關的併爲它們生成必要的序列化代碼。這個很是方便,可是若是咱們不須要每次在model類中進行更改都要手動運行構建命令的話會更好。
  2. 持續生成,使用_watcher_可使源代碼生成的過程更加方便,它會監視項目中文化的變化,並在須要時自動構建必要的文件,經過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文件:

生成dart文件
裏面的內容能夠本身去看看看,就是反序列化/序列化的操做。 注意:沒生成User.g.dart執行多幾回命令便可。 最後經過 json_serializable方式反序列化 JSON字符串,不須要對先前代碼修改:

2.3.反序列化

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}");
複製代碼

2.4.序列化

var user = new User("Knight","Knight163.com");
  String user_json = json.encode(user);
  print(user_json);
複製代碼

結果是跟上面同樣,不過這種方式額外多了生成一個文件...

8、例子

下面實現一個簡單例子,效果圖以下:

最終效果圖
返回的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,
              ),
            ),
          ),
        ],
      ),
    ),
  );

}

複製代碼

上面獲取數據有兩種方式。

9、總結

  1. 知道Dart的簡單大體執行模型,當啓動一個Flutter應用時,會建立一個isolate,而後會初始化兩個隊列MicroTaskEvent,和初始化一個消息循環,代碼執行方式和順序取決於MicroTaskEvent隊列。
  2. Futureasync都不是並行執行的。
  3. 簡單的本地數據庫操做。
  4. Json數據簡單處理(序列化和反序列化)方式。
  5. 網絡簡單的請求。

做爲萌新,確定有不少技術沒學到位,若有錯誤,歡迎指出指正,謝謝~

相關文章
相關標籤/搜索