內容指導
本章不是一個初學者的話題,如果您連基礎的介面呼叫,ui介面都不會,推薦先去學習flutter基礎再來看這篇部落格
架構簡介
- view層 顯示view
- model層 請求處理http操作
- viewmodel儲存狀態,處理業務邏輯,充當view跟model通訊的橋樑
- util儲存工具類,比如dio封裝
專案簡介
沒有封裝dio等 只是實現了mvvm登陸功能,也許有的地方不正確,希望各位大佬指出 如果對您有所幫助,麻煩動動小手點個贊
專案建立
flutter create mvvm_demo
複製程式碼
專案分析
1.由於我們viewmodel負責管理資料,以及負責view跟model層通訊,所以這裡我們用proivder狀態管理 引入provider
provider: ^4.3.2+3
複製程式碼
2.專案api請求呼叫,我這裡選擇dio,所以還需要引入dio
dio: 3.0.10
複製程式碼
由於要實現登陸功能,所以我在老專案上找了一個登陸介面,這裡不放出這個介面,大家自己解決介面問題 首先確認api需要的引數
- userName
- passWord
我的api只需要兩個引數
3.由於我們viewmodel來處理業務邏輯,所以我們viewmodel跳轉介面或彈框的時候,怎麼獲取到當前的context? 我們採用全域性navigatorKey
修改main.dart
final GlobalKey<NavigatorState> navigatorKey = new GlobalKey<NavigatorState>();
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: navigatorKey,有了這個,在其他類就可以呼叫navigatorKey來獲取context了
//省略
複製程式碼
搭建專案結構
api以及引入全部完成後,我們要搭建專案結構,建立以下幾個包
- model
- viewmodel
- view
- util
前面三個不用介紹,第四個util做dio封裝方法,我們這裡只簡單封裝一下
建立DioUtil
在util下建立 dio_util.dart 檔案
import 'package:dio/dio.dart';
//程式碼很簡單,需要傳一個介面地址,以及引數
dynamic post(String url, Map map) async {
Response response = await Dio().post(url, data: map);
return response.data;
}
複製程式碼
實際專案會對dio封裝,由於是demo不做演示
建立model
在 model下建立 login_model.dart 檔案 model只有http請求程式碼
import 'package:mvvm_demo/util/dio_util.dart';
class LoginModel {
//提供api請求方法,並把介面返回資訊當作返回值
dynamic login(Map map) async {
return await post(
"http://106.38.32.194:86/OrderTrackerService.asmx/Login", map);
}
}
複製程式碼
建立viewmodel
在 viewmodel下建立 login_viewmodel.dart 檔案 viewmodel只儲存狀態,以及處理業務邏輯,裡邊沒有widget更沒有http請求程式碼 首先我們的後臺需要賬號密碼兩個引數,那麼我們介面肯定有兩個輸入框,我們這裡輸入框的值用controller來接收
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:mvvm_demo/main.dart';
import 'package:mvvm_demo/model/login_model.dart';
import 'package:mvvm_demo/view/menu.dart';
class LoginViewmodel extends ChangeNotifier {
//model物件,用於呼叫api請求方法
LoginModel _model = LoginModel();
//賬號輸入框controller
TextEditingController _user = TextEditingController();
//密碼輸入框controller
TextEditingController _pass = TextEditingController();
dynamic _result = ""; //登陸返回的錯誤資訊,如果正確登陸,則為空
下邊get set方法不做介紹
TextEditingController get user {
return _user;
}
TextEditingController get pass {
return _pass;
}
void setResult(dynamic data) {
_result = data;
notifyListeners();//如果有錯誤資訊,則重新整理所有觀察者
//比如一個text用了這個值,則視為觀察者
//必須正確使用provider狀態管理,才可以監聽到值,下一步驟會告訴大家怎麼配置provider
}
dynamic get result {
return _result;
}
void login() async {//給view層提供登陸方法
dynamic result = await _model.login({//呼叫model層的api請求方法,並把引數當作map傳給model,提供給dio請求
"userName": _user.text,
"passWord": _pass.text,
});
print(result.toString());//返回值
if (result.toString().substring(0, 5) == "false") {
//如果登陸失敗,則在介面提示錯誤資訊
//我的如果登陸失敗返回格式為 **false錯誤資訊**
//所以我的錯誤資訊要從第六位擷取
setResult(result.toString().substring(5));
} else {
setResult(""); //登陸成功,清空錯誤提示資訊
//下邊程式碼可以獲取到我們剛才設定的全域性navigatorKey的context,來實現介面跳轉
Navigator.of(navigatorKey.currentContext).push(
CupertinoPageRoute(
builder: (context) {
return Menu();
},
),
); //跳轉介面
}
}
}
複製程式碼
配置provider狀態管理
修改 main.dart
將
void main() {
runApp(MyApp());
}
複製程式碼
修改為
void main() {
runApp(MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => LoginViewmodel()),
//多個介面請在下方新增多個viewmodel
],
child: MyApp(),
));
}
複製程式碼
建立view
在 view下建立 login.dart 檔案
import 'package:flutter/material.dart';
import 'package:mvvm_demo/viewmodel/login_viewmodel.dart';
import 'package:provider/provider.dart';
class Login extends StatefulWidget {
@override
_LoginState createState() => _LoginState();
}
class _LoginState extends State<Login> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("登陸"),
),
body: Padding(
padding: EdgeInsets.all(10),
child: Column(
children: [
TextField(
controller: Provider.of<LoginViewmodel>(context).user,//獲取viewmodel管理的controller狀態
decoration: InputDecoration(
labelText: "賬號",
prefixIcon: Icon(Icons.person),
),
),
SizedBox(height: 16),
TextField(
controller: Provider.of<LoginViewmodel>(context).pass,//獲取viewmodel管理的controller狀態
decoration: InputDecoration(
labelText: "密碼",
prefixIcon: Icon(Icons.lock),
),
obscureText: true,
),
SizedBox(height: 16),
Container(
width: double.infinity,
child: RaisedButton(
onPressed: context.read<LoginViewmodel>().login,//呼叫viewmodel層的登陸方法
color: Colors.blue,
child: Text(
"登陸",
style: TextStyle(color: Colors.white),
),
),
),
SizedBox(height: 16),
Expanded(
child:
Text(Provider.of<LoginViewmodel>(context).result.toString()),////獲取viewmodel管理的result錯誤資訊
),
],
),
),
);
}
}
複製程式碼
效果展示
總結
這樣是不是程式碼非常整潔? ui有問題就在view層改,並且view層只有ui程式碼 業務有問題就在viewmodel層改 這樣極大增加了專案維護的便捷性
如果本人有什麼地方理解錯誤,還望大家評論或私聊告訴我,一起學習進步