文中所用环境及配置
flutter 2.2.3
dependencies:
provider: 5.0.0
dio: ^4.0.0
logger: ^1.1.0
flutter_smart_dialog: ^3.1.0
目录结构
app: 管理类
- app_config: 基本的app配置参数
- application: 初始化项目配置
- route_paths: 页面路由统一存放
- routes: 路由管理
base: 基类
- base_widget:widget页面基类
- base_state_model: viewModel基类,包含一些公共的参数,函数
common: 公共组件
model: 数据模型
net: 网络请求封装
page: 页面UI存放
service: 请求
utils: 工具
view_model: 页面对应的viewModel
mvvm之viewModel:
这里使用到了flutter的ChangeNotifier类,它可以在数据改变的时候调用notifyListeners()函数通知组件更新状态。
开始
base_widget 基类
所有页面都继承自此类,初步实现ViewModel的绑定
class BaseWidget<T extends BaseStateModel> extends StatefulWidget {
final ValueWidgetBuilder<T> builder;
final T model;
final bool autoDispose;
final Function(T model) onModelReady;
//以下在使用默认布局时使用,仅当builder为空时有效
final Widget appBar;
final Widget Function(BuildContext context, T model) child;
final bool showLoadView;//显示页面初始加载动画
BaseWidget({
Key key,
@required this.model,
this.builder,
this.autoDispose: true,
this.onModelReady,
this.appBar,
this.child,
this.showLoadView: true,
}) : super(key: key);
@override
_BaseState<T> createState() => _BaseState<T>();
}
class _BaseState<T extends BaseStateModel> extends State<BaseWidget<T>> {
T model;
@override
void initState() {
super.initState();
model = widget.model;
widget.onModelReady?.call(model);
//首先初始化界面状态为加载中
setState(() {
model.pageState = getState(showLoadView: widget.showLoadView);
});
model.initData();
}
@override
void dispose() {
if (widget.autoDispose) model.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<T>.value(
value: model,
child: Consumer<T>(
//builder为空时显示默认的布局
//默认布局为标题栏 + body
builder: widget.builder ?? (_context, _model, _child) {
return Scaffold(
appBar: widget.appBar ?? const SizedBox(),
body: PageStateWidget(
pageState: model.pageState,
refresh: () {
_model.refresh();
},
child: widget.child(_context, _model) ?? const SizedBox(),
),
);
},
),
);
}
}
base_state_model 基类
此类为所有ViewModel的基类,后续可能还会添加其他方法(如分页加载)。
因项目需求,且为了避免在网络请求过程中直接操作view,所以另外创建了一个PageStateWidget来管理页面的多状态(如系统异常、请求失败、加载中),并通过request方法来统一进行网络请求前后的处理。
abstract class BaseStateModel with ChangeNotifier {
/// 防止页面销毁后,异步任务才完成,导致报错
bool _disposed = false;
PageState pageState; //当前页面的状态值
///初始化
@protected
void initData();
///刷新
///showLoadView: 显示加载动画
void refresh({bool showLoadView: true}) {
//刷新时使用
pageState = getState(
showLoadView: true,
);
notifyListeners();
initData();
}
@override
void notifyListeners() {
if (!_disposed) {
super.notifyListeners();
}
}
@override
void dispose() {
_disposed = true;
super.dispose();
}
///二次封装 用于请求接口返回的处理 例如弹窗、页面状态改变
///request 接口
///showLoadDialog 显示加载弹窗
///showLoadView: 显示加载动画
///showError: 显示异常
///gotoLogin 未登录是否自动前往登录页
Future<dynamic> request({
Future<dynamic> request,
bool showLoadDialog: true,
bool showLoadView: false,
bool showError: true,
bool gotoLogin: true,
}) async {
if (showLoadDialog) showLoading();
var response = await request;
hideLoading();
switch (response.code) {
case 401:
if (gotoLogin) {
showToast("登录失效,正在前往登录页面");
///取消当前cancelToken的所有请求
httpClient.cancelToken.cancel();
Future.delayed(Duration(seconds: 1)).then((value) {
httpClient.cancelToken = CancelToken();
closeAllAndJumpPage(RoutePaths.login);
});
}
break;
case 500:
showToast("系统异常啦~");
break;
case 400:
case -1:
showToast(response.message);
break;
default:
break;
}
pageState = getState(
code: response?.code ?? -1,
showLoadView: showLoadView,
showError: showError,
);
notifyListeners();
return response?.data ?? null;
}
}
PageStateWidget 页面多状态
根据pageState的传入值控制当前页面显示哪种内容。
这里仅做简单的处理,可根据实际需求更改。
class PageStateWidget extends StatefulWidget {
final PageState pageState;
final Widget child;
final bool showNoNetwork; //显示无网络状态
final Function refresh;
PageStateWidget({
@required this.pageState,
@required this.child,
this.showNoNetwork: true,
this.refresh,
});
@override
State<StatefulWidget> createState() => _PageState();
}
class _PageState extends State<PageStateWidget> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
if (widget.pageState == PageState.normal) {
return widget.child ?? SizedBox();
} else if (widget.pageState == PageState.loading) {
return _loading();
} else if (widget.pageState == PageState.noNetwork &&
widget.showNoNetwork) {
return _NoNetWork(widget.refresh);
} else if (widget.pageState == PageState.systemAbnormal) {
return _systemError();
} else
return widget.child ?? SizedBox();
}
Widget _loading() {
return Container(
child: Text("加载中"),
);
}
Widget _systemError() {
return Container(
child: Text("系统异常"),
);
}
}
///无网络状态
class _NoNetWork extends StatelessWidget {
final Function refresh;
_NoNetWork(this.refresh);
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
child: Column(
children: [
Text(
"网络离家出走了",
),
MaterialButton(
child: Text(
"戳这重试",
),
onPressed: () {
if (refresh != null) refresh();
},
),
],
),
);
}
}
/// 返回当前页面状态
/// code: 请求返回的状态码
/// result: 当前网络状态
/// showLoadView: 显示加载view
/// showError: 显示异常界面
PageState getState({int code, ConnectivityResult result,
bool showLoadView: true, bool showError: true}) {
if (result == null) {
result = CommonData.connectivityResult;
}
switch (result) {
case ConnectivityResult.none:
return PageState.noNetwork;
case ConnectivityResult.mobile:
case ConnectivityResult.wifi:
switch (code) {
case 200:
return PageState.normal;
case 500:
return showError ? PageState.systemAbnormal : PageState.normal;
default:
return showLoadView ? PageState.loading : PageState.normal;
}
break;
default:
return showLoadView ? PageState.loading : PageState.normal;
}
}
enum PageState {
///默认,显示child
normal,
///加载中
loading,
///无网络
noNetwork,
///系统异常
systemAbnormal,
}
RoutePaths
class RoutePaths {
static const String home = "home";
static const String login = "login";
}
Route 路由管理
class MyRoute {
static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case RoutePaths.home:
return MaterialPageRoute(builder: (_) => Home());
case RoutePaths.login:
return MaterialPageRoute(builder: (_) => Login());
default:
return MaterialPageRoute(builder: (_) => Home());
}
}
}
main
final logger = Logger(
printer: PrettyPrinter(
methodCount: 2, // number of method calls to be displayed
errorMethodCount: 8, // number of method calls if stacktrace is provided
lineLength: 300, // width of the output
colors: true, // Colorful log messages
printEmojis: true, // Print an emoji for each log message
printTime: false, // Should each log print contain a timestamp
),
);
//final httpClient = HttpClient.getInstance();
void main() {
runApp(MyApp());
}
my_app
此处应注意initialRoute和onGenerateRoute的实现。
navigatorObservers: [FlutterSmartDialog.observer] 与 FlutterSmartDialog(child: child)为插件flutter_smart_dialog的初始化,如果不使用此插件可不添加。
class MyApp extends StatefulWidget {
@override
State<StatefulWidget> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
@override
void initState() {
super.initState();
Connectivity().onConnectivityChanged.listen((ConnectivityResult result) {
// WIFI网络
if(result == ConnectivityResult.wifi){
print("当前为WIFI网络");
// 移动网络
}else if(result == ConnectivityResult.mobile){
print("当前为手机网络");
// 没有网络
}else{
print("当前没有网络");
}
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: true,
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
checkboxTheme: Theme.of(context).checkboxTheme.copyWith(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(100),
),
),
),
initialRoute: RoutePaths.home,
onGenerateRoute: MyRoute.generateRoute,
navigatorObservers: [FlutterSmartDialog.observer],
navigatorKey: AppConfig.globalKey,
builder: (context, child) => MediaQuery(
//设置文字大小不随系统设置改变
data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
child: Scaffold(
body: GestureDetector(
onTap: () {
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus &&
currentFocus.focusedChild != null) {
FocusManager.instance.primaryFocus.unfocus();
}
},
child: FlutterSmartDialog(child: child),
),
),
),
);
}
}
使用
创建Home页面
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BaseWidget<HomeViewModel>(
model: HomeViewModel(),
onModelReady: (model) {
},
appBar: AppBar(
title: Text("标题"),
),
child: (context, model) => Container(),
);
}
}
HomeViewModel
class HomeViewModel extends BaseStateModel {
///此方法进入页面后自动加载
@override
void initData()async {
var response = await request(
request: HomeService().getHomePage(),
showLoadDialog: false,
gotoLogin: false,
);
}
}
HomeService
class HomeService {
Future<BaseResponse<dynamic>> getHomePage() async {
List<Records> result;
var response = await HttpClient.getInstance().get(url: NetWorkAddress.homePage, params: {
"page": 2,
"size": 10,
});
if (response.code == 200) {
result = response.data['list'].map<Records>((item) => Records.fromJson(item)).toList();
response.data = result;
}
return response;
}
}
结束
当前为基础封装,后续可能会根据项目继续改进~