美文网首页FlutterFlutter
flutter mvvm 模式 2020-01更新

flutter mvvm 模式 2020-01更新

作者: lzyprime | 来源:发表于2020-01-16 22:26 被阅读0次

    lzyprime 博客

    λ:

    这篇文章是对 “ 19年11月的文章 ”的补充, 就文章里用到的 rxdart ^0.22.2 与最新版本 rxdart ^0.23.1 出现了大改动,以至于原文中的代码在最新插件下不可用。。。

    这次插件更新, 相当于给原生 Stream 类添加了一组拓展方法,而不是像以前一样用 Observable 类再包一层达到效果。(详情rxdart的更新日志)

    也就是说,添加完插件后,你可以在任意Stream 调用一系列方法。如之前demo里用到的doOnData doOnDone doOnListen zipWith。还有更多内容可看官网详情

    github 仓库: flutter_demos 分支 mvvm_demo

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

    mvvm

    mvvm 在 原文章 大概介绍过,而且架构这种东西无关语言。具体是什么,相信wiki说的比我好。

    flutter项目 mvvm

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

    pubspec.png

    2. view, 构建UI,数据来源于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,
    
                /// 根据 state 的值,按钮显示不同内容。
                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 {
      /// 直接将网络请求的 Future 对象包装成 Stream 返回
      /// Stream.fromFuture 等构造方法。更多细节参考官方文档
      /// 因为是 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() 函数,可以通知本对象数据的正在使用者们。
    /// 如 state 变量,在改变后调用 notifyListeners(), UI根据值重新构建登录按钮显示内容
    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, 并通知监听者
        /// 如果用rxDart插件,可作为doOnListen参数的函数体
        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 实现与view 层的绑定。
          /// Provider 是对 [InheritedWidget] 封装。因此我们才能实现调用notifyListeners() 时,通知子树重新构建
          /// 当然你也可以一个插件也不用,自己封装[InheritedWidget]
          home: ChangeNotifierProvider(
            create: (_) => LoginViewModel(),
            child: LoginWidget(),
          ),
        );
      }
    }
    
    

    效果图

    • state == 0, 未请求

    state0.png
    • state == 1, 显示加载

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

    state2.png
    • state == 3, 请求失败

    state3.png

    ~λ:

    这只是个demo, 主要看三者怎么拆以及怎么拼装,实际请求肯定还要判好多问题。

    实际应用时,model组织api请求,而一个页面可能涉及多个模块的请求,因此,一个 viewModel 包含多个 model 类也有可能。但尽量避免这种情况,做好请求分类

    相关文章

      网友评论

        本文标题:flutter mvvm 模式 2020-01更新

        本文链接:https://www.haomeiwen.com/subject/lrpqzctx.html