美文网首页FlutterFlutter
Flutter MVP 快速开发

Flutter MVP 快速开发

作者: xiaoZhaoZhao | 来源:发表于2019-12-11 10:54 被阅读0次

    Flutter MVP 快速开发

    练习做的项目,来做一些学习笔记,具体框架的使用方法就不做记录,网上很多,这里只做 MVP 框架搭建,框架借鉴了其他大佬写的一些代码,然后按照自己又做了些调整

    基础配置

    先来看下依赖的库都有哪些,也不一定全部都用到了 ,看自己项目需要吧

    dependencies:
      flutter:
        sdk: flutter
      # The following adds the Cupertino Icons font to your application.
      # Use with the CupertinoIcons class for iOS style icons.
      cupertino_icons: ^0.1.2
      # 路由
      fluro: ^1.5.1
      # 简单数据存储
      shared_preferences: ^0.5.3+4
      # 依赖注入
      get_it: ^3.0.1
      # 吐司
      fluttertoast: ^3.1.3
      # 尺寸适配
      flutter_screenutil: ^0.6.0
      # 二维码条形码扫描
      flutter_qr_reader: ^1.0.3
      # 权限申请
      permission_handler: ^3.0.0
      # 网络请求
      dio: ^2.0.1
      # 手机网络状态监听
      connectivity: ^0.4.3+7
      # 消息总线
      event_bus: ^1.1.0
      # 刷新 加载
      pull_to_refresh: ^1.5.4
      # Rx
      rxdart: ^0.21.0
    
    

    在入口函数 main() 中进行部分框架的初始化:

    void main() {
     Router router = Router();
     Routes.configureRoutes(router);
     Application.router = router;
     Application.setupLocator();
     runApp(MyApp());
    }
    
    1. 1、2、3 行是用来配置项目中路由的(fluro 框架)
    2. Application.setupLocator() 初始化 get_it 依赖注入框架,相当于 Android 中 dagger2
    3. 其中在 Application 中还做了屏幕适配配置

    看下 Application 中的代码吧:

    class Application {
      static Router router;
      static GlobalKey<NavigatorState> globalKey = GlobalKey();
      static SharedPreferences sp;
      static double screenWidth;
      static double screenHeight;
      static double statusBarHeight;
      static GetIt getIt = GetIt.instance;
    
      static initSp() async {
        sp = await SharedPreferences.getInstance();
      }
    
      static initScreenUtil(BuildContext context){
        ScreenUtil.instance = ScreenUtil(width: 750, height: 1334) ..init(context);
        final size = MediaQuery.of(context).size;
        Application.screenWidth = size.width;
        Application.screenHeight = size.height;
        Application.statusBarHeight = MediaQuery.of(context).padding.top;
      }
    
      static setupLocator(){
        getIt.registerSingleton(NavigateService());
        getIt.registerSingleton(DioRequest());
        getIt.registerSingleton(ApiService());
        getIt.registerSingleton(SettingPresenter());
        getIt.registerSingleton(LoginPresenter());
        getIt.registerSingleton(CollectionPresenter());
        getIt.registerSingleton(BillPresenter());
      }
    }
    

    看一下 main.dart 中的全部代码,基础框架初始化完成直接打开登录页面

    void main() {
     Router router = Router();
     Routes.configureRoutes(router);
     Application.router = router;
     Application.setupLocator();
     runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          navigatorKey: Application.getIt<NavigateService>().globalKey,
          theme: ThemeData(///项目主题设置
            brightness: Brightness.light,
            primaryColor: Color(0xFF00A0E9),
            splashColor: Colors.transparent,
            backgroundColor: Color(0xFFF2F2F2),
          ),
          home: LoginPage(),
          onGenerateRoute: Application.router.generator,///路由生成
          debugShowCheckedModeBanner: true,
        );
      }
    }
    

    开始搭建 MVP

    base_contract.dart

    abstract class IBaseView {
    }
    
    abstract class IBasePresenter<T extends IBaseView> {
    
      /// 绑定 view
      void attachView(T view);
    
      /// 分离 view
      void detachView();
    }
    
    abstract class IBaseModel {
    
    }
    

    base_view.dart(继承 base_contract.dart 中的 IBaseView)

    import 'base_contract.dart';
    
    class BaseView extends IBaseView {
      void showLoading({String msg}) {
      }
    
      void closeLoading() {
    
      }
    
      void renderPage(Object object) {
    
      }
    
      void reload() {
    
      }
    
      void showError({String errorMsg}) {
    
      }
    
      void showDisConnect() {
    
      }
    }
    

    接下来创建三个基础抽象类

    base_page.dart(实现 base_contract.dart 中的 IBaseView)

    abstract class BasePageState<T extends BasePresenter>
        extends State<StatefulWidget> with AutomaticKeepAliveClientMixin implements BaseView {
      T presenter = Application.getIt.get<T>();
    
      bool _isPrepared = false;
    
      @mustCallSuper///注意看这个注解,贼鸡儿重要,必须在子类中调用父类的这个方法,不然子类的                  ///presenter 中会报 view 空指针
      @override
      Widget build(BuildContext context) {
        _attachView();
        if (!_isPrepared) {
          Timer.run(() => preparePage());
          _isPrepared = true;
        }
        return null;
      }
    
      @mustCallSuper
      @override
      void dispose() {
        super.dispose();
        _detachView();
      }
    
      /// 初始化一次 =》 用于 presenter 请求网络数据后调用 showDialog 拿不到合适的 context 报错
      void preparePage();
    
      @override
      void reload() {}
    
      @override
      void renderPage(Object o) {}
    
      @override
      void showDisConnect() {}
    
      @override
      void showError({String errorMsg}) {
        // TODO: implement showError
        if (errorMsg != null) {
          ToastUtil.showToast(errorMsg);
        }
      }
    
      @override
      void showLoading({String msg}) {
        // TODO: implement showLoad
        /// 把 dialog 的 show 从 普通页面里分离
        showDialog(
            context: context,
            barrierDismissible: false,
            builder: (BuildContext context) {
              return LoadingDialog(
                text: msg ?? '加载中',
              );
            });
      }
    
      @override
      void closeLoading() {
        // TODO: implement closeLoading
        /// 必须和 showLoading 方法配对使用 ,避免 pop 当前页面
        Navigator.pop(context);
      }
    
      void _attachView(){
        if(null != presenter){
          presenter.attachView(this);
        }
      }
    
      void _detachView(){
        if(null != presenter){
          presenter.detachView();
        }
      }
    
      @override
      // TODO: implement wantKeepAlive
      bool get wantKeepAlive => true;
    }
    

    base_presenter.dart (实现 base_contract.dart 中的 IBasePresenter)

    import 'base_contract.dart';
    
    abstract class BasePresenter<T extends IBaseView> implements IBasePresenter<T> {
      T view;
    
      @override
      void attachView(T view) {
        this.view = view;
      }
    
      @override
      void detachView() {
        if (null != view) {
          this.view = null;
        }
      }
    }
    

    base_model.dart(实现 base_contract.dart 中的 IBaseModel)

    class BaseModel implements IBaseModel {
      ApiService apiService = Application.getIt.get<ApiService>();
    }
    

    应用(登录)

    login_contract.dart

    import 'package:flutter_mvp/base/base_contract.dart';
    import 'package:rxdart/rxdart.dart';
    
    abstract class ILoginView extends IBaseView {
    
      /// 跳转到首页
      void navigateHome();
    
      /// 显示加载提示
      void showLoading({String msg});
    
      /// 关闭加载提示
      void closeLoading();
    
      /// 获取用户输入账号
      String getAccount();
    
      /// 获取用户输入密码
      String getPassword();
    }
    
    abstract class ILoginPresenter extends IBasePresenter<ILoginView> {
    
      /// 登录操作
      void login();
    }
    
    abstract class ILoginModel extends IBaseModel{
    
      /// 登录请求
      Observable login(String account, String password);
    }
    

    login_page.dart

    import 'package:flutter/cupertino.dart';
    import 'package:flutter/material.dart';
    import 'package:flutter_mvp/app/application.dart';
    import 'package:flutter_mvp/base/base_page.dart';
    import 'package:flutter_mvp/route/navigator_util.dart';
    import 'package:flutter_screenutil/flutter_screenutil.dart';
    
    import 'login_contract.dart';
    import 'login_presenter.dart';
    
    class LoginPage extends StatefulWidget {
      @override
      LoginPageState createState() => LoginPageState();
    }
    
    class LoginPageState extends BasePageState<LoginPresenter> implements ILoginView{
    
      _navigateHomePage(BuildContext context) {
        NavigatorUtil.goHomePage(context, replace: true);
      }
      
      TextEditingController _accountController = TextEditingController();
      TextEditingController _passwordController = TextEditingController();
    
      Color _btnColor = Color(0xff00A0E9);
    
      bool _isObscureText = true;
      void _setStatus(int status) {
        setState(() {
          switch (status) {
            case 0:
              _login();
              break;
            case 1:
              _btnColor = Color(0xff0083BF);
              break;
            case 2:
              _btnColor = Color(0xff00A0E9);
              break;
          }
        });
      }
    
      void _closeOrOpenEye(){
        setState(() {
          _isObscureText = !_isObscureText;
        });
      }
    
      @override
      void initState() {
        _accountController.text = '00113582010041';
        _passwordController.text = '123456';
        super.initState();
      }
    
      @override
      Widget build(BuildContext context) {
        Application.initScreenUtil(context);
        super.build(context);
        _accountController.addListener((){});
        _passwordController.addListener((){});
        return Scaffold(
          body: Container(
            color: Colors.white,
            child: ListView(
              children: <Widget>[
                /// 手机号
                Container(
                  margin: EdgeInsets.only(
                      left: ScreenUtil().setWidth(50),
                      right: ScreenUtil().setWidth(50),
                      top: ScreenUtil().setHeight(580)),
                  child: TextField(
                    controller: _accountController,
                    decoration: InputDecoration(
                      hintText: '请输入手机号',
                    ),
                    style: TextStyle(fontSize: ScreenUtil().setSp(30)),
                  ),
                ),
    
                /// 密码
                Container(
                  margin: EdgeInsets.only(
                      left: ScreenUtil().setWidth(50),
                      right: ScreenUtil().setWidth(50),
                      top: ScreenUtil().setHeight(50)),
                  child: Stack(
                    children: <Widget>[
                      TextField(
                        obscureText: _isObscureText,
                        textInputAction: TextInputAction.done,
                        controller: _passwordController,
                        decoration: InputDecoration(
                          hintText: '输入密码',
                        ),
                        style: TextStyle(fontSize: ScreenUtil().setSp(30)),
                      ),
                      /// 小眼睛
                      Positioned(
                        child: Center(
                          child: GestureDetector(
                            child: Image.asset('assets/images/${_isObscureText ? 'icon_eye_close' : 'icon_eye_open'}.png'),
                            onTap: _closeOrOpenEye,
                          ),
                        ),
                        right: 0,
                        top: ScreenUtil().setHeight(25),
                      )
                    ],
                  ),
                ),
    
                /// 忘记密码
                Row(),
                /// 登录
                GestureDetector(
                  onTapDown: (_) => _setStatus(1),
                  onTapUp: (_) => _setStatus(2),
                  onTap: () => _setStatus(0),
                  child: Container(
                    height: ScreenUtil().setHeight(80),
                    margin: EdgeInsets.only(
                        left: ScreenUtil().setWidth(75),
                        right: ScreenUtil().setWidth(75),
                        top: ScreenUtil().setHeight(227)),
                    child: Center(
                      child: Text(
                        '登录',
                        style: TextStyle(
                          color: Colors.white,
                          fontSize: ScreenUtil().setSp(34),
                        ),
                      ),
                    ),
                    decoration: BoxDecoration(
                      borderRadius: BorderRadius.all(
                        Radius.circular(
                          ScreenUtil().setWidth(10),
                        ),
                      ),
                      color: _btnColor,
                    ),
                  ),
                ),
              ],
            ),
          ),
        );
      }
    
      void _login() async{
        presenter.login();
      }
    
      @override
      void preparePage() {
        // TODO: implement preparePage
      }
    
      @override
      String getAccount() {
        // TODO: implement getAccount
        return _accountController.text;
      }
    
      @override
      String getPassword() {
        // TODO: implement getPassword
        return _passwordController.text;
      }
    
      @override
      void navigateHome() {
        // TODO: implement navigateHome
        NavigatorUtil.goHomePage(context,replace: true);
      }
    }
    

    login_presenter.dart

    import 'package:flutter_mvp/base/base_presenter.dart';
    import 'package:flutter_mvp/exception/exception.dart';
    import 'package:flutter_mvp/pojo/response/login_rsp.dart';
    import 'package:flutter_mvp/utils/sputil.dart';
    
    import 'login_contract.dart';
    import 'login_model.dart';
    
    class LoginPresenter extends BasePresenter<ILoginView>
        implements ILoginPresenter {
      ILoginModel _loginModel;
    
      LoginPresenter() {
        _loginModel = LoginModel();
      }
    
      @override
      void login() {
        // TODO: implement login
        view.showLoading(msg: '登录中...');
        _loginModel.login(view.getAccount(), view.getPassword()).listen((dataMap) {
          view.closeLoading();
          print('登录响应数据:${dataMap.toString()}');
          LoginRsp loginRsp = LoginRsp.fromJson(dataMap);
          SpUtil.setToken(loginRsp.access_token);
        }, onError: (error) {
          view.closeLoading();
          if (error is CommonException) {
            print('登录错误: ${error.errorMsg}');
          } else {
            print('登录错误: ${error.toString()}');
          }
        }, onDone: () {
          //实际开发中只有登录成功才跳转
          view.navigateHome();
        });
      }
    }
    

    login_model.dart

    import 'package:flutter_mvp/base/base_model.dart';
    import 'package:rxdart/src/observables/observable.dart';
    
    import 'login_contract.dart';
    
    class LoginModel extends BaseModel implements ILoginModel {
    
      @override
      Observable login(String account, String password) {
        // TODO: implement login
        return apiService.login(account, password);
      }
    }
    

    其他配置

    1. 图片适配:项目根目录下创建 assets/images/2.0x & assets/images/3.0x 路径,将不同倍图以同一命名分别放入 /images/、/images/2.0x/、/images/3.0x/ 路径下,然后在 pubspec.yaml 中 flutter: assets: 添加 - assets/images/
    2. Android 启动页:放置启动页图片到Android 路径 res 中相对应的 drawable 文件夹下,在 res/drawable/launch_background.xml 中,bitmap 标签的 src 修改为启动页图片名

    附上地址 flutter_mvp

    相关文章

      网友评论

        本文标题:Flutter MVP 快速开发

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