那今天就来让这个非同步资料透过MobX 来串接到画面上:
首先一样建立一个 UsersViewModel
import 'dart:convert';
import 'package:flutter_rest_api_playground/model/users/users.dart';
import 'package:flutter_rest_api_playground/service/http.dart';
import 'package:mobx/mobx.dart';
part 'users_view_model.g.dart';
class UsersViewModel = _UsersViewModelBase with _$UsersViewModel;
abstract class _UsersViewModelBase with Store {
@observable
ObservableFuture<String>? foo;
@action
Future fetchFoo() {
return foo = ObservableFuture(Future.delayed(
const Duration(seconds: 3),
() => 'foo',
));
}
}
但在正式搬移 fetch users 相关的行为之前,我们先来看看MobX到底如何操作非同步事件
我们宣告一个 observable: ObservableFuture <String>? foo;
,这个ObservableFuture
类似一个可以被监听的future,当他的非同步事件的状态改变时会通知给监听者。
然後我们需要一个action回传一个 ObservableFuture
大部分就跟之前一样将 usersViewModel
实例化出来,然後我们在 initState
这里做 usersViewModel.fetchUserList()
class _MyHomePageState extends State<MyHomePage> {
UsersViewModel usersViewModel = UsersViewModel();
@override
void initState() {
usersViewModel.fetchFoo();
super.initState();
}
// 省略以下
}
那什麽是 initState
?在 Flutter 的widget有各种生命周期,所谓生命周期就是widget从建立到销毁的各种状况。
而initState
这个 State
是在初始化时会执行的一个function ,详细的 statefulWidget
的生命周期可以参考文章最後面的连结。
Observer(builder: (_) {
final future = usersViewModel.foo;
if (future == null) {
return const Text('null');
}
switch (future.status) {
case FutureStatus.pending:
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
CircularProgressIndicator(),
Text('loading'),
],
);
case FutureStatus.fulfilled:
final String items = future.result;
return Text(items);
}
return const Text('loading');
}),
这个 future
的 type 是 ObservableFuture
在操作它时就就跟我们在 FutureBuilder
里操作 Snapshot
一样,所以我们可以从他的 status
来得知这个非同步事件目前的状态是什麽。
接下来就是开始搬移我们昨天fetch User的程序码
final HttpService _httpService = HttpService();
@observable
ObservableFuture<ObservableList<Users>>? userList;
@action
Future fetchUserList() {
return userList = ObservableFuture(Future(() async {
final response = await _httpService.get('users');
final jsonStr = json.encode(response.data);
final ObservableList<Users> result =
ObservableList.of(usersFromJson(jsonStr));
return userList = ObservableFuture.value(result);
}));
}
这里会发现一个很麻烦的事情就是我们在 ObservableFuture
里在包一层 Future
然後才在Future
里面的执行我们的真正的实作。
这样子是为了让 observer 才能正确的得知 ObservableFuture
的里面的 Future
status真的有改变。
但当然还是有其他比较简单的方式可以达到这个需求,接下来的范例会使用另外一种方法。
既然我们现在有了一些非同步事件,那我们来试着使用 MobX 的 reactions 来管理看看,首先我们先在MobX store里宣告这三个 observable
来表示我们选择到的id及User还有读取状态,然後两个 @action
来改变这两个observable
。
@observable
String? seletedUserid = '1';
@observable
ObservableFuture<Users>? seletedUser;
@observable
bool loading = false;
@action
void seletedUsesrid(String? userid) {
seletedUserid = userid;
}
@action
Future fetchSeledtedUser(String? userid) async {
runInAction(() {
loading = true;
});
final response = await _httpService.get('users/$userid');
final Users result = Users.fromJson(response.data);
runInAction(() {
seletedUser = ObservableFuture.value(result);
loading = false;
});
}
这边的 runInAction
就是会在 async Action 中的另外一个scope 他会立即去变更 observable
,所以我就可以不用读取 Future
状态而是直接我们自己用另外一个值去替代。
接下来开始实作这功能的UI,我这里将 DropdownButton
抽出来成为另一个单独的 Widget
class CustomDropdownButton extends StatelessWidget {
CustomDropdownButton({Key? key, required this.usersViewModel})
: super(key: key);
final UsersViewModel usersViewModel;
@override
Widget build(BuildContext context) {
return Observer(builder: (_) {
return DropdownButton<String>(
value: usersViewModel.seletedUserid,
icon: const Icon(Icons.arrow_downward),
iconSize: 24,
elevation: 16,
style: const TextStyle(color: Colors.deepPurple),
underline: Container(
height: 2,
color: Colors.deepPurpleAccent,
),
onChanged: (String? newValue) {
usersViewModel.seletedUsesrid(newValue);
},
items: List.generate(10, (index) => (index + 1).toString())
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
);
});
}
}
这里比较重要得参数就是 value
、 onChanged
、 items
value
就是这个 DropdownButton
所绑定的 state 所以这边就直接绑定 usersViewModel.seletedUserid
然後 onChanged
就是绑定我们的 action
items
可以想像成select/option 里的 option 所以我们就放入 List<DropdownMenuItem<String>>
里面是1到10的选项。
然後将要根据这个 DropdownButton
变更的UI写好:
Observer(
builder: (_) {
final future = usersViewModel.seletedUser;
if (future == null) {
return const Text('null');
}
switch (usersViewModel.loading) {
case true:
return const CircularProgressIndicator();
case false:
final Users items = future.result;
return Column(
children: [
Text(items.name!),
],
);
}
return Text('null');
},
),
接下来你会发现,你无论怎麽按都不会变更这个 Observer
的UI,因为我们的DropdownButton
onChange时只有触发 seletedUsesrid
而没有去做 fetch
这时就可以写上我们的 Reactions
late List<ReactionDisposer> _disposers;
void setupReactions() {
_disposers = [
reaction<String?>((_) => seletedUserid, fetchSeledtedUser),
];
}
void dispose() {
for (var d in _disposers) {
d();
}
}
然後到 widget里注册
@override
void initState() {
usersViewModel.fetchUserList();
usersViewModel.fetchFoo();
super.initState();
usersViewModel.setupReactions();
}
@override
void dispose() {
usersViewModel.dispose();
super.dispose();
}
我们在 initState
时注册我们的Reactions 然後在dispose时清除掉。
通常Reactions就是当我们监听的对象,有所变化时我们须要执行的side effect都会放在这里。
现在我们来看一下这段code reaction<String?>((_) => seletedUserid, fetchSeledtedUser)
,就是指seletedUserid
变化时要执行 fetchSeledtedUser
。
当然Reactions 还有其他方法可以使用,这里就不多赘述了。
最後的成品如下:
今天的程序码
https://github.com/zxc469469/flutter_rest_api_playground/tree/Day24
今天的内容稍嫌复杂了点,我自己对於MobX非同步的操作也不是很有把握说一定是best pratice,所以如果有错的话还请帮我留言更正一下QQ
那接下来就是开始实作路由的功能了。
<<: Day22 AR隐形眼镜 让你闭着眼睛都能追剧!玩游戏!
>>: JavaScript学习日记 : Day25 - Set
Cookie 介绍 Cookie 指得是储存在Client (用户)端上的资料,是一种在服务器与浏览...
我能同意职员上班不能看股票,因为起起伏伏的线图,也会跟随着心情曲线起伏,让当天无法专注於工作上,毕竟...
改变path的样貌 首先观看以下程序码 const width = 800; const heigh...
因为一开始还需要稍微等待专案同步设定,两人便开始聊起之前没谈到的补课细节。 「那麽,你对哪里比较没有...
【前言】 本系列为个人前端学习之路的学习笔记,在过往的学习过程中累积了很多笔记,如今想藉着IT邦帮忙...