Flutter第6天--非同步-IO+網路訪問+json

張風捷特烈發表於2018-12-21

Flutter七日遊第六天:2018-12-21 天氣:雨-陰

零、前言

對於問我怎麼學習的人,空口白牙說的是雞湯,我不喜歡喝也不喜歡做。
文中根據實際情況,分享一些個人的程式設計心得,自己參考一下,取長補短


一、單執行緒模型下的非同步操作

  • 為什麼強調是單執行緒:Dart是單執行緒模型,單執行緒模型,單執行緒模型!!!

什麼是單執行緒:就是你是一個人在戰鬥
什麼是非同步: 比如你要燒水(耗時操作),並不需要傻傻地等著水開才能去做下一件事(掃地)
只要開火(方法呼叫),然後你就可以去掃地(執行非同步任務下面的方法),水燒開鳴叫(回撥), 去沖水(處理非同步任務結果)。

  • Dart非同步程式設計的方式:Future和Stream

Future相當於40米大砍刀,Stream相當於一捆40米大砍刀
dart提供了關鍵字async(非同步)await(延遲執行),相當於普通的便捷的小匕首


1.asyncawait的簡單使用

感覺網上一些教程上來就告訴你什麼樣是錯的,然後一步步糾正...最後都沒有完整程式碼總結一下
我想最起碼應該先給個正確的示範吧...然後再說錯誤情況

1.1:最簡單的檔案讀取
//根據名稱讀取檔案
readFile(name) {
  //建立檔案物件
  var file = File(name);
  return file.readAsString();
}

//讀取檔案成功
readOk() async{
  var result = await readFile(r"C:\Users\Administrator\Desktop\應龍.txt");
  print(result);
}

main()  {
  readOk();
  print("我是第幾?");
}
複製程式碼

async+await.png

檔案讀取.png

函式執行過程中看到了async(燒水)會先去執行下面的操作(掃地),水燒開await放行,print(result);(沖水)


1.2.asyncawait的分析

也許你就問,不加async或await會怎麼樣?不同時加又會怎麼樣?
不加async或await:就像平常程式碼一樣順序執行
加async不加await:然並卵
不加async加await:報錯


2.去拿我40米大砍刀:Future

可以看出:file.readAsString()返回的是:Future<String>,

Future.png

main() {
  var file = File(r"C:\Users\Administrator\Desktop\應龍.txt");
  Future<String> re = file.readAsString();
  re.then((result) {
    print(result);
  });
  print("我是第幾?");
}
複製程式碼

這樣操作也能達到非同步的效果,具體就不深入說了
有時間打算寫一篇:基於Java,Python,JavaScript(ES6+),Dart,node(都是我曾涉及過的)
綜合討論一下單執行緒,多執行緒,同步,非同步,畢竟這幾個詞讓我挺煩心


二、Dart中的IO操作

1.檔案操作的API測試建構函式

建構函式.png

File(檔案路徑)
File.fromUri(Uri資源路徑識別符號)
File.fromRawPath(Uint8List rawPath)
複製程式碼

[番外]:如何去認識一個類:Uri為例

也許看到File.fromUri(Uri uri)你會說Uri我不會,然後就不管了,如果有空就看兩眼唄,又不會吃虧
我的經驗是先看它的構造方法,然後再看欄位,再總覽一下方法名(Ctr+F12)
如果你對這個類一無所知,還是先看粗略瞄一下文件註釋,至少知道幹嘛的
一般都會有一句簡潔的話介紹它(英文不會,詞典查一下,讀原文件:這道坎早晚要過的)
Android中對Uri有一定的認識,知道它是一個資源定位的標誌,就像門牌號吧。

---->[註釋第一句]------------
A parsed URI, such as a URL.
url我們都知道:http://192.168.43.60:8089/file/springboot/data.json
-------------------------------------------------------------
它是由一下部分構成的:
http(協議名)://+ 192.168.43.60(域名) + :8089(埠) + file/springboot/data.json(資源地址)

比如我是資源,你要找我:
中國://安徽:合肥/瑤海區/XXX路/XXX小區/張風捷特烈
-------------------------------------------------------------

---->[Uri核心屬性]------------
factory Uri(
      {String scheme,
      String userInfo,
      String host,
      int port,
      String path,
      Iterable<String> pathSegments,
      String query,
      Map<String, dynamic /*String|Iterable<String>*/ > queryParameters,
      String fragment}) 
複製程式碼
不知道的東西去試試唄,反正跑一下又不要錢
var base = Uri.base;
print(base);//列印了跟路徑--file:///I:/Java/Android/FlutterUnit/toly/


----->[.parse方法測試]
static Uri parse(String uri, [int start = 0, int end])
//既然Uri.parse返回一個Uri物件,那麼它應該有Uri的相應屬性
 var parse = Uri.parse("http://192.168.43.60:8089/file/springboot/data.json");
  print("host=${parse.host}");//192.168.43.60
  print("port=${parse.port}");//8089
  print("path=${parse.path}");//file/springboot/data.json
  print("query=${parse.query}");//
  print("fragment=${parse.fragment}");//

----->[.http方法測試]
Uri.http(String authority, String unencodedPath,[Map<String, String> queryParameters]) 
----->[.http方法測試,它的註釋都寫成這樣了,你還不會用嗎?]-------
//Creates a new `http` URI from authority, path and query
//http://example.org/path?q=dart.
//new Uri.http("example.org", "/path", { "q" : "dart" });

複製程式碼
如果用File開一個網路的Uri會怎麼樣:

學會分析bug不要輕易否定
首先保證網址是正確的

url請求.png

var file = File.fromUri(new Uri.http("192.168.43.60:8089", "/file/springboot/data.json"));

Unhandled exception:
Unsupported operation: Cannot extract a file path from a http URI
#0      _Uri.toFilePath (dart:core/uri.dart:2617:7)
#1      new File.fromUri (dart:io/file.dart:265:49)
#2      readFile (file:///I:/Java/Android/FlutterUnit/toly/test/base/8_io.dart:11:19)
複製程式碼

也許你到走了,會想(當然我也是這樣):什麼鬼,老子看半天,TM不能用,浪費時間!
也許你會憤然而去,而我則會去分析錯誤的原因(這就是面對錯誤的不同選擇)
前者可能永遠也不知道原因,而後者即使最後無果,路上也會有所收穫(打字的現在,我還未去分析)


所以,一起去看看吧
---->[bug的連結處:]
 String toFilePath({bool windows}) {
    if (scheme != "" && scheme != "file") {
      throw new UnsupportedError(
          "Cannot extract a file path from a $scheme URI");
    }
    
// 原來是scheme的鍋------那scheme是什麼呢?
  var uri = new Uri.http("192.168.43.60:8089", "/file/springboot/data.json");
  print(uri.scheme);//http
  
//可見-- new Uri.http的scheme是http,而這裡不是file所以報錯
 至少你的知識庫中多收錄了一條資訊:File.fromUri()不能訪問非file型別的Uri
 也知道了scheme大概是什麼東西,知識庫就是這樣一點一點自己累積的
 
---->[這麼重要的限制,方法上能不註明嗎?]-------
  /**
   * Create a File object from a URI.
   *
   * If [uri] cannot reference a file this throws [UnsupportedError].
   如果uri未涉及 file會報錯: UnsupportedError
   */
  factory File.fromUri(Uri uri) => new File(uri.toFilePath());

好吧,是一開始沒注意,到此一個錯誤就可以畫上句號了  

錯誤不可怕,可怕的是你不知道為什麼而導致以後還會犯,總之踩的坑多了,就會知道坑在那裡
也許別人給說你那有坑,第一次小心的過去了,下一次沒人提醒你,你可能就掉下去  
複製程式碼

file的Uri是什麼鬼?

也許你不知道,檔案拖到瀏覽器裡,也是能開啟的,你所見的就是feil型別的Uri

file.png

  //原始碼上面還有很多註釋自己看....
* // file:///C:/xxx/yyy
 * new Uri.file(r"C:\xxx\yyy", windows: true);
 */
factory Uri.file(String path, {bool windows})
複製程式碼
然後你就會用File.fromUri了:
var file = File.fromUri(Uri.parse("file:///F:/SpringBootFiles/file/springboot/data.json"));
複製程式碼

uri訪問資源.png

從一個小的API開始,讓自己儘可能去多認識一些事物,並不是說你要把原始碼都理得很清楚
在自己接受範圍的150%之內可以去嘗試,失敗了沒有關係,總比看那些駁來駁去的文章有意義

如果你想提高自己(這句話也是自勉):
不要讓自己總走在平坦的路上,有時登高望遠方能窺見美景,也不要一心只走險峰,小心失足。
今天心情不佳,廢話有點多,聽得進去的就聽,聽不進去的就無視,如果要駁我,請在評論區!!

[番外結束]

2.File和Directory的常見Api

Java裡資料夾也是File物件,Dart裡區分了出來
很有意思,File和Directory的Api基本上都是同步,非同步成對出現

FileSystemEntity.png


2.1:遞迴建立資料夾

預設recursive是false,只能建立下一級

遞迴建立資料夾.png

main() {
  var dir = Directory(r"C:\Users\Administrator\Desktop\dart\test\all\li");
  dir.createSync(recursive: true);
}
複製程式碼

2.2:列出所有的檔案
main() async {
  var dir = Directory(r"E:\Material\MyUI");
  var list = dir.list();//預設非遞迴及只列出一級
  list.forEach((fs){
    print(fs.path);
  });
}
複製程式碼

目錄列表.png

main() async {
  var dir = Directory(r"E:\Material\MyUI");
  var list = dir.list(recursive: true);//遞迴列出所有檔案
  list.forEach((fs){
    print(fs.path);
  });
}
複製程式碼

目錄下所有檔案都列出來,就不貼圖了


2.3:重新命名

看一下就行了

  var dir = Directory(r"C:\Users\Administrator\Desktop\dart\test\all\li");
  dir.rename(r"C:\Users\Administrator\Desktop\dart\test\all\hello");
複製程式碼

3.File物件的常用操作:
//根據名稱讀取檔案
readFile(name) async {
  //建立檔案物件
  var file = File(name);
  try {
    //判斷是否存在
    bool exists = await file.exists();
    if (exists) {
      //如果存在
      print(await file.length()); //檔案大小(位元組)---137
      print(await file.lastModified()); //最後修改時間---2018-12-21 13:49:35.000
      print(file.parent.path); //獲取父資料夾的路徑---C:\Users\Administrator\Desktop\dart
      return await file.readAsString(); //讀取檔案並返回
    } else {
      await file.create(recursive: true); //不存在則建立檔案
      return "未發現檔案,已為您建立!Dart機器人:2333";
    }
  } catch (e) {
    //異常處理
    print(e);
  }
}
複製程式碼

另外還有幾種不同的開啟方式,基本上Java都包含了,看名字也知道是什麼


4.檔案的寫入:

和java一樣,預設全換:想要追加:引數加mode: FileMode.append

main() async {
  wirte(r"C:\Users\Administrator\Desktop\dart\應龍.txt");
}

wirte(name) async{
  var file = File(name);
  file.writeAsString("海的彼岸有我未曾見證的風采");
}
複製程式碼

預設全部替換.png


三、關於移動端的檔案讀取問題

1.路徑問題

path_provider: ^0.4.1:提供了三個路徑,勉強用用吧

localPath() async {
  try {
    print('臨時目錄: ' + (await getTemporaryDirectory()).path);
    //----/data/user/0/com.toly1994.toly/cache
    print('文件目錄: ' + (await getApplicationDocumentsDirectory()).path);
    //----/data/user/0/com.toly1994.toly/app_flutter
    print('sd卡目錄: ' + (await getExternalStorageDirectory()).path);
    //----/storage/emulated/0
  } catch (err) {
    print(err);
  }
}
複製程式碼

2.動態許可權申請問題

simple_permissions: ^0.1.9:提供了動態許可權申請

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
複製程式碼
readFormSD() async {
  try {
    var perm =
        SimplePermissions.requestPermission(Permission.ReadExternalStorage);
    var sdPath = getExternalStorageDirectory();
    sdPath.then((file) {
      perm.then((v) async {
        var res = await readFile(file.path + "/應龍.txt");
        print(res);
      });
    });
  } catch (err) {
    print(err);
  }
}
複製程式碼

許可權+讀取.png

好了,這樣知識就對接完畢


3.小測試:列出sd卡的檔案

比較基礎,就是讀取資料夾下的內容,設定給ListView的Item

列出sd卡的檔案.png

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:simple_permissions/simple_permissions.dart';

class ListFilePage extends StatefulWidget {
  @override
  _ListFilePageState createState() => _ListFilePageState();
}

class _ListFilePageState extends State<ListFilePage>
    with SingleTickerProviderStateMixin {
  List<String> _files = [];

  @override
  void initState() {
    super.initState();
    localPath();
  }

  @override
  Widget build(BuildContext context) {
    //生成listView
    var listview = ListView.builder(
      itemCount: _files.length,
      itemBuilder: (BuildContext context, int index) {
        return Column(
          children: <Widget>[
            Container(
                color: Colors.white,
                padding: EdgeInsets.all(15),
                child: renderItem(index))
          ],
        );
      },
    );

    return Scaffold(
      appBar: AppBar(
        title: Text("張風捷特烈"),
      ),
      body: listview,
    );
  }

// 新增所有SD卡檔名稱
  localPath() {
    try {
      var perm =
          SimplePermissions.requestPermission(Permission.ReadExternalStorage);
      var sdPath = getExternalStorageDirectory();
      sdPath.then((file) {
        perm.then((v) {
          file.list().forEach((i) {
            _files.add(i.path);
          });
          setState(() {});
        });
      });
    } catch (err) {
      print(err);
    }
  }

  //渲染單條目
  renderItem(index) {
    return Row(
      children: <Widget>[
        Icon(
          Icons.extension,
          color: Colors.blue,
        ),
        Expanded(
            child: Padding(
          padding: EdgeInsets.only(left: 20),
          child: Text(
            _files[index],
            style: TextStyle(fontSize: 18),
          ),
        )),
        Icon(Icons.arrow_forward),
        Divider(height: 1)
      ],
    );
  }
}

複製程式碼

三、Dart中的網路請求操作:

0.新增依賴:在pubspec.yaml的dependencies下
http: ^0.11.3+17
複製程式碼

我的伺服器上提供了一些網路請求的Api,如果你想自己搭建伺服器介面,請看這篇
來回顧一下介面的api:

查詢介面:GET請求--------------------------------------------
----查詢所有:
http://www.toly1994.com:8089/api/android/note
----查詢偏移12條,查詢12條(即12條為一頁的第2頁):
http://www.toly1994.com:8089/api/android/note/12/12
----按區域查詢(A為Android資料,SB為SpringBoot資料,Re為React資料)
http://www.toly1994.com:8089/api/android/note/area/A
http://www.toly1994.com:8089/api/android/note/area/A/12/12
----按部分名稱查詢
http://www.toly1994.com:8089/api/android/note/name/材料
http://www.toly1994.com:8089/api/android/note/name/材料/2/2
----按型別名稱查詢(型別定義表見第一篇)
http://www.toly1994.com:8089/api/android/note/name/ABCS
http://www.toly1994.com:8089/api/android/note/name/ABCS/2/2
----按id名稱查
http://www.toly1994.com:8089/api/android/note/12

添改刪介面---------------------------------------------------------------
添-POST請求:http://www.toly1994.com:8089/api/android/note
更新-PUT請求:http://www.toly1994.com:8089/api/android/note/1
刪-DELETE請求:http://www.toly1994.com:8089/api/android/note/1
複製程式碼

1.get請求

注:client你隨便取什麼名字都行,客戶端訪問服務端,所以我用client

import 'package:http/http.dart' as client;

main() {
  getData((data) {
    print(data);
  });
}

getData(cbk) async {
  var api = 'http://www.toly1994.com:8089/api/android/note/100';
  try {
    final response = await client.get(api);
    if (response.statusCode == 200) {
      cbk(response.body);
    }
  } catch (e) {
    print(e);
  }
}
複製程式碼

訪問成功.png

如果你覺得回撥有點low,也完全可以用Future(用什麼不是重點,怎麼簡潔怎麼來)

main() {
  getData().then((data){
    print(data);
  });
}

Future<String> getData() async {
  try {
    final response = await client.get('http://www.toly1994.com:8089/api/android/note/100');
    if (response.statusCode == 200) {
      return response.body;
    }
  } catch (e) {
    print(e);
  }
}
複製程式碼

2.post請求:插入資料
main() {
  add((data) {
    print(data);
  });
}

add(cbk) async {
  var api = 'http://www.toly1994.com:8089/api/android/note';
  var item = {
    "type": "C",
    "name": "插入測試",
    "localPath": "null",
    "jianshuUrl": "https://www.jianshu.com/p/12f8ab32591a",
    "juejinUrl": "null",
    "imgUrl":
        "http://toly1994.com:8089/imgs/android/c3af376135a7abe0655c908195b271db.png",
    "createTime": "2018-09-06",
    "info": "null",
    "area": "A"
  };
  try {
    final response = await client.post(api, body: item);
    if (response.statusCode == 200) {
      cbk(response.body);
    }
  } catch (e) {
    print(e);
  }
}
複製程式碼

插入操作.png


3.put請求:更新資料
main() {
  set((data) {
    print(data);
  });
}

set(cbk) async {
  var api = 'http://www.toly1994.com:8089/api/android/note/199';
  var item = {
    "type": "C",
    "name": "修改測試",
    "localPath": "null",
    "jianshuUrl": "https://www.jianshu.com/p/12f8ab32591a",
    "juejinUrl": "null",
    "imgUrl":
    "http://toly1994.com:8089/imgs/android/c3af376135a7abe0655c908195b271db.png",
    "createTime": "2018-09-06",
    "info": "null",
    "area": "A"
  };

  try {
    final response = await client.put(api, body: item);
    if (response.statusCode == 200) {
      cbk(response.body);
    }
  } catch (e) {
    print(e);
  }
}
複製程式碼

修改測試.png


4.delete請求:刪除操作
main() {
  delete((data) {
    print(data);
  });
}

delete(cbk) async {
  var api = 'http://www.toly1994.com:8089/api/android/note/199';

  try {
    final response = await client.delete(api);

    if (response.statusCode == 200) {
      cbk(response.body);
    }
  } catch (e) {
    print(e);
  }
}
複製程式碼

刪除成功.png


四、關於Json

一般都是解析伺服器端傳來的json,非後端基本不用生產json

1.將json轉化為物件
{
    "id": 100,
    "type": "繪圖相關",
    "name": "D5-Android繪圖之讓圖形動起來",
    "localPath": "null",
    "jianshuUrl": "https://www.jianshu.com/p/12f8ab32591a",
    "juejinUrl": "null",
    "imgUrl": "http://toly1994.com:8089/imgs/android/c3af376135a7abe0655c908195b271db.png",
    "createTime": "2018-09-06",
    "info": "以前在Html利用js控制SVG或canvas進行運動模擬。瀏覽器自帶window.requestAnimationFrame能不斷執行渲染在這...",
    "area": "A"
  }
複製程式碼
1.1:建立實體類,建立構造方法
class NoteBean {
  int id;
  String type;
  String name;
  String localPath;
  String jianshuUrl;
  String juejinUrl;
  String imgUrl;
  String createTime;
  String info;
  String area;

  NoteBean.fromJson(Map<String, dynamic> map)
      : id = map['id'],
        name = map['name'],
        localPath = map['localPath'],
        jianshuUrl = map['jianshuUrl'],
        juejinUrl = map['juejinUrl'],
        imgUrl = map['imgUrl'],
        createTime = map['createTime'],
        info = map['info'],
        area = map['area'];
}
複製程式碼
    var j =
        '{"id":100,"type":"繪圖相關","name":"D5-Android繪圖之讓圖形動起來","localPath":"null","jianshuUrl":"https://www.jianshu.com/p/12f8ab32591a","juejinUrl":"null","imgUrl":"http://toly1994.com:8089/imgs/android/c3af376135a7abe0655c908195b271db.png","createTime":"2018-09-06","info":"以前在Html利用js控制SVG或canvas進行運動模擬。瀏覽器自帶window.requestAnimationFrame能不斷執行渲染在這...","area":"A"}';
    var noteBean = NoteBean.fromJson(json.decode(j));
    print(noteBean.name);//D5-Android繪圖之讓圖形動起來
複製程式碼

2.復對的Json轉化(也就是Json裡套Json)
2.1:待處理的Json字串
{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "id": 100,
    "type": "繪圖相關",
    "name": "D5-Android繪圖之讓圖形動起來",
    "localPath": "null",
    "jianshuUrl": "https://www.jianshu.com/p/12f8ab32591a",
    "juejinUrl": "null",
    "imgUrl": "http://toly1994.com:8089/imgs/android/c3af376135a7abe0655c908195b271db.png",
    "createTime": "2018-09-06",
    "info": "以前在Html利用js控制SVG或canvas進行運動模擬。瀏覽器自帶window.requestAnimationFrame能不斷執行渲染在這...",
    "area": "A"
  }
}
複製程式碼

2.2:增加實體類ResultBean
class ResultBean {
  String msg;
  int code;
  NoteBean data;

  ResultBean.fromJson(Map<String, dynamic> map)
      : msg = map['msg'],
        code = map['code'],
        data = NoteBean.fromJson(map['data']);
}
複製程式碼

2.3:使用:
  var j =
      '{"code":200,"msg":"操作成功","data":{"id":100,"type":"繪圖相關","name":"D5-Android繪圖之讓圖形動起來","localPath":"null","jianshuUrl":"https://www.jianshu.com/p/12f8ab32591a","juejinUrl":"null","imgUrl":"http://toly1994.com:8089/imgs/android/c3af376135a7abe0655c908195b271db.png","createTime":"2018-09-06","info":"以前在Html利用js控制SVG或canvas進行運動模擬。瀏覽器自帶window.requestAnimationFrame能不斷執行渲染在這...","area":"A"}}';

  var result = ResultBean.fromJson(json.decode(j));
  print(result.data.name);//D5-Android繪圖之讓圖形動起來
複製程式碼

3.關於Json的內嵌陣列

這裡data是一個json的陣列,這樣訪問的服務端介面的資料處理就搞定了

{
  "code": 200,
  "msg": "操作成功",
  "data": [
    {
      "id": 198,
      "type": "繪圖相關",
      "name": "",
      "localPath": "---",
      "jianshuUrl": "",
      "juejinUrl": "---",
      "imgUrl": "http://toly1994.com:8089/imgs/android/8a11d27d58f4c1fa4488cf39fdf68e76.png",
      "createTime": "2021-02-18",
      "info": "hh",
      "area": "A"
    },
    {
      "id": 200,
      "type": "繪圖相關",
      "name": "",
      "localPath": "---",
      "jianshuUrl": "",
      "juejinUrl": "---",
      "imgUrl": "http://toly1994.com:8089/imgs/android/8a11d27d58f4c1fa4488cf39fdf68e76.png",
      "createTime": "2018-12-21",
      "info": "hh",
      "area": "A"
    }
  ]
複製程式碼
class ResultBean {
  String msg;
  int code;
  var data;

  ResultBean.fromJson(Map<String, dynamic> map)
      : msg = map['msg'],
        code = map['code'],
        data = map['data'];
}

class NoteBean {
  int id;
  String type;
  String name;
  String localPath;
  String jianshuUrl;
  String juejinUrl;
  String imgUrl;
  String createTime;
  String info;
  String area;

  NoteBean.fromJson(Map<String, dynamic> map)
      : id = map['id'],
        name = map['name'],
        localPath = map['localPath'],
        jianshuUrl = map['jianshuUrl'],
        juejinUrl = map['juejinUrl'],
        imgUrl = map['imgUrl'],
        createTime = map['createTime'],
        info = map['info'],
        area = map['area'];
}
複製程式碼
  var j ='{"code":200,"msg":"操作成功","data":[{"id":198,"type":"繪圖相關","name":"","localPath":"---","jianshuUrl":"","juejinUrl":"---","imgUrl":"http://toly1994.com:8089/imgs/android/8a11d27d58f4c1fa4488cf39fdf68e76.png","createTime":"2021-02-18","info":"hh","area":"A"},{"id":200,"type":"繪圖相關","name":"","localPath":"---","jianshuUrl":"","juejinUrl":"---","imgUrl":"http://toly1994.com:8089/imgs/android/8a11d27d58f4c1fa4488cf39fdf68e76.png","createTime":"2018-12-21","info":"hh","area":"A"}]}';
  var result = ResultBean.fromJson(json.decode(j));
  print(NoteBean.fromJson(result.data[1]).imgUrl);//http://toly1994.com:8089/imgs/android/8a11d27d58f4c1fa4488cf39fdf68e76.png
複製程式碼

好了,今天就到這裡,明天最後一天,敬請期待


後記:捷文規範

1.本文成長記錄及勘誤表
專案原始碼 日期 備註
V0.1-github 2018-12-21 Flutter第6天--非同步-IO+網路+json
2.更多關於我
筆名 QQ 微信 愛好
張風捷特烈 1981462002 zdl1994328 語言
我的github 我的簡書 我的掘金 個人網站
3.宣告

1----本文由張風捷特烈原創,轉載請註明
2----歡迎廣大程式設計愛好者共同交流
3----個人能力有限,如有不正之處歡迎大家批評指證,必定虛心改正
4----看到這裡,我在此感謝你的喜歡與支援


icon_wx_200.png

相關文章