去年10月分開始用flutter做項(xiàng)目存炮,剛剛接觸也沒有怎么做架構(gòu),直接mvc就上手了蜈漓,磕磕碰碰的終于也算是把項(xiàng)目做上線了」回過頭來(lái)再看代碼融虽,一大堆亂七八糟的代碼耦合在一起,各種重復(fù)代碼灼芭,自己都有點(diǎn)嫌棄有额,我想以后入坑的同學(xué)一定會(huì)罵死我,為此決定使用mvvm重構(gòu)彼绷。github地址
mvvm項(xiàng)目結(jié)構(gòu):
app_manager:管理類
AppManager: 公共參數(shù)
AppRoutesManager: 路由管理
ListenerManager: 監(jiān)聽管理
base:基類
BaseState: 繼承自State巍佑,用來(lái)處理一些公共的功能比如:友盟統(tǒng)計(jì),公共的UI界面
BaseViewModel: viewModel基類寄悯,包含一些公共的參數(shù)萤衰,函數(shù)
ResponseModel: 一個(gè)model類
config:配置文件
網(wǎng)絡(luò)路徑,各種平臺(tái)參數(shù)猜旬,api等
network:網(wǎng)絡(luò)請(qǐng)求封裝
page:界面
utils:工具類
widgets:公共組件
mvvm之viewModel:
這里使用到了flutter的ChangeNotifier類脆栋,它可以在數(shù)據(jù)改變的時(shí)候調(diào)用notifyListeners()函數(shù)通知組件更新狀態(tài)倦卖。
BaseViewModel
isLoading:當(dāng)它true的時(shí)候顯示加載動(dòng)畫組件,為false的時(shí)候顯示正常的UI組件椿争;
refreshData:我們需要在子類里面實(shí)現(xiàn)它怕膛,主要是用來(lái)加載數(shù)據(jù),它有一個(gè)參數(shù)isShowLoading默認(rèn)為true秦踪,也就是調(diào)用這個(gè)函數(shù)時(shí)我們默認(rèn)顯示加載動(dòng)畫組件褐捻。
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();
}
}
///刷新數(shù)據(jù)
@protected
Future refreshData({bool isShowLoading = true});
}
HomeViewModel
HomeViewModel里面我們實(shí)現(xiàn)refreshData()函數(shù)椅邓,并notifyListeners()函數(shù)通知組件刷新狀態(tài)柠逞。
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);
// 頁(yè)數(shù)
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("獲取首頁(yè)列表異常$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
這個(gè)類我們繼承自State,這里我開始的目的是為了不想友盟界面統(tǒng)計(jì)的時(shí)候在每個(gè)界面寫統(tǒng)計(jì)的代碼希坚,后來(lái)又加了泛型將viewModel傳了進(jìn)來(lái)边苹,一部分公共的UI處理邏輯initView(),利用viewModel.isLoading來(lái)控制組件的顯示裁僧。
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 進(jìn)入${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()函數(shù)
Widget initView() {
return Consumer<E>(
builder: (build, provide, _) {
print('Consumer-initView');
print('${viewModel.isLoading}');
return viewModel.isLoading ? LoadingWidget() : buildView();
},
);
}
//不同部分UI處理聊疲,在子類必須實(shí)現(xiàn)buildView()函數(shù)
Widget buildView();
//跳轉(zhuǎn)界面
void push({Widget page, Function popCallback}) {
print("push: 離開${pageName}界面");
Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) {
return page;
})).then((data) {
print("pop 進(jìn)入${pageName}界面");
if (popCallback != null) {
popCallback(data);
}
});
}
//路由跳轉(zhuǎn)
void routerPush({String route, Function popCallback}) {
print("routerPush: 離開${pageName}界面");
AppRoutesManager.router.navigateTo(context, route).then((data) {
print("pop 進(jìn)入${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()函數(shù)里面初始化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 = "首頁(yè)";
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('價(jià)格:${model.price}'),
),
),
Expanded(
child: Container(
child: Text('數(shù)量:${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只是一個(gè)數(shù)據(jù)模型贡珊,一層為services專門從服務(wù)器獲取數(shù)據(jù)最爬。
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的延時(shí)功能模擬網(wǎng)絡(luò)請(qǐng)求
List<ProductModel> _dataList = [];
await Future.delayed(Duration(milliseconds: 2000),(){
for(int i=0;i<10;i++){
Map<String,dynamic> data = {
'name': "產(chǎn)品名字",
'num': 30,
'price': 33.5,
'img': 'HTTP',
};
_dataList.add(ProductModel.fromJson(data)) ;
}
});
return _dataList;
// final res = await Network.get(Api.homeList);
// return res.data;
}
}
這里只是我自己做項(xiàng)目的一些經(jīng)驗(yàn),如果有什么寫的不好的地方歡迎大家指正门岔,還有文筆不好爱致,請(qǐng)大家多多包含。這里是是demo的github地址感興趣的朋友可以前往下載寒随。本人QQ:464708476糠悯,大家多多交流。