Flutter 入坑指南

Flutter 入坑指南

Flutter + Dio + Fish_Redux

目前網(wǎng)上只有單個的介紹,沒有整合使用的demo桨踪,那就由我來吧老翘。要給我點星星啊!F糖汀墓怀!

代碼地址: https://github.com/liangwei0101/liang_flutter_demo

本次將實現(xiàn)的界面如下:

效果

之前做的一個管理端界面(spring boot + vue )數(shù)據(jù),下圖網(wǎng)址為:http://101.132.124.171:8000/about

管理端數(shù)據(jù)

看完能懂啥捺疼?

看完能大概知道目錄結構,Dio基礎請求的封裝, Fish_Redux狀態(tài)的使用,和一些亂七八糟的坑。筆者也是差不多摸石頭過河,這些天也是踩了一些坑。

選型

因為組人員有限餐曹,所以沒有過多的精力去維護一套IOS和Android代碼饱狂。經過和組里的人員套論以后,決定放棄選用React Native婆咸,選用Flutter倔丈。因為組人人員都是React Native和Flutter都沒有接觸過,差不多都是從0開始,然后覺得Flutter可能是以后的趨勢捎泻,性能還行厌丑,就開始搞了。

搭環(huán)境

此處省略一萬字渔呵,按Flutter官網(wǎng)來即可:Flutter中文網(wǎng)怒竿。

項目結構

項目結構
  • api 用來存放和后端接口請求
  • apiModel 用來存放后端返回的json對應的model對象集合
  • asset 用來存放一些例如圖片啥的資源
  • component 為能抽出來的單個的組件的集合
  • page 為單個的頁面的集合
  • utils 為功能的方法和類的集合
  • pubspec.yaml是管理包的地方

demo需要用的依賴包

dependencies:
  json_annotation: ^2.4.0 # json序列化和反序列化用的
  dio: ^2.1.7 # http請求用的
  fish_redux: ^0.2.2 # 狀態(tài)管理用的
  flutter:
    sdk: flutter
        
# 開發(fā)環(huán)境的依賴
dev_dependencies:
  flutter_test:
    sdk: flutter
  build_runner: ^1.5.2 # 開發(fā)時生成model用的
  json_serializable: ^3.0.0 # json序列化和反序列化用的       

為啥要使用狀態(tài)管理

  1. 為了View(界面)和VM(邏輯)解耦。
  2. 為了代碼的可維護性扩氢。
  3. 為了以后的可擴展性耕驰。

使用 fish_redux前戲

  1. 在狀態(tài)管理的選型中,有bloc和redux录豺,后面選了阿里的 fish_redux朦肘。一是因為相信阿里的團隊,二是看了他們的狀態(tài)管理和其他的對比(狀態(tài)管理對比)双饥,三是它的星星是最多的媒抠。
  2. 官方推出了一個插件:FishReduxTemplate,分別有vscode 和Android studio咏花,不過Android studio創(chuàng)建了文件以后趴生,過許久以后才會刷新阀趴,有點小慢。
  3. 開始有點不是很理解他的意思苍匆,接觸和看文檔以后發(fā)現(xiàn)他的這個和Vuex狀態(tài)有點類似刘急。
  4. 看的知乎上的一個解釋(不知道為啥,翻墻才搜到了知乎的浸踩,講的挺好的叔汁,為啥某度的搜索引擎就沒有出來),覺得下面幾個還是很重要的:View检碗、Effect据块、Reducer組件三要素。 (掘金解釋)折剃、(知乎解釋

使用Flutter的坑

  1. fish_redux去github上找官方的example瑰钮,我是沒有跑起來。是因為我的flutter的版本是1.74的微驶。報錯如下浪谴,原因也挺簡單,是因為咸魚團隊的Action和官方的Action的類名重復了因苹。解決辦法:降級苟耻,將flutter版本降至:v1.5.4-hotfix.2及以下。
Error: 'Action' is imported from both 'package:flutter/src/widgets/actions.dart' and 'package:fish_redux/src/redux/basic.dart'.
  1. fish_redux 創(chuàng)建以后effect文件報錯扶檐,錯誤如下凶杖。
The function '_onAction' has type 'void Function(Action, Context<DemoState>)' that isn't of expected type 'dynamic Function(dynamic, Context<DemoState>)'. This means its parameter or return type does not match what is expected.
  1. 在開始模式調用API的時候,遇上一個大坑款筑,因為之前做JAVA 智蝠、C#、Vue居多奈梳,移動端偏少杈湾,然后在使用Dio封裝后,開始連接后臺API攘须,報錯漆撞。原因也是找了一會,是因為于宙,我自己開的后臺去調試的浮驳,開的node的后臺和java的后臺都不行,BaseUrl寫的是:localhost捞魁,后面看見有大兄弟說至会,linux系統(tǒng)下,被映射的端口是啥10.20啥的谱俭,我猜這個dart的底層虛擬機是linux的奉件,我果斷換了遠程的IP的API宵蛀,立馬來事。

    // 錯誤是這個瓶蚂,
    DioError [DioErrorType.DEFAULT]
    
    // 官網(wǎng)的解釋如下
    enum DioErrorType {
      /// Default error type, usually occurs before connecting the server.
      DEFAULT,
    }
    
  1. 沒啥文檔,很多靠猜和網(wǎng)上的例子宣吱。有些不是很理解窃这,也沒有人講解啥的,還是有點痛苦的征候。

fish_redux幾個重要的概念

  • Action定義一種行為杭攻,可以攜帶信息,發(fā)往Store疤坝。換言之Store發(fā)生改變須由Action觸發(fā)兆解,F(xiàn)ish redux 有以下約定:Action 包含兩個字段type和payload;推薦寫法是在action.dart里定義一個type枚舉類和一個ActionCreator類跑揉,這樣有利于約束payload的類型锅睛。

  • Reducer/Effect這兩個函數(shù)都是用來處理數(shù)據(jù)的函數(shù),Reducer是純函數(shù)響應Action對Store數(shù)據(jù)進行改變历谍。Effect用來處理來自視圖的意圖现拒,如點擊事件,發(fā)起異步請求望侈,這些有副作用的操作印蔬。

  • Page可以看成是一個容器,它用來配置聚合State脱衙,Effect侥猬,Reduce,View捐韩,Dependencies等退唠。

  • Adapter(可選),這個不咋懂荤胁。

  • view 解耦出來的純頁面铜邮。

首先創(chuàng)建一個簡單的頁面

使用模板新建后

使用咸魚提供插件新建后,將出現(xiàn)上圖的文件寨蹋。

  • action松蒜,里面是定義一些動作,給view或者effect用的已旧。
import 'package:fish_redux/fish_redux.dart';
import '../../../apiModel/user.dart';

//TODO replace with your own action
enum BusinessAction { query }

class BusinessActionCreator {

  static Action updateAction(List<User> userList){
    print('由effect請求后dispatch的值來了');
    return Action(BusinessAction.query, payload: userList);
  }
}
  • effect 里頭是一些事件秸苗,發(fā)起異步請求等
import 'package:fish_redux/fish_redux.dart';
import 'action.dart';
import 'state.dart';
import '../../../api/user.dart';

Effect<BusinessState> buildEffect() {
  return combineEffects(<Object, Effect<BusinessState>>{
    Lifecycle.initState: _init,
  });
}

void _init(Action action, Context<BusinessState> ctx) {
  // Http請求
  UserApi.getUser().then((value){
    ctx.dispatch(BusinessActionCreator.updateAction(value));
  });
}
  • page 是配置聚合State,Effect运褪,Reduce惊楼,View
import 'package:fish_redux/fish_redux.dart';

import 'effect.dart';
import 'reducer.dart';
import 'state.dart';
import 'view.dart';

class BusinessPage extends Page<BusinessState, Map<String, dynamic>> {
  BusinessPage()
      : super(
            initState: initState,
            effect: buildEffect(),
            reducer: buildReducer(),
            view: buildView,
            dependencies: Dependencies<BusinessState>(
                adapter: null,
                slots: <String, Dependent<BusinessState>>{
                }),
            middleware: <Middleware<BusinessState>>[
            ],);
}
  • reducer 修改值的地方
import 'package:fish_redux/fish_redux.dart';

import 'action.dart';
import 'state.dart';

Reducer<BusinessState> buildReducer() {
  return asReducer(
    <Object, Reducer<BusinessState>>{
      BusinessAction.query: _onQuery,
    },
  );
}

BusinessState _onQuery(BusinessState state, Action action) {
  print('我是值真正更新的地方');
  final BusinessState newState = state.clone();
  newState.userList = action.payload;
  return newState;
}
  • state 初始化玖瘸,存屬性的地方
import 'package:fish_redux/fish_redux.dart';
import '../../../apiModel/user.dart';

class BusinessState implements Cloneable<BusinessState> {

  BusinessState({this.userList});

  List<User> userList = new List<User>();

  @override
  BusinessState clone() {
    return BusinessState();
  }
}

// 初始化
BusinessState initState(Map<String, dynamic> args) {
  List<User> tempList = new List<User>();
  User user = new User();
  user.no = 0;
  user.name = '梁二狗';
  user.email = '1@qq.com';
  tempList.add(user);
  return BusinessState(userList: tempList);
}
  • view 解耦出來的界面
import 'package:fish_redux/fish_redux.dart';
import 'package:flutter/material.dart';

import 'action.dart';
import 'state.dart';

Widget buildView(BusinessState state, Dispatch dispatch, ViewService viewService) {

  var buildListView = ListView.builder(
    itemCount: state.userList.length,
    itemBuilder: (BuildContext context, int index) {
      return Card(
        child: ListTile(
          leading: FlutterLogo(),
          title: Text('編號:' +
              state.userList[index].no.toString() +
              '名稱:' +
              state.userList[index].name.toString() +
              '郵箱:' +
              state.userList[index].email.toString()),
        ),
      );
    },
  );
  // 中間頁面的視圖
  return Scaffold(appBar: null, body: Center(child: buildListView));
}

Http的封裝

在上面,我們使用的Http請求檀咙,是用Dio封裝過一次的雅倒,代碼如下:

import 'package:dio/dio.dart';
import 'dart:io';
import 'dart:async';

/*
 * 封裝 restful 請求
 *
 * GET、POST弧可、DELETE蔑匣、PATCH
 * 主要作用為統(tǒng)一處理相關事務:
 *  - 統(tǒng)一處理請求前綴;
 *  - 統(tǒng)一打印請求信息棕诵;
 *  - 統(tǒng)一打印響應信息裁良;
 *  - 統(tǒng)一打印報錯信息;
 */

class HttpUtils {

  /// global dio object
  static Dio dio;

  /// default options
  static const String API_PREFIX = 'http://101.132.124.171:8080/demo-1.0/api';
  static const int CONNECT_TIMEOUT = 10000;
  static const int RECEIVE_TIMEOUT = 3000;

  /// http request methods
  static const String GET = 'get';
  static const String POST = 'post';
  static const String PUT = 'put';
  static const String PATCH = 'patch';
  static const String DELETE = 'delete';

  static Future<dynamic> request (
      String url,
      { data, method }) async {

    data = data ?? {};
    method = method ?? 'GET';

    /// restful 請求處理
    data.forEach((key, value) {
      if (url.indexOf(key) != -1) {
        url = url.replaceAll(':$key', value.toString());
      }
    });

    Dio dio = createInstance();
    /// 打印請求相關信息:請求地址校套、請求方式价脾、請求參數(shù)
    print('請求地址:【' + dio.options.baseUrl + url + '】');
    print('請求參數(shù):' + data.toString());

    var result;

    try {
      Response response = await dio.request(url, data: data, options: new Options(method: method));
      result = response.data;

      /// 打印響應相關信息
      print('響應數(shù)據(jù)成功!');
    } on DioError catch (e) {
      /// 打印請求失敗相關信息
      print('請求出錯:' + e.toString());
    }

    return result;
  }

  /// 創(chuàng)建 dio 實例對象
  static Dio createInstance () {
    if (dio == null) {
      /// 全局屬性:請求前綴笛匙、連接超時時間侨把、響應超時時間
      BaseOptions option = new BaseOptions(
          baseUrl: API_PREFIX,
          connectTimeout: CONNECT_TIMEOUT,
          receiveTimeout: RECEIVE_TIMEOUT,

          headers: {
            "user-agent": "dio",
            "api": "1.0.0"
          },

          contentType: ContentType.JSON,
          // Transform the response data to a String encoded with UTF8.
          // The default value is [ResponseType.JSON].
          responseType: ResponseType.plain
      );
      dio = new Dio(option);
    }

    return dio;
  }

  /// 清空 dio 對象
  static clear () {
    dio = null;
  }
}

json 字符串解析

在上面http中,我們的數(shù)據(jù)為json數(shù)組妹孙,數(shù)據(jù)如下座硕,我們使用 json_serializable 將json串轉化成List數(shù)組。

[{"no":10,"name":"12","email":"12@qq.com"},{"no":11,"name":"11","email":"1@qq.com"},{"no":12,"name":"asdf","email":"asf@asdf.com"},{"no":14,"name":"li","email":"22@qq.com"},{"no":15,"name":"w","email":"1@qq.com"},{"no":16,"name":"梁","email":"17@qq.com"},{"no":17,"name":"李","email":"1@qq.com"},{"no":18,"name":"li","email":"2@qq.com"},{"no":112,"name":"里","email":"56@qq.com"},{"no":122,"name":"1","email":"1@qq.com"}]

他這個東西還是有點復雜的涕蜂,沒有java或者C#轉json方便华匾。我們建立對應的Model的文件user.dart,示例代碼如下:

import 'package:json_annotation/json_annotation.dart';

part 'user.g.dart';

@JsonSerializable()
class User {
  User({this.no, this.name, this.email});

  int no;
  String name;
  String email;

  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);

  Map<String, dynamic> toJson() => _$UserToJson(this);
}

開始建立文件的時候机隙,最后兩行會報錯蜘拉。然后我們使用如下命令行,將會生成一個"user.g.dart"有鹿,然后就能使用了旭旭。

flutter packages pub run build_runner watch 

在json串轉List的時候,找了很久葱跋,終于找到啦持寄,這樣寫:

 // json 為后臺返回的json數(shù)組串
 var usersJson = json.decode(result);
 List<User> userList = (usersJson as List).map((i)=>User.fromJson(i)).toList();

就寫到這里吧。希望能幫到大家哦娱俺,還有稍味,記得給我點星星,寫文不容易啊荠卷。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末模庐,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子油宜,更是在濱河造成了極大的恐慌掂碱,老刑警劉巖怜姿,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異疼燥,居然都是意外死亡沧卢,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門醉者,熙熙樓的掌柜王于貴愁眉苦臉地迎上來但狭,“玉大人,你說我怎么就攤上這事湃交∈炜眨” “怎么了藤巢?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵搞莺,是天一觀的道長。 經常有香客問我掂咒,道長才沧,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任绍刮,我火速辦了婚禮温圆,結果婚禮上,老公的妹妹穿的比我還像新娘孩革。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布寞焙。 她就那樣靜靜地躺著吕粗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪饱搏。 梳的紋絲不亂的頭發(fā)上非剃,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天,我揣著相機與錄音推沸,去河邊找鬼备绽。 笑死,一個胖子當著我的面吹牛鬓催,可吹牛的內容都是我干的肺素。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼宇驾,長吁一口氣:“原來是場噩夢啊……” “哼压怠!你這毒婦竟也來了?” 一聲冷哼從身側響起飞苇,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤菌瘫,失蹤者是張志新(化名)和其女友劉穎蜗顽,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體雨让,經...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡雇盖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了栖忠。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片崔挖。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖庵寞,靈堂內的尸體忽然破棺而出狸相,到底是詐尸還是另有隱情,我是刑警寧澤捐川,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布脓鹃,位于F島的核電站,受9級特大地震影響古沥,放射性物質發(fā)生泄漏瘸右。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一岩齿、第九天 我趴在偏房一處隱蔽的房頂上張望太颤。 院中可真熱鬧,春花似錦盹沈、人聲如沸龄章。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽做裙。三九已至,卻和暖如春歌亲,著一層夾襖步出監(jiān)牢的瞬間菇用,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工陷揪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留惋鸥,地道東北人。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓悍缠,卻偏偏與公主長得像卦绣,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子飞蚓,可洞房花燭夜當晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內容