FlutterFlutter开发圈Flutter

flutter mvvm实现demo

2020-09-03  本文已影响0人  幸福的脚步2016

去年10月分开始用flutter做项目,刚刚接触也没有怎么做架构,直接mvc就上手了,磕磕碰碰的终于也算是把项目做上线了。回过头来再看代码,一大堆乱七八糟的代码耦合在一起,各种重复代码,自己都有点嫌弃,我想以后入坑的同学一定会骂死我,为此决定使用mvvm重构。github地址

mvvm项目结构:

mvvm结构.png
app_manager:管理类

AppManager: 公共参数
AppRoutesManager: 路由管理
ListenerManager: 监听管理

base:基类

BaseState: 继承自State,用来处理一些公共的功能比如:友盟统计,公共的UI界面
BaseViewModel: viewModel基类,包含一些公共的参数,函数
ResponseModel: 一个model类

config:配置文件

网络路径,各种平台参数,api等

network:网络请求封装
page:界面
utils:工具类
widgets:公共组件

mvvm之viewModel:

这里使用到了flutter的ChangeNotifier类,它可以在数据改变的时候调用notifyListeners()函数通知组件更新状态。

BaseViewModel

isLoading:当它true的时候显示加载动画组件,为false的时候显示正常的UI组件;
refreshData:我们需要在子类里面实现它,主要是用来加载数据,它有一个参数isShowLoading默认为true,也就是调用这个函数时我们默认显示加载动画组件。

import 'package:flutter/widgets.dart';

///所有viewModel的父类,提供一些公共功能
///author:liuhc
abstract class BaseViewModel  with ChangeNotifier {

  BaseViewModel(this.context);

  BuildContext context;
  //是否正在加载
  bool _isLoading = false;
  bool get isLoading => _isLoading;
  set isLoading(bool isLoading) {
    if(_isLoading!=isLoading){
      _isLoading= isLoading;
      this.notifyListeners();
    }
  }
  ///刷新数据
  @protected
  Future refreshData({bool isShowLoading = true});
}
HomeViewModel

HomeViewModel里面我们实现refreshData()函数,并notifyListeners()函数通知组件刷新状态。

import 'package:flutter/material.dart';
import 'package:flutter_sg_mvvm/base/base_view_model.dart';
import 'package:flutter_sg_mvvm/page/home/model/product_model.dart';
import 'package:flutter_sg_mvvm/page/home/services/home_services.dart';
import 'package:flutter_sg_mvvm/widgets/loading_dialog/loading_dialog.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
import 'package:rxdart/rxdart.dart';

class HomeViewModel extends BaseViewModel {
  HomeViewModel(BuildContext context) : super(context);

  // 页数
  int _page = 0;
  int get page => _page;
  set page(int page) {
    _page = page;
  }

  List <ProductModel> _dataList = [];
  List<ProductModel> get dataList => _dataList;
  set dataList(List<ProductModel> arr) {
    _dataList = arr;
    this.notifyListeners();
  }

  RefreshController _refreshController = RefreshController(initialRefresh: false);
  RefreshController get refreshController => _refreshController;

  @override
  Future refreshData({bool isShowLoading = false}) {

    this.isLoading = isShowLoading;

    HomeServices.getHomeList().then((data){
      if(_page==0){
        _dataList.clear();
        _refreshController.refreshCompleted();
      }else{
        _refreshController.loadComplete();
      }

      _dataList.addAll(data) ;

      if(_dataList.length>=30){
        _refreshController.loadNoData();
      }else{
        _refreshController.resetNoData();
      }
      _page++;
      this.isLoading = false;
      this.notifyListeners();

    }).catchError((e){
      print("获取首页列表异常$e");
    });
  }

  //收藏或取消收藏
  Future collectionProduc(int index){
    LoadingDialog.showLoadingDialog(context);
    Future.delayed(Duration(milliseconds: 2000),(){
      ProductModel model = _dataList[index];
      model.isCollection = !model.isCollection;
      this.notifyListeners();
      LoadingDialog.cancelLoadingDialog(context);
    });
  }
}

mvvm之view:

BaseState

这个类我们继承自State,这里我开始的目的是为了不想友盟界面统计的时候在每个界面写统计的代码,后来又加了泛型将viewModel传了进来,一部分公共的UI处理逻辑initView(),利用viewModel.isLoading来控制组件的显示。

import 'package:flutter/material.dart';
import 'package:flutter_sg_mvvm/app_manager/app_routes_manager.dart';
import 'package:flutter_sg_mvvm/base/base_view_model.dart';
import 'package:flutter_sg_mvvm/widgets/loading_widget/loading_widget.dart';
import 'package:provider/provider.dart';

abstract class BaseState<T extends StatefulWidget, E extends BaseViewModel> extends State<T> {
  String pageName;
  E viewModel;
  void initState() {
    // TODO: implement initState
    super.initState();
    Future.delayed(Duration(microseconds: 500), () {
      print("initState 进入${pageName}界面");
    });
  }

  @override
  Widget build(BuildContext context) {
    return build(context);
  }

  @override
  void didChangeDependencies() {
    // TODO: implement didChangeDependencies
    super.didChangeDependencies();
    print("didChangeDependencies: ${pageName}界面");
  }

  @override
  void didUpdateWidget(StatefulWidget oldWidget) {
    // TODO: implement didUpdateWidget
    super.didUpdateWidget(oldWidget);
    print("didUpdateWidget: ${pageName}界面");
  }

  @override
  void deactivate() {
    // TODO: implement deactivate
    super.deactivate();
    print("deactivate:${pageName}界面");
  }

  //公共UI处理,如不想使用公共部分,直接在子类重写initView()函数
  Widget initView() {
    return Consumer<E>(
      builder: (build, provide, _) {
        print('Consumer-initView');
        print('${viewModel.isLoading}');
        return viewModel.isLoading ? LoadingWidget() : buildView();
      },
    );
  }

  //不同部分UI处理,在子类必须实现buildView()函数
  Widget buildView();


  //跳转界面
  void push({Widget page, Function popCallback}) {
    print("push: 离开${pageName}界面");
    Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) {
      return page;
    })).then((data) {
      print("pop 进入${pageName}界面");
      if (popCallback != null) {
        popCallback(data);
      }
    });
  }

  //路由跳转
  void routerPush({String route, Function popCallback}) {
    print("routerPush: 离开${pageName}界面");
    AppRoutesManager.router.navigateTo(context, route).then((data) {
      print("pop 进入${pageName}界面");
      if (popCallback != null) {
        popCallback(data);
      }
    });
  }

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
    print("dispose 离开${pageName}界面");
    print("销毁${pageName}界面");
  }
}

HomePage

_HomePageState继承自BaseState,以后所有的界面的State都要继承自BaseState;在initState()函数里面初始化viewModel,然后使用Provider绑定viewModel。

import 'package:flutter/material.dart';
import 'package:flutter_sg_mvvm/app_manager/app_manager.dart';
import 'package:flutter_sg_mvvm/base/base_state.dart';
import 'package:flutter_sg_mvvm/page/home/model/product_model.dart';
import 'package:flutter_sg_mvvm/page/home/view_model/home_view_model.dart';
import 'package:flutter_sg_mvvm/page/login/login_page.dart';
import 'package:flutter_sg_mvvm/page/shopping_cart/shopping_cart_page.dart';
import 'package:flutter_sg_mvvm/widgets/app_bar/custom_app_bar.dart';
import 'package:flutter_sg_mvvm/widgets/loading_widget/loading_widget.dart';
import 'package:flutter_sg_mvvm/widgets/refresher_footer/refresher_footer.dart';
import 'package:flutter_sg_mvvm/widgets/refresher_header/refresher_header.dart';
import 'package:provider/provider.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';

class HomePage extends StatefulWidget {
  HomePage({Key key}):super(key:key);
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends BaseState<HomePage,HomeViewModel>
    with AutomaticKeepAliveClientMixin {

  @override
  bool get wantKeepAlive => true;
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    pageName = "首页";
    viewModel = HomeViewModel(context);
    viewModel.refreshData(isShowLoading: true);
  }

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider.value(
      value: viewModel,
      child: Scaffold(
        appBar: CustomAppBar(
          title: pageName,
        ),
        body: initView(),
      ),
    );
  }

  @override
  Widget buildView() {
    return Container(
      child: Column(
        children: <Widget>[
          Expanded(
              child: SmartRefresher(
                enablePullDown: true,
                enablePullUp: true,
                header: RefresherHeader(),
                footer: RefresherFooter(),
                controller: viewModel.refreshController,
                onRefresh: (){
                  viewModel.page = 0;
                  viewModel.refreshData(isShowLoading: false);
                },
                onLoading: (){
                  viewModel.refreshData(isShowLoading: false);
                },
                child: ListView.builder(
                  itemCount: viewModel.dataList.length,
                  itemBuilder: (BuildContext context, index) {
                    ProductModel model = viewModel.dataList[index];
                    return Container(
                      height: 100,
                      child: Column(
                        children: <Widget>[
                          Expanded(
                            child: Row(
                              children: <Widget>[
                                Expanded(
                                  child: Container(
                                    child: Text(model.name),
                                  ),
                                ),
                                GestureDetector(
                                  onTap: (){
                                    viewModel.collectionProduc(index);
                                  },
                                  child: Container(
                                    padding: EdgeInsets.all(10),
                                    child: Text(model.isCollection?'取消':'收藏'),
                                  ),
                                )

                              ],
                            ),
                          ),
                          Expanded(
                            child: Container(
                              child: Row(
                                children: <Widget>[
                                  Expanded(
                                    child: Container(
                                      child: Text('价格:${model.price}'),
                                    ),
                                  ),
                                  Expanded(
                                    child: Container(
                                      child: Text('数量:${model.num}'),
                                    ),
                                  ),
                                ],
                              ),
                            ),
                          ),
                        ],
                      ),
                    );
                  },
                ),
              )),
          GestureDetector(
            onTap: () {
              push(page: ShoppingCartPage(), popCallback: (dada) {

              });
            },
            child: Container(
              width: AppManager.width,
              height: 40,
              color: Colors.yellow,
            ),
          )
        ],
      ),
    );

  }

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
  }
}

mvvm之model:

我将model分为两层,一层model只是一个数据模型,一层为services专门从服务器获取数据。

HomeServices


import 'package:flutter/cupertino.dart';
import 'package:flutter_sg_mvvm/config/api.dart';
import 'package:flutter_sg_mvvm/network/network.dart';
import 'package:flutter_sg_mvvm/page/home/model/product_model.dart';

class HomeServices{

  static Future<dynamic> collectionProduct({
    @required String productId,
    @required int moduleType,
  }) async {
    final res = await Network.post(
      Api.homeList,
      data: {
        'productId': productId,
      },
    );
    return res.data;
  }

  static Future<List<ProductModel>> getHomeList() async {
 //这里使用Future的延时功能模拟网络请求
    List<ProductModel> _dataList = [];
   await Future.delayed(Duration(milliseconds: 2000),(){
      for(int i=0;i<10;i++){
        Map<String,dynamic> data = {
          'name': "产品名字",
          'num': 30,
          'price': 33.5,
          'img': 'HTTP',
        };
        _dataList.add(ProductModel.fromJson(data)) ;
      }
    });
    return _dataList;

//    final res = await Network.get(Api.homeList);
//    return res.data;
  }

}

这里只是我自己做项目的一些经验,如果有什么写的不好的地方欢迎大家指正,还有文笔不好,请大家多多包含。这里是是demo的github地址感兴趣的朋友可以前往下载。本人QQ:464708476,大家多多交流。

上一篇下一篇

猜你喜欢

热点阅读