GetxService实践 2022-11-01周二

官网介绍

This class is like a GetxController, it shares the same lifecycle ( onInit(), onReady(), onClose()). But has no "logic" inside of it. It just notifies GetX Dependency Injection system, that this subclass can not be removed from memory.
So is super useful to keep your "Services" always reachable and active with Get.find(). Like: ApiService, StorageService, CacheService.

Future main() async {
  await initServices(); /// AWAIT SERVICES INITIALIZATION.
  runApp(SomeApp());
}

/// Is a smart move to make your Services intiialize before you run the Flutter app.
/// as you can control the execution flow (maybe you need to load some Theme configuration,
/// apiKey, language defined by the User... so load SettingService before running ApiService.
/// so GetMaterialApp() doesnt have to rebuild, and takes the values directly.
void initServices() async {
  print('starting services ...');
  /// Here is where you put get_storage, hive, shared_pref initialization.
  /// or moor connection, or whatever that's async.
  await Get.putAsync(() => DbService().init());
  await Get.putAsync(SettingsService()).init();
  print('All services started...');
}

class DbService extends GetxService {
  Future init() async {
    print('$runtimeType delays 2 sec');
    await 2.delay();
    print('$runtimeType ready!');
    return this;
  }
}

class SettingsService extends GetxService {
  void init() async {
    print('$runtimeType delays 1 sec');
    await 1.delay();
    print('$runtimeType ready!');
  }
}

The only way to actually delete a GetxService, is with Get.reset() which is like a "Hot Reboot" of your app. So remember, if you need absolute persistence of a class instance during the lifetime of your app, use GetxService.

Getx 官网

  • 以上就是Getx官网关于GetxService的介绍。大致的意思是和GetxController差不多,有生命周期回调,也依赖注入系统。不同点是一旦创建,就会一直存在,行为和单例很像。

  • 目标使用场景是创建各种服务,比如网络服务ApiService,存储服务StorageService,CacheService等等。

本地存储

shared_preferences

企业微信截图_7064d170-612f-4848-a8c5-0693794bc9d0.png
  • 可以看出,shared_preferences在本地存储方面还是非常受欢迎的,如果没有特别的要求,就选择这个了。

  • Flutter中网络一般使用Dio,网上的封装方式基本上用单例,但是没有人用ApiService。所以这个不大合适。本地存储shared_preferences都是异步的,封装起来也不是很方便,所以可以考虑封装成StorageService试试。

  • 参考文章:已经有人用GetxService封装过shared_preferences,可以作为参考

flutter_ducafecat_news_getx

安装插件

flutter pub add shared_preferences

利用GetxService封装shared_preferences

创建文件

  • Flutter Tools右键菜单可以创建以GetxService为基类的类。app/data/services目录已经存在,放在这里也还可以,直接用现成的就行。
企业微信截图_62405879-179c-4de4-8e42-458564129061.png
  • 文件名输入: storage service就好。 不需要区分大小写,用空格而不是用_来区分单词,确实挺方便的。但是不自动添加Service后缀,感觉不是很好。
企业微信截图_fe8ce293-bcba-4bd3-874e-0b304c245c04.png
import 'package:get/get.dart';

class StorageService extends GetxService {
  //TODO: Implement StorageService GetxService.
}

虽然有点简陋,不过作为框架结构还是可以用用的。

初始化

  • 由于是借助shared_preferences实现本地存储,所以内部要保留shared_preferences单例作为实际执行者。

  • 提供一个init方法作为初始化方法

class StorageService extends GetxService {
  late final SharedPreferences _spInstance;

  Future init() async {
    _spInstance = await SharedPreferences.getInstance();
    return this;
  }
}

这种方法模仿自Getx的官方文档

class DbService extends GetxService {
  Future init() async {
    print('$runtimeType delays 2 sec');
    await 2.delay();
    print('$runtimeType ready!');
    return this;
  }
}

单例获得

  • 这里先不管注入的事(需要集中注入),这里只用Get.find();
    既然GetxService天然就是单例,所以采用Getx注入体系来获取这个单例。

  • 内部用一个静态全局变量来表示这个单例,并且用get属性的方式实现,和普通单例使用习惯一致。

static StorageService get sharedInstance => Get.find();
  • 以后访问只需要StorageService.sharedInstance.XXXX(xxxx);就可以了

方法封装

  • 基本上就是SharedPreferences官网介绍的方法重新包了一层

  • get方法的可选值都给了默认值,减少一层判断。

  • 当然,int给默认值0,double给默认值0.0,在某些时候是需要特别注意的。

  Future setInt(String key, int value) async {
    return await _spInstance.setInt(key, value);
  }

  Future setDouble(String key, double value) async {
    return await _spInstance.setDouble(key, value);
  }

  Future setString(String key, String value) async {
    return await _spInstance.setString(key, value);
  }

  Future setBool(String key, bool value) async {
    return await _spInstance.setBool(key, value);
  }

  int getInt(String key) {
    return _spInstance.getInt(key) ?? 0;
  }

  double getDouble(String key) {
    return _spInstance.getDouble(key) ?? 0.0;
  }

  Future setStringList(String key, List value) async {
    return await _spInstance.setStringList(key, value);
  }

  String getString(String key) {
    return _spInstance.getString(key) ?? '';
  }

  bool getBool(String key) {
    return _spInstance.getBool(key) ?? false;
  }

  List getStringList(String key) {
    return _spInstance.getStringList(key) ?? [];
  }

  bool containsKey(String key) {
    return _spInstance.containsKey(key);
  }

  Future remove(String key) async {
    return await _spInstance.remove(key);
  }

  Future clear() async {
    return await _spInstance.clear();
  }

集中注入

  • Getx官网在介绍GetxService的时候,在一个全局的initServices()函数中集中初始化,这个是很好的。
void initServices() async {
  print('starting services ...');
  /// Here is where you put get_storage, hive, shared_pref initialization.
  /// or moor connection, or whatever that's async.
  await Get.putAsync(() => DbService().init());
  await Get.putAsync(SettingsService()).init();
  print('All services started...');
}
  • initServices()这个全局函数的执行,在runApp()之前,这个是有性能问题的。比如,我们在实际开发中就遇到firebase在国内没法用,初始化要等到网络访问超时才退出,这样就导致app启动时间过长。

  • GetxService的初始化其实就是一个注入的过程,所以本人认为更好的执行位置就是ApplicationBindings。这个是异步的,不会影响页面主流程的启动。

时序问题

  • 就算将initServices()放在runApp()之前,不过由于Get.putAsync的异步特性,并不能保证init()执行完成,同样存在时序问题。(init没完成,就执行具体的操作,会导致异常)

  • 将initServices放在ApplicationBindings做,时序问题会更加严重。

企业微信截图_41b37db4-4e52-46e9-8afb-7f6e2a92c5d8.png
  • 如果时序不正确,那么会导致两个崩溃点
    (1)Get.find会崩溃;如果没有put完成,执行find会崩溃;
    (2)SharedPreferences如果没有初始化,执行存取操作会崩溃;

如何解决时序和崩溃问题

  1. 不解决。flutter的崩溃不会导致程序崩溃,这个已经跟try catch的效果差不多了。程序出错而不是直接退出,这个已经比iOS原生的崩溃要好很多了。

  2. 采用Get.put; 放弃Get.putAsyn; 这样可以保证find之前都会有put,就能避免Get.find的崩溃。
    这个时候init方法可以保留,初始化工作可以重写GetXServices的onInit保证只执行一次。

  3. SharedPreferences由于是异步初始化,无法保证调用者调用时初始化完成。所以这个就需要内部处理。要么不管,要么用try catch,要么就是用内部的逻辑变量判断。

如何选择?

怎么选都不完美,都有遗憾,所以需要有取舍。
本文就选择:
(1)用Get.put注入,用Get.find获取单例。
(2)保留异步init()方法
(3)重写onInit()方法,在内部调用异步的init()方法
(4)内部采用一个逻辑变量,判断初始化是否完成

这样选当然漏洞多多,但是目前还没有找到更加好的方法,暂时就这么着吧。

例子代码

这种异步初始化的问题本来就麻烦。如果初始化没有完成,就调用相关方法,必然出错。
如果需要,就给个toast提示。

import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart';

class StorageService extends GetxService {
  /// 需要先Get.put,不然会报错
  static StorageService get sharedInstance => Get.find();

  /// 内部插件单例初始化
  late final SharedPreferences _spInstance;
  bool isInitFinished = false;

  /// 具体的初始化方法
  Future _init() async {
    if (!isInitFinished) {
      _spInstance = await SharedPreferences.getInstance();
      isInitFinished = true;
    }
    return this;
  }

  @override
  void onInit() {
    super.onInit();
    _init();
  }

  /// 检查初始化化过程是否完成,不然会报错
  bool checkInitial() {
    if (!isInitFinished) {
      EasyLoading.showError('初始化未完成');
      return false;
    }
    return true;
  }

  Future setInt(String key, int value) async {
    if (!checkInitial()) {
      return false;
    }
    return await _spInstance.setInt(key, value);
  }

  Future setDouble(String key, double value) async {
    if (!checkInitial()) {
      return false;
    }
    return await _spInstance.setDouble(key, value);
  }

  Future setString(String key, String value) async {
    if (!checkInitial()) {
      return false;
    }
    return await _spInstance.setString(key, value);
  }

  Future setBool(String key, bool value) async {
    if (!checkInitial()) {
      return false;
    }
    return await _spInstance.setBool(key, value);
  }

  int getInt(String key) {
    if (!checkInitial()) {
      return 0;
    }
    return _spInstance.getInt(key) ?? 0;
  }

  double getDouble(String key) {
    if (!checkInitial()) {
      return 0.0;
    }
    return _spInstance.getDouble(key) ?? 0.0;
  }

  Future setStringList(String key, List value) async {
    if (!checkInitial()) {
      return false;
    }
    return await _spInstance.setStringList(key, value);
  }

  String getString(String key) {
    if (!checkInitial()) {
      return '';
    }
    return _spInstance.getString(key) ?? '';
  }

  bool getBool(String key) {
    if (!checkInitial()) {
      return false;
    }
    return _spInstance.getBool(key) ?? false;
  }

  List getStringList(String key) {
    if (!checkInitial()) {
      return [];
    }
    return _spInstance.getStringList(key) ?? [];
  }

  bool containsKey(String key) {
    if (!checkInitial()) {
      return false;
    }
    return _spInstance.containsKey(key);
  }

  Future remove(String key) async {
    if (!checkInitial()) {
      return false;
    }
    return await _spInstance.remove(key);
  }

  Future clear() async {
    if (!checkInitial()) {
      return false;
    }
    return await _spInstance.clear();
  }

   /// 保存为json字符串
   Future setObject(String key, Object object) async {
    if (!checkInitial()) {
      return false;
    }
    String jsonString = '';
    bool result = false;
    try {
      jsonString = json.encode(object);
      result = await _spInstance.setString(key, jsonString);
    } catch (e) {
      EasyLoading.showError(e.toString());
    } 
    return result;
  }

  /// 读取json字符串
  Object getObject(String key) {
    if (!checkInitial()) {
      return '';
    }
    String jsonString = _spInstance.getString(key) ?? '';
    Object object = {};
    if (jsonString.isNotEmpty) {
      try {
        object = json.decode(jsonString) ?? {};
      } catch (e) {
        EasyLoading.showError(e.toString());
      }
    }
    return object;
  }
}
import 'package:get/get.dart';
import 'package:panda_buy/app/data/services/storage_service.dart';

class ApplicationBindings extends Bindings {
  @override
  void dependencies() async {
    /// 初始化服务
    initServices();
  }

  /// 服务集中初始化
  void initServices() async {
    Get.put(StorageService());
  }
}

你可能感兴趣的:(GetxService实践 2022-11-01周二)