Flutter 佈局聊天列表頁及網路資料處理

語言: CN / TW / HK

這是我參與11月更文挑戰的第8天,活動詳情檢視:2021最後一次更文挑戰

json 轉模型

``` class ChatModel { final String? name; final String? message; final String? imageUrl; ChatModel({this.name, this.message, this.imageUrl});

//工廠構造方法 factory ChatModel.fromMap(Map map) { return ChatModel( name: map['name'], message: map['message'], imageUrl: map['imageUrl'], ); } } ```

``` final chatMap = { 'name' : 'ChenXi', 'message' : 'Hello!', }; //Map 轉 json final chatJson = json.encode(chatMap); print(chatJson);

// json 轉 Map
final newChatMap = json.decode(chatJson);
print(chatJson);

final chatModel = ChatModel.fromMap(newChatMap as Map);
print(chatModel);

```

這裡我們簡單定義了一個 map 物件,程式碼示例中給出裡 jsonMap 的相互轉換,及 Map 轉模型。我們定義了一個 ChatModel 的模型,添加了 fromMap 方法,由外部傳入一個 Map 型別的物件。開源的也有一些轉模型的框架,這裡我們先自己實現。

Future 使用

void initState() { super.initState(); //獲取網路資料 _getDatas().then((value) { print('$value'); }); } Future<List<ChatModel>> _getDatas() async { //url 連結 final url = Uri.parse('http://rap2api.taobao.org/app/mock/294394/api/chat/list'); //傳送請求 final response = await http.get(url); if (response.statusCode == 200) { //獲取響應資料,並且把 json 轉成 Map final bodyMap = json.decode(response.body); // 取出 bodyMap 中的 chat_list 陣列,通過 map 方法進行遍歷並轉為模型,通過 toList 返回一個模型陣列 final chatList = (bodyMap['chat_list'] as List).map((item) => ChatModel.fromMap(item)).toList(); return chatList; } else { throw Exception('statusCode:${response.statusCode}'); }

這裡 Future 代表未來的資料,Future 需要跟非同步方法結合使用,我們對返回的資料 Future<List<ChatModel>> 進行包裝,Future 有一個 then 方法,then 有一個外部傳入一個閉包屬性,當資料請求完成會呼叫閉包,這裡我們可以拿到 value 的值,也就是模型陣列。

FutureBuilder 非同步渲染

image.png

body: Container( child: FutureBuilder( future: _getDatas(), builder: (BuildContext context, AsyncSnapshot snapshot) { //正在載入 if (snapshot.connectionState == ConnectionState.waiting) { return Container( child: Text('正在載入!'), ); } //載入完成 return ListView( children: snapshot.data.map<Widget>((ChatModel item) { return ListTile( title: Text(item.name as String), subtitle: Container( alignment: Alignment.bottomCenter, height: 25, child: Text(item.message as String, overflow: TextOverflow.ellipsis,), ), leading: Container( width: 44, height: 44, decoration: BoxDecoration( borderRadius: BorderRadius.circular(6.0), image: DecorationImage(image: NetworkImage(item.imageUrl as String)), ), ), ); }).toList(), ); }, ) )

這裡我們通過 FutureBuilder 部件實現網路資料的載入,FutureBuilder 部件支援非同步渲染,future 屬性是 Future 型別的資料。在每次進入微信頁面的時候 builder 方法會被呼叫兩次,ConnectionState.waiting 代表資料正在載入,在這裡我們可以做一些空頁面展示的處理。ConnectionState.done 代表資料載入完成,snapshot.data 就是 _getDatas 方法返回的列表資料,這裡可以進行相關邏輯的處理, 這裡我們展示聊天列表資料。ListView 中我們用 ListTile 部件來作為 cellListTile 包含主標題 title、副標題 subtitle、頭像 leading 等屬性,用起來很方便。

網路請求資料處理

``` //模型陣列 List _datas = [];

void initState() { super.initState(); //獲取網路資料 _getDatas().then((value) { if (!_cancelConnect) { setState(() { _datas = value; }); } }).catchError((e) { _cancelConnect = true; //獲取資料失敗 print(e); }).whenComplete(() { print('資料請求結束'); }).timeout(Duration(seconds: 5)).catchError((timeout) { _cancelConnect = true; print('請求超時 ! $timeout'); }); } ```

這裡我們建立了一個外部成員變數 _datas,用來儲存網路請求的資料,網路請求成功之後呼叫 setState 方法,定義了一個屬性 _cancelConnect 標識網路請求是否取消。catchError 代表請求失敗,whenComplete 代表請求結束,timeout 可以設定超時時間,這裡我們可以用來做一下 loading 頁面的展示及錯誤頁面的展示,

Container( child: _datas.length == 0 ? Center(child: Text('Loading...')) : ListView.builder(itemCount: _datas.length ,itemBuilder: (BuildContext context, int index) { return ListTile( title: Text(_datas[index].name as String), subtitle: Container( alignment: Alignment.bottomCenter, height: 25, child: Text(_datas[index].message as String, overflow: TextOverflow.ellipsis,), ), leading: Container( width: 44, height: 44, decoration: BoxDecoration( borderRadius: BorderRadius.circular(6.0), image: DecorationImage(image: NetworkImage(_datas[index].imageUrl as String)), ), ), ); }), )

對於列表的展示我們這裡換回了 ListView,使用 FutureBuilder,資料量比較大的話會影響載入問題。

頁面保持狀態

class _ChatPageState extends State<ChatPage> with AutomaticKeepAliveClientMixin<ChatPage>

Widget build(BuildContext context) { super.build(context); }

class _RootPageState extends State<RootPage> { int _currentIndex = 0; List <Widget>_pages = [ChatPage(), FriendsPage(), DiscoverPage(), MinePage()]; final PageController _controller = PageController(); @override Widget build(BuildContext context) { return Container( child: Scaffold( body: PageView( //禁止頁面拖拽 physics: NeverScrollableScrollPhysics(), onPageChanged: (int index) { setState(() { _currentIndex = index; }); }, controller: _controller, children: _pages, ),

當我們切換底部 tabBar 的時候,每次進入頁面都會重新載入,這裡我們採用 AutomaticKeepAliveClientMixin 來保持狀態,讓頁面只會被載入一次,以聊天頁面為例,_ChatPageState 後面加上 with AutomaticKeepAliveClientMixin<ChatPage>,並在 build 方法中呼叫 super.build(context)。在 RootPage 中,用 _pages 陣列來儲存底部子頁面,body 使用 PageView 部件,controller 賦值為我們定義的 _controllerchildren 賦值為 _pages