[Flutter必備]-Flutter中的網路訪問

張風捷特烈發表於2019-07-25
首先將依賴加上

[Flutter必備]-Flutter中的網路訪問


1.巧婦難為無米之炊

首先我們需要找寫測試的url連結,有一個好地方,那就是Github提供了很多api
但在此之前我們需要先行處理一些事,比如註冊Github賬號,獲取一個token
下面的Github賬號是我專門為Flutter準備的,token值就不加密了,大家不要亂玩。


1.1:如何獲取token

小頭像-->Settings-->Developer settings -->Personal access tokens-->Generate new token

[Flutter必備]-Flutter中的網路訪問


1.2:如何通過post請求在你的github專案中新增一個檔案

api:https://api.github.com/repos/使用者名稱/專案名/contents/檔案路徑?access_token=token值
請求頭:Content-Type=application/json,請求體如下,注意檔案內容需要用base64
可以用wanandroid裡的工具轉化,該請求的其他引數可以詳見Github的相應api

{
  "message": "commit from toly ",//提交資訊
  "content": "aGVsbG8="//資料內容
}
複製程式碼

[Flutter必備]-Flutter中的網路訪問

  • Flutter中傳送put請求,在github專案中新增一個檔案
import 'package:http/http.dart' as client;

main() {
  put();
}

void put() {
  var baseUrl="https://api.github.com/";
  var operate="repos/toly-flutter/flutter_journey/contents/";
  var path="http-put-file.txt";
  var params="?access_token=4514388836f6da9f6c6cf7ba0721f2a6d1e89528";//請求引數

  var api =baseUrl+operate+path+params;//url

  Map<String ,String> headers = {"Content-Type":"application/json"};//請求頭
  var reqBody="""
  {
  "message": "commit from commit from toly",
  "content": "aGVsbG8="
  }
  """;//請求體

  client.put(api,headers:headers,body: reqBody).then((rep){
    print(rep.statusCode);
    print(rep.body);
  });
}
複製程式碼

[Flutter必備]-Flutter中的網路訪問

注,Dart中將字串轉換base64可以:base64Encode(utf8.encode("hello"));


1.3:通過put請求修改一個github檔案

api:https://api.github.com/repos/使用者名稱/專案名/contents/檔案路徑?access_token=token值
請求頭:Content-Type=application/json,請求體如下,注意檔案內容需要用base64
關於sha值,在新增的時候,響應體中有,見上圖。每次修改也會返回新的sha值

{
  "message": "update by toly ",//提交資訊
  "content": "aGVsbG8="//資料內容
  "sha":"檔案所對應的sha值"
}
複製程式碼

[Flutter必備]-Flutter中的網路訪問

  • Flutter中傳送put請求,在github專案中修改一個檔案
void update() {
  var baseUrl="https://api.github.com/";
  var operate="repos/toly-flutter/flutter_journey/contents/";
  var path="http-put-file.txt";
  var params="?access_token=4514388836f6da9f6c6cf7ba0721f2a6d1e89528";//請求引數

  var api =baseUrl+operate+path+params;//url

  Map<String ,String> headers = {"Content-Type":"application/json"};//請求頭
  var reqBody="""
  {
  "message": "update by toly",
  "content": "5byg6aOO5o2354m554OI",
  "sha":"b6fc4c620b67d95f953a5c1c1230aaab5db5a1b0"
  }
  """;//請求體

  client.put(api,headers:headers,body: reqBody).then((rep){
    print(rep.statusCode);
    print(rep.body);
  });
}
複製程式碼

[Flutter必備]-Flutter中的網路訪問


1.4:通過delete請求刪除一個github檔案

api:https://api.github.com/repos/使用者名稱/專案名/contents/檔案路徑?access_token=token值
請求頭:Content-Type=application/json,可以要當前檔案的sha值

{
  "message": "delete by toly ",//提交資訊
  "sha":"檔案所對應的sha值"
}
複製程式碼

http庫的delete請求居然不能加請求體?!這裡用PostMan演示一下

[Flutter必備]-Flutter中的網路訪問


1.5:用post提交一個issue

api:https://api.github.com/repos/使用者名稱/專案名/issues?access_token=token值
請求頭:Content-Type=application/json,可以要當前檔案的sha值

{
  "title": "一起來Flutter之旅吧",
  "body": "Flutter,大家感覺怎麼樣?應該不難吧!"
}
複製程式碼

[Flutter必備]-Flutter中的網路訪問

  • Flutter中傳送post請求,在github專案中新增一條issue
void post() {
  var baseUrl="https://api.github.com/";
  var operate="repos/toly-flutter/flutter_journey/issues";
  var params="?access_token=4514388836f6da9f6c6cf7ba0721f2a6d1e89528";//請求引數

  var api =baseUrl+operate+params;//url

  Map<String ,String> headers = {"Content-Type":"application/json"};//請求頭
  var reqBody="""
{
  "title": "一起來Flutter之旅吧",
  "body": "Flutter,大家感覺怎麼樣?應該不難吧!"
}
  """;//請求體

  client.post(api,headers:headers,body: reqBody).then((rep){
    print(rep.statusCode);
    print(rep.body);
  });
}
複製程式碼

1.6:使用get請求獲取一個issue

api:https://api.github.com/repos/使用者名稱/專案名/issues/第幾個?access_token=token值

void get(){
  //GET /repos/:owner/:repo/issues/:issue_number
  var baseUrl="https://api.github.com/";
  var operate="repos/toly-flutter/flutter_journey/issues/1";
  var params="?access_token=4514388836f6da9f6c6cf7ba0721f2a6d1e89528";//請求引數

  var api =baseUrl+operate+params;//url
  client.get(api).then((rep){
    print(rep.statusCode);
    print(rep.body);
  });
}
複製程式碼

好了,Http的幾種常用的請求方式基本都會了吧。


2. Json的解析

2.0:簡介

Dart中的Map<String,String>物件和Json非常相似,所以可以用其作為轉換媒介
通過convert包中的json.decode方法,可以將Json字串轉化成一個Map物件
在實體類中可以根據這個Map物件的屬性對實體類進行例項化。

import 'dart:convert';

main() {
  String jsonStr = """
{
  "name":"Flutter之旅",
  "author":"張風捷特烈"
}
""";

  var book = Book.fromMap(json.decode(jsonStr));
  print(book.name);//Flutter之旅
  print(book.author);//張風捷特烈
}

class Book {
  String name;
  String author;

  Book.fromMap(Map<String, dynamic> json) {//根據Map穿件例項
    name = json["name"];
    author = json["author"];
  }
}
複製程式碼

2.1: 獲取json

Github的https://api.github.com/users/使用者名稱可以獲取使用者基本資訊
這裡就先解析我的https://api.github.com/users/toly1994328

{
  "login": "toly1994328",
  "id": 26687012,
  "node_id": "MDQ6VXNlcjI2Njg3MDEy",
  "avatar_url": "https://avatars3.githubusercontent.com/u/26687012?v=4",
  "gravatar_id": "",
  "url": "https://api.github.com/users/toly1994328",
  "html_url": "https://github.com/toly1994328",
  "followers_url": "https://api.github.com/users/toly1994328/followers",
  "following_url": "https://api.github.com/users/toly1994328/following{/other_user}",
  "gists_url": "https://api.github.com/users/toly1994328/gists{/gist_id}",
  "starred_url": "https://api.github.com/users/toly1994328/starred{/owner}{/repo}",
  "subscriptions_url": "https://api.github.com/users/toly1994328/subscriptions",
  "organizations_url": "https://api.github.com/users/toly1994328/orgs",
  "repos_url": "https://api.github.com/users/toly1994328/repos",
  "events_url": "https://api.github.com/users/toly1994328/events{/privacy}",
  "received_events_url": "https://api.github.com/users/toly1994328/received_events",
  "type": "User",
  "site_admin": false,
  "name": "張風捷特烈(toly)",
  "company": "捷特王國",
  "blog": "http://www.toly1994.com",
  "location": "China",
  "email": null,
  "hireable": null,
  "bio": "The king of coder.",
  "public_repos": 64,
  "public_gists": 0,
  "followers": 238,
  "following": 9,
  "created_at": "2017-03-26T09:55:25Z",
  "updated_at": "2019-07-15T08:05:52Z"
}
複製程式碼

2.2:實體類的生成

你也可以一點一點寫出這個實體類,不過推薦用生成的方法,比較有時候欄位太多,比較費勁
這裡給一個用起來還不錯的地方JSON to Dart,有時間自己寫個轉換外掛來玩玩

class User {
  String login;
  int id;
  String nodeId;
  String avatarUrl;
  String gravatarId;
  String url;
  String htmlUrl;
  String followersUrl;
  String followingUrl;
  String gistsUrl;
  String starredUrl;
  String subscriptionsUrl;
  String organizationsUrl;
  String reposUrl;
  String eventsUrl;
  String receivedEventsUrl;
  String type;
  bool siteAdmin;
  String name;
  String company;
  String blog;
  String location;
  String email;
  String hireable;
  String bio;
  int publicRepos;
  int publicGists;
  int followers;
  int following;
  String createdAt;
  String updatedAt;

  User.fromJson(Map<String, dynamic> json) {
    login = json['login'];
    id = json['id'];
    nodeId = json['node_id'];
    avatarUrl = json['avatar_url'];
    gravatarId = json['gravatar_id'];
    url = json['url'];
    htmlUrl = json['html_url'];
    followersUrl = json['followers_url'];
    followingUrl = json['following_url'];
    gistsUrl = json['gists_url'];
    starredUrl = json['starred_url'];
    subscriptionsUrl = json['subscriptions_url'];
    organizationsUrl = json['organizations_url'];
    reposUrl = json['repos_url'];
    eventsUrl = json['events_url'];
    receivedEventsUrl = json['received_events_url'];
    type = json['type'];
    siteAdmin = json['site_admin'];
    name = json['name'];
    company = json['company'];
    blog = json['blog'];
    location = json['location'];
    email = json['email'];
    hireable = json['hireable'];
    bio = json['bio'];
    publicRepos = json['public_repos'];
    publicGists = json['public_gists'];
    followers = json['followers'];
    following = json['following'];
    createdAt = json['created_at'];
    updatedAt = json['updated_at'];
  }
複製程式碼

2.3.網路請求+json的使用

現在完全可以將以前寫的介面改一改,然後用Github獲取的資料填充進去
這裡只是簡單展示一下,說明網路資料和佈局介面的對接,並沒有做得太精細
GithubPanel就是以前寫得介面稍微改裝一下,這裡程式碼就不貼了。

[Flutter必備]-Flutter中的網路訪問

[Flutter必備]-Flutter中的網路訪問

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as client;
import 'day6/github_panel.dart';
import 'day6/user.dart';

void main() {
  var baseUrl = "https://api.github.com/";
  var operate = "users/";
  var name = "toly1994328";

  var api = baseUrl + operate + name; //url
  client.get(api).then((rep) {
    var user = User.fromJson(json.decode(rep.body));
print(user.avatarUrl);
    var scaffold = Scaffold(
        appBar: AppBar(title: Text("Flutter之旅"),),
        body: GithubPanel(user: user,)
    );

    var app = MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: scaffold,
    );
    return runApp(app);
  });
}
複製程式碼

2.4.元件的再封裝

你會發現上面雖然能用,但是看著真的非常難受,怎麼讓它用起來爽一點呢,兩個字封裝
實現一個GithubUserPanel,用法是傳入一個使用者名稱引數就行了。並且複用以前的皮膚。
由於網路訪問是非同步的,我們需要一個有狀態的元件,當非同步載入完成之後,再setState重新渲染。

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as client;
import 'github_panel.dart';
import 'user.dart';
class GithubUserPanel extends StatefulWidget {
  GithubUserPanel({
    Key key,
    this.userName,
  }) : super(key: key);
  final String userName;
  @override
  _GithubUserPanelState createState() => _GithubUserPanelState();
}
class _GithubUserPanelState extends State<GithubUserPanel> {
  var baseUrl = "https://api.github.com/";
  var operate = "users/";
  var panel;
  @override
  void initState() {
    super.initState();
    var api = baseUrl + operate + widget.userName; //url
    client.get(api).then((rep) {
      var user = User.fromJson(json.decode(rep.body));
      panel = GithubPanel(
        user: user,
      );
      setState(() {});
    });
  }
  @override
  Widget build(BuildContext context) {
    return Container(
      child: panel,
    );
  }
}
複製程式碼

也就這寫程式碼就行了,是不是感受到了GithubPanel複用的爽感。


2.5.使用

這樣用起來就和往常一樣,只要傳個名字就行了

[Flutter必備]-Flutter中的網路訪問

void main() {

    var scaffold = Scaffold(
        appBar: AppBar(title: Text("Flutter之旅"),),
        body: GithubUserPanel(userName: "toly1994328",)
    );

    var app = MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: scaffold,
    );
    return runApp(app);

}
複製程式碼

3.網路請求包dio的使用

dio作為JoJo的奇妙冒險的幾部大boss,聽名字就挺霸氣,在網頁搜dio根本沒有Flutter的事
上來說的那個http包相對比較原始,dio封裝的更好些,用法比較多。
反正再怎麼玩,都脫離不了http請求,所以要分清主次,切莫捨本逐末。

dependencies:
  dio: ^2.1.13
複製程式碼

3.1:get獲取github使用者資訊
var dio=Dio();
var baseUrl = "https://api.github.com/";
var operate = "users/";
var api=baseUrl+operate+"toly1994328";
dio.get(api).then((rep)=>print(rep.data));
複製程式碼

3.2: put請求新增github專案檔案
void put() {
  var baseUrl="https://api.github.com/";
  var operate="repos/toly-flutter/flutter_journey/contents/";
  var path="http-put-file-dio.txt";
  var params="?access_token=4514388836f6da9f6c6cf7ba0721f2a6d1e89528";//請求引數
  var api =baseUrl+operate+path+params;//url
  Map<String ,String> headers = {"Content-Type":"application/json"};//請求頭
  var reqBody="""
  {
  "message": "commit from commit from toly",
  "content": "aGVsbG8="
  }
  """;//請求體
  Dio().put(api,queryParameters:headers,data: reqBody).then((rep){
    print(rep.statusCode);
    print(rep.data);
  });
}
複製程式碼

3.3:delete請求刪除github專案檔案

dio中的delete是可以新增請求體的

void delete() {
  var baseUrl="https://api.github.com/";
  var operate="repos/toly-flutter/flutter_journey/contents/";
  var path="http-put-file-dio.txt";
  var params="?access_token=4514388836f6da9f6c6cf7ba0721f2a6d1e89528";//請求引數
  var api =baseUrl+operate+path+params;//url
  Map<String ,String> headers = {"Content-Type":"application/json"};
  var reqBody="""
  {
  "message": "delete by toly",
  "sha": "b6fc4c620b67d95f953a5c1c1230aaab5db5a1b0"
  }
  """;
  Dio().delete(api,queryParameters:headers,data: reqBody).then((rep){
    print(rep.data);
  });
}
複製程式碼

3.4:post提交一條issue
void post() {
  var baseUrl="https://api.github.com/";
  var operate="repos/toly-flutter/flutter_journey/issues";
  var params="?access_token=4514388836f6da9f6c6cf7ba0721f2a6d1e89528";//請求引數
  var api =baseUrl+operate+params;//url
  Map<String ,String> headers = {"Content-Type":"application/json"};//請求頭
  var reqBody="""
{
  "title": "張風捷特烈",
  "body": "我是誰,我在哪裡,我要到哪去?"
}
  """;//請求體
  Dio().post(api,queryParameters:headers,data: reqBody).then((rep){
    print(rep.statusCode);
    print(rep.data);
  });
}
複製程式碼

3.5:通過dio下載

就拿掘金的app下載吧,在dio中是很方便的,一行搞定。

[Flutter必備]-Flutter中的網路訪問

var url="https://landing.juejin.im/app-download?utm_source=app_download&utm_medium=yingyongbao&utm_campaign=app1808";
Dio().download(url,"./掘金.apk").then((rep){
  print(rep.statusCode);
  print(rep.data);
});
複製程式碼

3.6:通過dio上傳

檔案上傳一直是個較難問題,要實現檔案上傳,你需要一點後端的知識
核心就是客戶端將資料通過請求給伺服器,伺服器將請求中的內容進行操作
上傳也就是伺服器將資料或檔案儲存到了服務端指定位置。
一般通過表單提交,也可以直接將二進位制流通過請求體給服務端。

FormData formData = FormData.from({//建立表單
  "name": "toly",
  "age": 25,
  "data":  UploadFileInfo(File("./data.json"), "data.json"),
  "image":  UploadFileInfo(File("./photo.png"), "photo.png"),
});
var api="/loadFile";
Dio().post(api, data: formData).then((rep){//將表單通過請求體傳給服務端
});
複製程式碼

3.7:基本配置引數

看Dio的原始碼中有一個可選引數BaseOptions

---->[dio-2.1.13/lib/src/dio.dart:53]----
class Dio {
  /// Create Dio instance with default [Options].
  /// It's mostly just one Dio instance in your application.
  Dio([BaseOptions options]) {

---->[dio-2.1.13/lib/src/options.dart:39]----
class BaseOptions extends _RequestConfig {
  BaseOptions({
    String method,//請求方法
    int connectTimeout,//連結超時
    int receiveTimeout,//接收超時
    Iterable<Cookie> cookies,//cookies
    this.baseUrl,//基礎Url
    this.queryParameters,//請求引數
    Map<String, dynamic> extra,
    Map<String, dynamic> headers,//請求頭
    ResponseType responseType = ResponseType.json,//返回型別
    ContentType contentType,//內容型別
    ValidateStatus validateStatus,
    bool receiveDataWhenStatusError = true,
    bool followRedirects = true,
    int maxRedirects = 5,
   RequestEncoder requestEncoder,
    ResponseDecoder responseDecoder,
  }) : super(
複製程式碼

關於更多dio的用法,這裡不過多介紹,可以去這裡看一下,dio作者的講解


結語

本文到此接近尾聲了,如果想快速嚐鮮Flutter,《Flutter七日》會是你的必備佳品;如果想細細探究它,那就跟隨我的腳步,完成一次Flutter之旅。
另外本人有一個Flutter微信交流群,歡迎小夥伴加入,共同探討Flutter的問題,本人微訊號:zdl1994328,期待與你的交流與切磋。

相關文章