概述
其實本身 flutter 就是一個 mvvm 的架構, 當你載入完資料 ,直接 setState() 就可以了,
MVVM各代表什麼
- M:Model class,便是指這裡的Repository ,主要負責從本地資料庫或者遠端伺服器來獲取資料,Repository統一了資料的入口,獲取到資料,將資料傳送給 ViewModel
- VM:ViewModel ,即 Flutter 中的ValueNotifier 或者是 ChangeNotifier
- V:View ,即 Flutter 中 Widget,也可以認為 ValueListenableBuilder 或者是 ChangeNotifierProvider
使用MVVM實現一個簡單的登入頁面
比如我們去請求一個網路的書籍
先建 Model
class BookModel {
String? name; // 書的名字
String? author;// 書的作者
String? detail;
int loadState = 1; // 0 : 載入中 1:載入成功 2:載入失敗
}
複製程式碼
建立我們的BookRepository
下面的是模擬網路去請求資料,用一個變數 當 count 是偶數的時候模擬網路錯誤,是奇數的時候模擬網路成功
class BookRepository {
var count = 0;
Future<BookModel> getData() async {
await Future.delayed(const Duration(seconds: 2));
BookModel model = BookModel();
count++;
if (count.isOdd) {
throw Exception("網路錯誤");
} else {
model.name = "Flutter$count";
model.author = "google$count";
}
return model;
}
}
複製程式碼
再寫ViewModel層
class BookViewModel extends ValueNotifier<BookModel> {
// 建立 BookRepository
BookRepository repository = BookRepository();
BookViewModel() : super(BookModel());
// 當 已經 dispose 的時候,就不要在發了
bool _dispose = false;
@override
void dispose() {
super.dispose();
_dispose = true;
}
@override
void notifyListeners() {
if (!_dispose) {
super.notifyListeners();
}
}
/// 用來請求資料
void getData() {
// 這裡的value 就是 BookModel
// 設定為載入中
value.loadState = 0;
// 通知改變
notifyListeners();
repository.getData().then((book) {
// 設定載入成功
book.loadState = 1;
// 賦值的時候 會呼叫notifyListeners
value = book;
}).catchError((e) {
// 設定網路錯誤的碼
value.loadState = 2;
notifyListeners();
});
}
}
複製程式碼
建立我們的View層
下面有使用 ValueListenableBuilder 以及 ChangeNotifierProvider 兩種方式去實現,具體請看程式碼
class FlutterBook extends StatefulWidget {
FlutterBook({Key? key}) : super(key: key);
@override
_FlutterBookState createState() => _FlutterBookState();
}
class _FlutterBookState extends State<FlutterBook> {
final BookViewModel _viewModel = BookViewModel();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('MVVM的demo'),
),
// body: _useValueListenableBuilderBody(),
body: _useProviderBody(),
);
}
/// 使用 ValueListenableBuilder
Widget _useValueListenableBuilder() {
return ValueListenableBuilder<BookModel>(
valueListenable: _viewModel,
builder: (BuildContext context, BookModel model, Widget? child) {
return _bodyChild(model);
},
);
}
/// 使用 ChangeNotifierProvider
Widget _useProviderBody() {
return ChangeNotifierProvider(
create: (_) => _viewModel,
child: Consumer<BookViewModel>(
builder: (context, BookViewModel viewModel, child) {
BookModel model = viewModel.value;
return _bodyChild(model);
}),
);
}
Widget _bodyChild(BookModel model) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
alignment: Alignment.center,
width: 200,
height: 200,
child: IndexedStack(
alignment: Alignment.center,
// 根據載入狀態去顯示對應的佈局
index: model.loadState,
children: [
const CircularProgressIndicator(),
Text(
"名字是:${model.name ?? ""} , 作者是:${model.author ?? ""}",
),
// 載入失敗,點選重試
InkWell(
child: Image.asset("assets/img/net_error.jpg"),
onTap: _viewModel.getData,
)
],
),
),
Container(
margin:
const EdgeInsets.only(top: 50, left: 50, right: 50, bottom: 0),
height: 50,
width: MediaQuery.of(context).size.width,
child: MaterialButton(
elevation: 3,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20))),
color: Theme.of(context).primaryColor,
minWidth: 60,
// 點選載入
onPressed: _viewModel.getData,
child: const Text("請求資料",
style: TextStyle(color: Colors.white, fontSize: 20)),
)),
],
);
}
}
複製程式碼