Flutter 入門與實戰(二十五):使用 Post 請求增加動態

島上碼農發表於2021-07-08

「本文已參與好文召集令活動,點選檢視:後端、大前端雙賽道投稿,2萬元獎池等你挑戰!

前言

之前幾篇分別介紹了利用 Dio 完成後臺資料的獲取、刪除和編輯,相關文章如下:

本篇介紹如何使用 Post 請求建立動態資料,本篇相關知識點如下:

  • 導航欄右側的 actions
  • 路由匹配先後順序,優先匹配先定義的路由
  • 新增與編輯的異同
  • post 請求
  • 防重點選

導航欄增加操作按鈕

在導航欄右上角增加操作按鈕是十分常見的情況,Flutter 的 AppBar 元件提供了actions 引數,用於設定右上角的操作按鈕,actions 是一個 List<Widget>,意味著可以新增多個元件。本例增加了一個圖示按鈕,用於進入新增動態頁面:

appBar: AppBar(
    title: Text('動態', style: Theme.of(context).textTheme.headline4),
    actions: [
      IconButton(
          icon: Icon(Icons.add),
          onPressed: () {
            RouterManager.router
                .navigateTo(context, RouterManager.dynamicAddPath);
          })
    ],
    brightness: Brightness.dark,
  ),
    
  //...
複製程式碼

RouterManager.dynamicAddPath是新增頁面的路由路徑常量,為 /dynamics/add。但是,我們會發現這個路由規則和/dynamics/:id ,即動態詳情的實際是可以匹配的,一不小心就跳到了詳情頁而不是新增頁面,這個時候該怎麼處理呢?

Fluro路由匹配的先後次序

Fluro 的路由匹配次序是按照定義路由的先後次序進行匹配的,因此需要把更具體的路由放置在範圍匹配的前面,即定義新增頁面路由時要放置在詳情路由的前面。這點實際上和 React Router類似,匹配到了就跳出規則,不再往下匹配。因此,在使用 Fluro 的時候需要注意定義路由的次序,否則可能會導致路由跳轉不正確。

新增頁面

新增頁面的表單和編輯頁面一樣,只是沒有從後臺讀取資料填充表單內容。我們先直接複製之前的 dynamic_edit.dart 檔案,並重新命名為 dynamic_add.dart,同時將DynamicEdit 替換為 DynamicAdd。與編輯頁面的不同之處在於:

  1. _formData 需要提前定義,如下所示。
Map<String, Map<String, Object>> _formData = {
  'title': {
    'value': '',
    'controller': TextEditingController(),
    'obsecure': false,
  },
  'content': {
    'value': '',
    'controller': TextEditingController(),
    'obsecure': false,
  },
  'imageUrl': {
    'value': '',
    'controller': TextEditingController(),
    'obsecure': false,
  },
};
複製程式碼
  1. _getFormWidgets構建表單元件時無需返回載入提示,直接返回表單即可。
  2. 網路請求修改為 Post 請求。
_handleSubmit() async {
  if ((_formData['title']['value'] as String).trim() == '') {
    Dialogs.showInfo(this.context, '標題不能為空');
    return;
  }

  if ((_formData['content']['value'] as String).trim() == '') {
    Dialogs.showInfo(this.context, '內容不能為空');
    return;
  }

  if ((_formData['imageUrl']['value'] as String).trim() == '') {
    Dialogs.showInfo(this.context, '圖片連結不能為空');
    return;
  }

  try {
    Map<String, String> newFormData = {};
    _formData.forEach((key, value) {
      newFormData[key] = value['value'];
    });
    var response = await DynamicService.post(newFormData);
    if (response.statusCode == 200) {
      Dialogs.showInfo(context, '新增成功');
    } else {
      Dialogs.showInfo(this.context, response.statusMessage);
    }
  } on DioError catch (e) {
    Dialogs.showInfo(this.context, e.message);
  } catch (e) {
    Dialogs.showInfo(this.context, e.toString());
  }
}
複製程式碼

我們會發現有很多方法是類似的,比如表單、表單校驗以及編輯時表單資料處理。因此這些共同的地方可以進行封裝,但是需要考慮實際業務新增和編輯的表單內容可能不同,比如某些欄位不允許編輯等,因此考慮共通性,我們做更通用的處理,提取一個 dynamic_form.dart 類,將通用的部分統一封裝進去,提高複用性。

class DynamicForm extends StatelessWidget {
  final Map<String, Map<String, Object>> formData;
  final Function handleTextFieldChanged;
  final ValueChanged<String> handleClear;
  final String buttonName;
  final Function handleSubmit;
  const DynamicForm(this.formData, this.handleTextFieldChanged,
      this.handleClear, this.buttonName, this.handleSubmit,
      {Key key})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Column(children: _getForm(context)),
    );
  }

  List<Widget> _getForm(BuildContext context) {
    List<Widget> widgets = [];
    formData.forEach((key, formParams) {
      widgets.add(FormUtil.textField(key, formParams['value'],
          controller: formParams['controller'] ?? null,
          hintText: formParams['hintText'] ?? '',
          prefixIcon: formParams['icon'],
          onChanged: handleTextFieldChanged,
          onClear: handleClear));
    });

    widgets
        .add(ButtonUtil.primaryTextButton(buttonName, handleSubmit, context));

    return widgets;
  }
}
複製程式碼

封裝完之後,編輯和新增頁面的_formData需要增加將構建 TextField 的欄位補齊,而不是之前那樣寫死,這樣更靈活。並且,程式碼將更為簡潔,以新增頁面為例。

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text('新增動態'),
      brightness: Brightness.dark,
    ),
    body: DynamicForm(
      _formData,
      _handleTextFieldChanged,
      _handleClear,
      '提交',
      _handleSubmit,
    ),
  );
}
複製程式碼

防重提交

除錯過程中發現,點選提交按鈕時儲存的資料會有多條。Flutter 如何防重提交? image.png

一般防重提交的處理方法一種是點選後禁用,等待網路請求結果返回後再啟用按鈕。另外一種方式就是增加Loading蒙層,在網路請求沒結束前使用蒙層將頁面遮擋,從而避免操作表單及按鈕。這裡我們採用第二種方式,通過蒙層的方式指示可以避免操作表單,也能夠給出載入指示。

在 pub 上提供了一個 flutter_easyloading 的外掛,可以滿足這要求。具體使用是在main.dart的 MatertialApp 的 builder 引數傳遞EasyLoading.init()方法,初始化一個全域性的EasyLoading物件,之後就可以在頁面中隨時呼叫了。顯示的時候呼叫 showXXX 方法,消失的時候呼叫 dismiss 方法,可以設定多種 loading 樣式,也支援自定義 loading 元件以及自定義引數,具體可以參考:flutter_easyloading。我們在提交前顯示EasyLoading,接收到資料後移除EasyLoading即可。

_handleSubmit() async {
  //...校驗程式碼
	EasyLoading.showInfo('請稍候...', maskType: EasyLoadingMaskType.black);
  //...網路請求程式碼
  EasyLoading.dismiss();
}
複製程式碼

總結

本篇介紹了新增資料頁面的示例,同時對於編輯和新增的頁面重複部分通過封裝共用的表單元件簡化了頁面結構和提高複用性。考慮實際操作的重複點選,還引入了 flutter_easyloading 來實現載入蒙層的效果。原始碼已提交至:網路章節原始碼。注意執行時拉取最新的後臺程式碼執行,以免找不到後臺服務載入不了資料。

相關文章