Flutter | 定義一個通用的多功能網絡請求 Widget

首先道個歉,最近公司很忙,又遇上十一假期,因此鴿了將近半個月。git

不過,後續仍是會每週最少更新兩篇的!github

那提及網絡請求的控件,咱們首先是否是會想起 FutureBuilder微信

FutureBuilder 給咱們封裝好了網絡請求中的各類狀態。markdown

若是沒有了解過,那麼能夠看我這篇文章:Flutter - FutureBuilder 異步UI神器。網絡

這篇文章是早期寫的,有些地方寫的有些問題,但不重要!主要了解一下 FutureBuilder 的狀態就能夠了。異步

本篇文章中只是提供一種思路,歡迎一塊兒探討,也歡迎不吝賜教!async

效果以下。ide

首先是沒有開啓服務的狀況:工具

能夠看到所有都是錯誤的信息,ui

而後開啓服務:

1. 先定義一個通用的網絡請求

那既然是網絡請求,那首先咱們要定義一個通用的網絡請求方法。

每一家後臺 API 的風格都不同,有的是 RSETful,有的是咱們最熟悉的 GET、POST。

這裏就以 GET 爲例,API 接口爲 GitHub - 網易雲音樂 Node.js API service。

網絡請求使用的是 Dio,先建立一個 NetUtils.dart

初始化代碼:

static Dio _dio;

static void init() async {
  Directory tempDir = await getTemporaryDirectory();
  String tempPath = tempDir.path;
  CookieJar cj = PersistCookieJar(dir: tempPath);
  _dio = Dio(BaseOptions(baseUrl: 'http://127.0.0.1:3000'))
    ..interceptors.add(CookieManager(cj))
    ..interceptors.add(LogInterceptor(responseBody: true, requestBody: true));
}
複製代碼

在 runApp 前面調用即完成初始化。

接着定義一個通用的網絡請求:

static Future<Response> _get(
  BuildContext context,
  String url, {
    Map<String, dynamic> params,
  }) async {
  Loading.showLoading(context);
  try {
    return await _dio.get(url, queryParameters: params);
  } on DioError catch (e) {
    if (e.response is Map) {
      return Future.value(e.response);
    } else {
      return Future.error(0);
    }
  } finally {
    Loading.hideLoading(context);
  }
}
複製代碼

這裏代碼很簡單,方法須要傳入三個參數:

  1. context:用於 showLoading
  2. url:API 地址
  3. params:該網絡請求的參數,能夠爲空

方法內部咱們捕獲了 DioError,而後判斷接口是否還返回了正常的內容。

例如:狀態碼不爲2xx,可是仍然返回了數據,這樣 Dio 是會拋出 DioError 的,須要咱們本身捕獲來處理。

若是返回了正常的數據,那咱們仍是返回回去,若是不是正常的數據,則直接拋出 Future.error(0)

使用該通用方法:

/// 新碟上架
static Future<AlbumData> getAlbumData(
  BuildContext context, {
    Map<String, dynamic> params = const {
      'offset': 0,
      'limit': 10,
    },
  }) async {
  var response = await _get(context, '/top/album', params: params);
  return AlbumData.fromJson(response.data);
}
複製代碼

咱們就能夠像這樣來使用剛纔定義好的方法,也方便咱們後續定義一個通用的 FutureBuilder

2. 確認網絡請求控件所須要的功能

咱們從最開始的圖中明顯能看出來的,實際上是有三個功能:

  1. 請求數據並顯示 Loading
  2. 正常時返回正常數據,錯誤時返回錯誤 Widget
  3. 錯誤 Widget 能夠點擊從新請求

然鵝,細心的同窗也發現問題了。

咱們在網絡請求中添加了一個 Loading,並且須要一個 BuildContext。

咱們都知道,是不能在 initState() 方法中去使用這個 BuildContext 的。

因此,咱們還要進行一個 第一幀回調

@override
void initState() {
  super.initState();
  WidgetsBinding.instance.addPostFrameCallback((call) {
    _request();
  });
}
複製代碼

這樣就完成了咱們的需求調研。

3. 編寫通用網絡請求控件

說的是一個通用的網絡請求控件,其實就是把 FutureBuilder 封裝一層。

請求數據並顯示 Loading

可是,這裏也有一個問題:

咱們在最開始定義網絡請求工具類的時候,每個網絡請求都是一個方法,而每一個方法中都有或者沒有參數。

咱們也知道,FutureBuilder 須要傳入一個 Future,那這可怎麼辦?

並且咱們不能在使用該控件的時候調用網絡請求方法,由於網絡請求中封裝了一個 Loading,這個 Loading 須要 BuildContext

既然如此,那咱們只能傳入方法(Function)了:

typedef ValueWidgetBuilder<T> = Widget Function(
  BuildContext context,
  T value,
);


final ValueWidgetBuilder<T> builder;
final Function futureFunc;
final Map<String, dynamic> params;

CustomFutureBuilder({
  @required this.futureFunc,
  @required this.builder,
  this.params,
});
複製代碼

這樣,咱們就能夠在 第一幀回調 中來調用該網絡請求了,這樣一箭雙鵰:

既不用在使用該控件的時候調用方法,又避免了 Loading 使用 BuildContext 報錯的問題。

Future<T> _future;

@override
void initState() {
  super.initState();
  WidgetsBinding.instance.addPostFrameCallback((call) {
    _request();
  });
}

void _request() {
  setState(() {
    if (widget.params == null)
      _future = widget.futureFunc(context);
    else
      _future = widget.futureFunc(context, params: widget.params);
  });
}
複製代碼

首先咱們定義了一個 Future,而後在 第一幀回調 中初始化該 Future 就能夠了。

正常時返回正常數據,錯誤時返回錯誤 Widget

這就須要咱們封裝好的網絡請求和 FutureBuilder 有一個互動了,

網絡請求的邏輯以下:

這樣正好就能夠對應 FutureBuilder 的幾種狀態:

  1. 網絡請求 -> ConnectionState.noneConnectionState.waiting
  2. 顯示Loading -> ConnectionState.active
  3. 請求結束 -> ConnectionState.done
  4. 是否有數據(不管對錯)-> snapshot.hasData
  5. 拋出錯誤 -> snapshot.hasError

瞭解這些以後,咱們就能夠寫出代碼:

Widget build(BuildContext context) {
    return _future == null
        ? Container(
            alignment: Alignment.center,
            height: ScreenUtil().setWidth(200),
            child: CupertinoActivityIndicator(),
          )
        : FutureBuilder(
            future: _future,
            builder: (context, snapshot) {
              switch (snapshot.connectionState) {
                case ConnectionState.none:
                case ConnectionState.waiting:
                case ConnectionState.active:
                  return Container(
                    alignment: Alignment.center,
                    height: ScreenUtil().setWidth(200),
                    child: CupertinoActivityIndicator(),
                  );
                case ConnectionState.done:
                  if (snapshot.hasData) {
                    return widget.builder(context, snapshot.data);
                  } else if (snapshot.hasError) {
                    return NetErrorWidget(
                      callback: () {
                        _request();
                      },
                    );
                  }
              }
              return Container();
            },
          );
  }
複製代碼

首先判斷 _future 是否爲 null,若是爲空,那麼則表示尚未初始化該 Future,

我的建議這個時候返回本身定義好的加載中 Widget,由於後續在網絡請求中的時候也返回該 Widget,這樣不會顯得亂。

而後在 ConnectionState.done 中判斷是否存在數據,若是有的話,就顯示傳進來的 Widget。

若是返回錯誤,則返回錯誤的 Widget。

錯誤 Widget 能夠點擊從新請求

這個邏輯其實很簡單,在我最開始說的文章中有講解一部分。

那就是何時 FutureBuilder 會從新建立?

@override
void didUpdateWidget(FutureBuilder<T> oldWidget) {
  super.didUpdateWidget(oldWidget);
  if (oldWidget.future != widget.future) {
    if (_activeCallbackIdentity != null) {
      _unsubscribe();
      _snapshot = _snapshot.inState(ConnectionState.none);
    }
    _subscribe();
  }
}
複製代碼

能夠很清晰的看到,在兩次 Future 不同的狀況下會從新走一遍流程。不然是不會走的。

而咱們在上面也已經定義好了,由於傳進來的是 Function 和 Params,咱們能夠隨時從新建立該 Future:

void _request() {
  setState(() {
    if (widget.params == null)
      _future = widget.futureFunc(context);
    else
      _future = widget.futureFunc(context, params: widget.params);
  });
}
複製代碼

錯誤 Widget 的點擊事件寫成這個就 ok 了,這樣就從新建立了該 FutureBuilder,也就是從新請求了。

總結

代碼的話,我就不傳上去了,由於這個只適用於一部分。

我這裏只是提供一種思路,我的以爲仍是不錯的。

若是有什麼想法的話,歡迎一塊兒探討,不吝賜教!

另我我的建立了一個「Flutter 交流羣」,能夠添加我我的微信 「17610912320」來入羣。

推薦閱讀:

Flutter | 超簡單仿微信QQ側滑菜單組件

Flutter | 一個超級酷炫的登陸頁是怎樣煉成的

Flutter | WReorderList 一個能夠指定兩個item互換位置的組件

Flutter | 如何實現一個水波紋擴散效果的 Widget

相關文章
相關標籤/搜索