使用容器和Provider实现全局状态与局部状态管理交互

场景

在实际中存在全局状态需要与局部状态进行交互,例如用户登录成功后需要通知业务模块更新数据(比如后台上传位置,开始刷新拉取数据)。传统的方式中需要在局部状态向全局状态主动订阅消息,当全局状态改变时再通知局部状态处理业务。这样存在如下缺陷:

  1. 未使用局部状态前也需要创建局部状态实例(因需要发起全局状态消息订阅)
  2. 局部状态需要调用全局状态的消息订阅方法,出现了耦合。

主动订阅方式

以之前的主动订阅方式,使用用户登录为例。这里定义两个状态管理,一个是用户登录退出全局状态UserLoginStore,一个是消息接收局部状态MessageStore。在用户登录和用户退出的时候需要MessageStore进行登录成功和退出登录后的业务处理。原先的两个类定义如下:


基本类图

代码实现上,以Provider为例,首先是在顶层widget注册全局状态管理UserLoginStore,同时还需要将MessageStore实例化(或者也注册为全局状态)。

Widget myApp = MultiProvider(
    providers: [
      ChangeNotifierProvider(
        create: (context) => UserLoginStore(),
        lazy: false,
      ),
    ],
    child: MyApp(),
  );

UsreLoginStore的实现如下,在登录成功和退出登录时,通过调用对应listeners的Function对象,执行相应的函数进行响应。

class UserLoginStore with ChangeNotifier {
  List<Function()> loginListeners = [];
  List<Function()> logoutListeners = [];
  
  void login() async {
    //省略调用登录接口代码
    if (loginListeners != null) {
      loginListeners.forEach((func) {
        func();
      })
    }
  }
  
  void logout() async {
    //省略调用退出登录接口代码
    if (logoutListeners != null) {
      logoutListeners.forEach((func) {
        func();
      })
    }
  }
  
  void addLoginListeners(Function func) {
    loginListeners.add(func);
  }
  
  void addLogoutListeners(Function func) {
    logoutListeners.add(func);
  }
}

MessageStore的实现方法如下:

class MessageStore with ChangeNotifier {

  void _requestMessage() async {
    //省略后台接口请求消息代码
    notifyListeners();
  }
  
  void _clearMessage() async {
    //省略将消息列表清空方法
    notifyListeners();
  }
  
  void registerLoginListener() {
    final userLoginStore = Provider.of<UserLoginStore>();
    //注册登录成功订阅,成功后请求消息
    userLoginStore.addLoginListeners(() {
      _requestMessage();
    });
  }
  
  void addLogoutListers(Function func) {
    final userLoginStore = Provider.of<UserLoginStore>();
    //注册退出登录订阅,退出登录后清空消息
    userLoginStore.addLogoutListeners(() {
      _clearMessage();
    });
  }
}

从代码可以看出UserLoginStore的addLoginListeners和addLogoutListeners暴露给了MessageStore,增加了耦合。而且因为是主动订阅,会需要提前实例化MessageStore,哪怕是一开始没有任何用户信息。

使用容器和通过接口解耦

为了对UserLoginStore和MessageStore进行解耦,更改一下类图。


引入抽象类解耦

增加UserLoginService抽象类,定义登录成功和登录失败的处理接口方法。此时MessageStore需要实现UserLoginService的loginHandler和logoutHandler方法。代码修改如下:

class MessageStore with ChangeNotifier, UserLoginService {

  void _requestMessage() async {
    //省略后台接口请求消息代码
    notifyListeners();
  }
  
  void _clearMessage() async {
    //省略将消息列表清空方法
    notifyListeners();
  }
  
  void loginHandler() {
    _requestMessage();
  }
  
  void logoutHandler() {
    _clearMessage();
  }
}

从代码里看,已经完全不依赖于UserLoginStore。<br />这个时候UserLoginStore如何去通知MessageStore呢?容器登场。使用GetIt组件定义全局容器类,代码如下:

class GlobalServiceRepository {
  static void resisterServices() {
    GetIt getIt = GetIt.instance;
    getIt.registerLazySingleton<MessageStore>(() => MessageStore());
    getIt.registerLazySingleton<List<UserLoginService>>(() {
      return [getService<MessageStore>()];
    });
  }

  static T getService<T>() {
    GetIt getIt = GetIt.instance;
    return getIt<T>();
  }
}

代码也很简单,首先为了保证在需要的时候拿到MessageStore的实例,通过registerLazySingleton<MessageStore>注册一个懒加载的MessageStore。然后考虑UserLoginStore可能与多个局部Store关联,一次注册一个List<UserLoginService>,注意这里使用的是抽象类UserLoginService了,也是懒加载的方式返回一个List,这个List其实就是在用户登录成功或退出登录需要通知的对象。这个List里通过getService<MessageStore>()返回了UserLoginStore需要通知的MessageStore示例。<br />再来看UserLoginStore的实现代码。

class UserLoginStore with ChangeNotifier {
  void login() async {
    //省略调用登录接口代码
    //从容器取出需要通知的listeners对象
    List<UserLoginService> listners = GlobalServiceRepository.getService<List<UserLoginService>>();
    listners.forEach((listener) {
       listener.loginHandler();
    });
  }
  
  void logout() async {
    //省略调用退出登录接口代码
    List<UserLoginService> listners = GlobalServiceRepository.getService<List<UserLoginService>>();
    listners.forEach((listener) {
       listener.logoutHandler();
    });
  }
}

从代码里可以看到,UserLoginStore只需要关心容器里是否有需要通知的listeners即可,任何注册到容器的UserLoginService的实现类都可以接收到登录和退出登录的消息。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,718评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,683评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,207评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,755评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,862评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,050评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,136评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,882评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,330评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,651评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,789评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,477评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,135评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,864评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,099评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,598评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,697评论 2 351