Day 24 | 在flutter 中串接 restful api - MobX的非同步操作

那今天就来让这个非同步资料透过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(),
      );
    });
  }
}

这里比较重要得参数就是 valueonChangeditems

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://ithelp.ithome.com.tw/upload/images/20211007/20112906SQ3RfDaUq9.png


今天的程序码

https://github.com/zxc469469/flutter_rest_api_playground/tree/Day24

今天的内容稍嫌复杂了点,我自己对於MobX非同步的操作也不是很有把握说一定是best pratice,所以如果有错的话还请帮我留言更正一下QQ

那接下来就是开始实作路由的功能了。


<<:  Day22 AR隐形眼镜 让你闭着眼睛都能追剧!玩游戏!

>>:  JavaScript学习日记 : Day25 - Set

Day 19 - Spring Boot & Cookie

Cookie 介绍 Cookie 指得是储存在Client (用户)端上的资料,是一种在服务器与浏览...

心情管理不好,知识管理如何发挥?

我能同意职员上班不能看股票,因为起起伏伏的线图,也会跟随着心情曲线起伏,让当天无法专注於工作上,毕竟...

D3JsDay17 Fill the color,Zoom in on center—地图各项操作及填色

改变path的样貌 首先观看以下程序码 const width = 800; const heigh...

寝室的秘密授课(二):程序概念

因为一开始还需要稍微等待专案同步设定,两人便开始聊起之前没谈到的补课细节。 「那麽,你对哪里比较没有...

【JavaScript】解构赋值

【前言】 本系列为个人前端学习之路的学习笔记,在过往的学习过程中累积了很多笔记,如今想藉着IT邦帮忙...