阅读 974
Flutter 入门与实战(七十三):再仿掘金个人主页来看 GetX 和 Provider 之间的 PK

Flutter 入门与实战(七十三):再仿掘金个人主页来看 GetX 和 Provider 之间的 PK

前言

上一篇我们介绍了 GetX 的的简单状态管理 GetBuilder 的使用,本篇我们继续,来在状态管理中完成网络请求后更新界面。在我们介绍 Provider 的时候仿了掘金的个人主页:Flutter 入门与实战(五十三):仿掘金个人主页,学习 FutureProvider 状态管理,我们这一篇来对比一下 GetXProvider 的差异。

状态代码

这里我们网络请求还是使用 Dio,原先的请求相关的代码我们直接复制过来了,源码可以在这里下载:GetX应用代码。在 VSCode 的编辑界面输入 getcontroller 代码模板指令(需要安装 GetX Snippets 扩展)来说输入状态管理代码。这里我们有两个状态属性,一个是网络请求状态枚举_loadingStatus,用于指示网络请求状态;另一个是掘金的个人信息_personalProfile。在构造方法中我们传递用户id,来请求个人信息数据。 可以看到,实际上的代码和计数器的基本上一样,也是更新状态对象最新值,使用 update 通知界面刷新。存在的差别在于我们引入了 GetxController 的一个生命周期函数onReady 来进行网络请求。onReady 在 GetX 的说明如下:

Called 1 frame after onInit(). It is the perfect place to enter navigation events, like snackbar, dialogs, or a new route, or async request. 在 onInit()之后的一帧调用,非常适合放置导航入口事件,例如 SnackBar,对话框、新的路由或异步请求。

关于 GetxController 的生命周期我们下一篇再具体介绍,目前我们知道 GetX 推荐在 onReady 进行网络请求即可。

enum LoadingStatus {
  loading,
  success,
  failed,
}

class PersonalController extends GetxController {
  static PersonalController get to => Get.find();
  final String userId;
  PersonalController({required this.userId});

  @override
  void onReady() {
    getPersonalProfile(userId);
    super.onReady();
  }

  PersonalEntity? _personalProfile;
  get personalProfile => _personalProfile;

  LoadingStatus _loadingStatus = LoadingStatus.loading;
  get loadingStatus => _loadingStatus;

  void getPersonalProfile(String userId) async {
    _loadingStatus = LoadingStatus.loading;
    _personalProfile = await JuejinService.getPersonalProfile(userId);
    if (_personalProfile != null) {
      _loadingStatus = LoadingStatus.success;
    } else {
      _loadingStatus = LoadingStatus.failed;
    }
    update();
  }
}
复制代码

界面代码

界面代码我们不贴了,我们只来对比和 Provider 的差异部分。

  • 最外层代码:这里当时这么写主要是 Provider要给下级组件做共享,因此需要有个包裹 Widget。看起来相差不太大。
// GetX 最外层
class PersonalHomePageWrapper extends StatelessWidget {
  const PersonalHomePageWrapper({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return GetBuilder<PersonalController>(
      init: PersonalController(userId: '70787819648695'),
      builder: (controller) => _PersonalHomePage(),
    );
  }
}
//Provider 最外层
class PersonalHomePageWrapper extends StatelessWidget {
  const PersonalHomePageWrapper({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return FutureProvider<PersonalEntity?>(
      create: (context) => JuejinService.getPersonalProfile('70787819648695'),
      initialData: null,
      child: _PersonalHomePage(),
    );
  }
}
复制代码
  • 实际页面代码:两部分代码看起来也几乎没差别,都是获取状态对象后再构建页面。这里为了语义更准确,我们这次增加了一个网络加载属性来标识请求。这里看来,如果是从 Provider 切换到 GetX,在使用 GetBuilder 这种简单的呃状态管理方式的话,代码改动量应该会很少。其实两部分代码最大的差别是 GetX 不需要使用 context 来获取状态对象了。
// GetX 代码
@override
  Widget build(BuildContext context) {
    if (PersonalController.to.loadingStatus == LoadingStatus.loading) {
      return Center(
        child: Text('加载中...'),
      );
    }
    if (PersonalController.to.loadingStatus == LoadingStatus.failed) {
      return Center(
        child: Text('请求失败'),
      );
    }
    PersonalEntity personalProfile = PersonalController.to.personalProfile;
    return Stack(
      children: [
        CustomScrollView(
          slivers: [
            _getBannerWithAvatar(context, personalProfile),
            _getPersonalProfile(personalProfile),
            _getPersonalStatistic(personalProfile),
          ],
        ),
     //...
}

// Provider 代码
class _PersonalHomePage extends StatelessWidget {
  const _PersonalHomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    PersonalEntity? personalProfile = context.watch<PersonalEntity?>();
    if (personalProfile == null) {
      return Center(
        child: Text('加载中...'),
      );
    }
    return Stack(
      children: [
        CustomScrollView(
          slivers: [
            _getBannerWithAvatar(context, personalProfile),
            _getPersonalProfile(personalProfile),
            _getPersonalStatistic(personalProfile),
          ],
        ),
  //...
}
复制代码

那么 GetX 的优势在哪里呢?我们来将个人主页的代码复制一份,但是不再使用 GetBuilder 了,然后从个人主页增加一个按钮,点击后路由到该页面(见下图,为了区分,我们换了一张背景图)。 屏幕录制2021-09-05 下午9.56.24.gif 这个时候,这个页面其实和上一个页面已经没有任何关系了,但是却依旧可以使用PersonalController.to.personalProfile访问到个人信息数据。部分代码如下所示:

class PersonalHomePageCopy extends StatelessWidget {
  const PersonalHomePageCopy({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    PersonalEntity personalProfile = PersonalController.to.personalProfile;
    return Stack(
      children: [
        CustomScrollView(
          slivers: [
            _getBannerWithAvatar(context, personalProfile),
            _getPersonalProfile(personalProfile),
            _getPersonalStatistic(personalProfile),
          ],
        ),
  //...
}
复制代码

这就使得状态数据的复用更加简洁了,只要状态对象被初始化过后,就可以在任何组件使用——不需要组件之间有任何的关联,比如不需要有共同的父级组件。

总结

本篇介绍了在GetX 的状态管理中进行异步请求刷新界面,并和使用Provider进行状态管理的代码。可以看到 GetX 具备如下特点:

  • 异步请求和普通的状态管理没什么差别,方式相当简单;
  • Provider 迁移到 GetX 修改的代码量不多;
  • GetX 的状态共享方式更简单,无需组件之间有任何关联,只要状态对象被初始化了,就可以在任何组件访问。这才是 GetX 真正的优势所在。

我是岛上码农,微信公众号同名,这是Flutter 入门与实战的专栏文章,提供体系化的 Flutter 学习文章。对应源码请看这里:Flutter 入门与实战专栏源码。如有问题可以加本人微信交流,微信号:island-coder

👍🏻:觉得有收获请点个赞鼓励一下!

🌟:收藏文章,方便回看哦!

💬:评论交流,互相进步!

文章分类
Android
文章标签