flutter mvvm 模式 2020-01更新

lzyprime 博客

λ:

這篇文章是對 “ 19年11月的文章 ”的補充, 就文章里用到的 rxdart ^0.22.2 與最新版本 rxdart ^0.23.1 出現(xiàn)了大改動,以至于原文中的代碼在最新插件下不可用。。。

這次插件更新, 相當于給原生 Stream 類添加了一組拓展方法,而不是像以前一樣用 Observable 類再包一層達到效果。(詳情rxdart的更新日志)

也就是說,添加完插件后,你可以在任意Stream 調(diào)用一系列方法。如之前demo里用到的doOnData doOnDone doOnListen zipWith。還有更多內(nèi)容可看官網(wǎng)詳情

github 倉庫: flutter_demos 分支 mvvm_demo

git clone -b mvvm_demo https://github.com/lzyprime/flutter_demos.git

mvvm

mvvm 在 原文章 大概介紹過,而且架構(gòu)這種東西無關(guān)語言。具體是什么,相信wiki說的比我好。

flutter項目 mvvm

1. 添加插件:provider ^4.0.1rxdart ^0.23.1 PS: 如果用不到 rxdart 的東西,可以不要這個插件)。 在 pubspec.yaml 文件

pubspec.png

2. view, 構(gòu)建UI,數(shù)據(jù)來源于viewModel

// file path: package:flutter_mvvm_demo/views/login_widget.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import 'package:flutter_mvvm_demo/viewModels/login_view_model.dart';

class LoginWidget extends StatelessWidget {
  @override
  build(BuildContext context) {
    final provider = Provider.of<LoginViewModel>(context);
    return Scaffold(
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          TextField(
            controller: provider.usernameController,
            decoration: InputDecoration(labelText: "username"),
          ),
          TextField(
            controller: provider.passwordController,
            decoration: InputDecoration(labelText: "password"),
          ),
          RaisedButton(
            onPressed: provider.login,

            /// 根據(jù) state 的值,按鈕顯示不同內(nèi)容。
            child: provider.state == 0
                ? Text("login")
                : provider.state == 1
                    ? CircularProgressIndicator()
                    : provider.state == 2
                        ? Icon(Icons.done)
                        : Icon(Icons.cancel),
          ),
        ],
      ),
    );
  }
}

3. model, 請求和處理

// file path: package:flutter_mvvm_demo/models/login_model.dart

class LoginModel {
  /// 直接將網(wǎng)絡(luò)請求的 Future 對象包裝成 Stream 返回
  /// Stream.fromFuture 等構(gòu)造方法。更多細節(jié)參考官方文檔
  /// 因為是 demo 所以用 Future.delayed 模擬請求過程
   Stream<int> login(dynamic data) => Stream.fromFuture(
     Future.delayed(Duration(seconds: 2), () {
          if (data["username"] == "lzyprime" && data["password"] == "123")
            return 0;
          return -1;
        }),
   );
}

4. viewModel

// file path: package:flutter_mvvm_demo/view_models/login_view_model.dart

import 'package:flutter/material.dart';
// import 'package:rxdart/rxdart.dart'; 如果需要,自行添加插件

import 'package:flutter_mvvm_demo/models/login_model.dart';

/// with ChangeNotifier : 通過 notifyListeners() 函數(shù),可以通知本對象數(shù)據(jù)的正在使用者們。
/// 如 state 變量,在改變后調(diào)用 notifyListeners(), UI根據(jù)值重新構(gòu)建登錄按鈕顯示內(nèi)容
class LoginViewModel with ChangeNotifier {
  final _model = LoginModel();
  int state = 0; // 0 未請求,1 正在請求, 2 請求成功, 3請求失敗
  final usernameController = TextEditingController();
  final passwordController = TextEditingController();

  login() {
    final data = {
      "username": usernameController.text,
      "password": passwordController.text,
    };

    /// 不為 0 說明上一條請求未完成,直接退出
    if (state != 0) return;

    /// 開始請求,state 賦值為 1, 并通知監(jiān)聽者
    /// 如果用rxDart插件,可作為doOnListen參數(shù)的函數(shù)體
    state = 1;
    notifyListeners();

    _model.login(data)

        /// rxDart 插件
//        .doOnListen(() {
//      state = 1;
//      notifyListeners();
//    })
        .listen((v) {
      if (v != 0) {
        /// 返回值不為0,請求失敗
        state = 3;
        notifyListeners();
        Future.delayed(Duration(seconds: 1), () {
          state = 0;
          notifyListeners();
        });
      } else {
        /// 返回值為0,請求成功
        state = 2;
        notifyListeners();
        Future.delayed(Duration(seconds: 1), () {
          state = 0;
          notifyListeners();
        });
      }
    });
  }
}


組裝三者

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import 'package:flutter_mvvm_demo/views/login_widget.dart';
import 'package:flutter_mvvm_demo/viewModels/login_view_model.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter mvvm Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),

      /// [ChangeNotifierProvider]。所有的viewModel通過 Provider 實現(xiàn)與view 層的綁定。
      /// Provider 是對 [InheritedWidget] 封裝。因此我們才能實現(xiàn)調(diào)用notifyListeners() 時,通知子樹重新構(gòu)建
      /// 當然你也可以一個插件也不用,自己封裝[InheritedWidget]
      home: ChangeNotifierProvider(
        create: (_) => LoginViewModel(),
        child: LoginWidget(),
      ),
    );
  }
}

效果圖

  • state == 0, 未請求

state0.png
  • state == 1, 顯示加載

state1.png
  • state == 2, 請求成功

state2.png
  • state == 3, 請求失敗

state3.png

~λ:

這只是個demo, 主要看三者怎么拆以及怎么拼裝,實際請求肯定還要判好多問題。

實際應(yīng)用時,model組織api請求,而一個頁面可能涉及多個模塊的請求,因此,一個 viewModel 包含多個 model 類也有可能。但盡量避免這種情況,做好請求分類

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • lzyprime 博客 2020.01.16 更新 yesterday, 收到網(wǎng)友反饋,照抄如下內(nèi)容不好用,希望我...
    lzyprime閱讀 4,119評論 2 7
  • MVVM-live Google的項目: https://github.com/googlesamples/and...
    lbForce閱讀 2,067評論 0 8
  • 2018年2月23日星期五 昨晚九點半多了,兒子來電話說想我了,想找我,讓我去接他,說著說著就哭了,稀里嘩啦的,邊...
    妮妮哲閱讀 209評論 1 6
  • 隨父親出海, 一張網(wǎng), 鋪著生活與茍且, 撒出去, 遮住了眼睛, 也遮住了世界。 總有挑剔的魚兒, 在波光粼粼的水...
    拿什么拯救地球閱讀 127評論 0 0
  • 最近一直在追2017版射雕英雄傳,作為一個80后,雖然小時候看過83版本的射雕,但是83版的射雕太老了,比我的年齡...
    東東歷史閱讀 678評論 0 1

友情鏈接更多精彩內(nèi)容