Flutter實(shí)戰(zhàn) 全局變量及共享狀態(tài)

2021-03-09 15:12 更新

應(yīng)用程序中通常會(huì)包含一些貫穿 APP 生命周期的變量信息,這些信息在 APP 大多數(shù)地方可能都會(huì)被用到,比如當(dāng)前用戶信息、Local 信息等。在 Flutter 中我們把需要全局共享的信息分為兩類:全局變量和共享狀態(tài)。全局變量就是單純指會(huì)貫穿整個(gè) APP 生命周期的變量,用于單純的保存一些信息,或者封裝一些全局工具和方法的對(duì)象。而共享狀態(tài)則是指哪些需要跨組件或跨路由共享的信息,這些信息通常也是全局變量,而共享狀態(tài)和全局變量的不同在于前者發(fā)生改變時(shí)需要通知所有使用該狀態(tài)的組件,而后者不需要。為此,我們將全局變量和共享狀態(tài)分開單獨(dú)管理。

#15.4.1 全局變量-Global類

我們?cè)凇發(fā)ib/common”目錄下創(chuàng)建一個(gè)Global類,它主要管理 APP 的全局變量,定義如下:

// 提供五套可選主題色
const _themes = <MaterialColor>[
  Colors.blue,
  Colors.cyan,
  Colors.teal,
  Colors.green,
  Colors.red,
];


class Global {
  static SharedPreferences _prefs;
  static Profile profile = Profile();
  // 網(wǎng)絡(luò)緩存對(duì)象
  static NetCache netCache = NetCache();


  // 可選的主題列表
  static List<MaterialColor> get themes => _themes;


  // 是否為release版
  static bool get isRelease => bool.fromEnvironment("dart.vm.product");


  //初始化全局信息,會(huì)在APP啟動(dòng)時(shí)執(zhí)行
  static Future init() async {
    _prefs = await SharedPreferences.getInstance();
    var _profile = _prefs.getString("profile");
    if (_profile != null) {
      try {
        profile = Profile.fromJson(jsonDecode(_profile));
      } catch (e) {
        print(e);
      }
    }


    // 如果沒有緩存策略,設(shè)置默認(rèn)緩存策略
    profile.cache = profile.cache ?? CacheConfig()
      ..enable = true
      ..maxAge = 3600
      ..maxCount = 100;


    //初始化網(wǎng)絡(luò)請(qǐng)求相關(guān)配置
    Git.init();
  }


  // 持久化Profile信息
  static saveProfile() =>
      _prefs.setString("profile", jsonEncode(profile.toJson()));
}

Global 類的各個(gè)字段的意義都有注釋,在此不再贅述,需要注意的是init()需要在 App 啟動(dòng)時(shí)就要執(zhí)行,所以應(yīng)用的main方法如下:

void main() => Global.init().then((e) => runApp(MyApp()));

在此,一定要確保Global.init()方法不能拋出異常,否則 runApp(MyApp())根本執(zhí)行不到。

#15.4.2 共享狀態(tài)

有了全局變量,我們還需要考慮如何跨組件共享狀態(tài)。當(dāng)然,如果我們將要共享的狀態(tài)全部用全局變量替代也是可以的,但是這在 Flutter 開發(fā)中并不是一個(gè)好主意,因?yàn)榻M件的狀態(tài)是和 UI 相關(guān),而在狀態(tài)改變時(shí)我們會(huì)期望依賴該狀態(tài)的 UI 組件會(huì)自動(dòng)更新,如果使用全局變量,那么我們必須得去手動(dòng)處理狀態(tài)變動(dòng)通知、接收機(jī)制以及變量和組件依賴關(guān)系。因此,本實(shí)例中,我們使用前面介紹過的 Provider 包來實(shí)現(xiàn)跨組件狀態(tài)共享,因此我們需要定義相關(guān)的 Provider。在本實(shí)例中,需要共享的狀態(tài)有登錄用戶信息、APP 主題信息、APP 語(yǔ)言信息。由于這些信息改變后都要立即通知其它依賴的該信息的 Widget 更新,所以我們應(yīng)該使用ChangeNotifierProvider,另外,這些信息改變后都是需要更新 Profile 信息并進(jìn)行持久化的。綜上所述,我們可以定義一個(gè)ProfileChangeNotifier基類,然后讓需要共享的 Model 繼承自該類即可,ProfileChangeNotifier定義如下:

class ProfileChangeNotifier extends ChangeNotifier {
  Profile get _profile => Global.profile;


  @override
  void notifyListeners() {
    Global.saveProfile(); //保存Profile變更
    super.notifyListeners(); //通知依賴的Widget更新
  }
}

#用戶狀態(tài)

用戶狀態(tài)在登錄狀態(tài)發(fā)生變化時(shí)更新、通知其依賴項(xiàng),我們定義如下:

class UserModel extends ProfileChangeNotifier {
  User get user => _profile.user;


  // APP是否登錄(如果有用戶信息,則證明登錄過)
  bool get isLogin => user != null;


  //用戶信息發(fā)生變化,更新用戶信息并通知依賴它的子孫Widgets更新
  set user(User user) {
    if (user?.login != _profile.user?.login) {
      _profile.lastLogin = _profile.user?.login;
      _profile.user = user;
      notifyListeners();
    }
  }
}

#APP主題狀態(tài)

主題狀態(tài)在用戶更換 APP 主題時(shí)更新、通知其依賴項(xiàng),定義如下:

class ThemeModel extends ProfileChangeNotifier {
  // 獲取當(dāng)前主題,如果為設(shè)置主題,則默認(rèn)使用藍(lán)色主題
  ColorSwatch get theme => Global.themes
      .firstWhere((e) => e.value == _profile.theme, orElse: () => Colors.blue);


  // 主題改變后,通知其依賴項(xiàng),新主題會(huì)立即生效
  set theme(ColorSwatch color) {
    if (color != theme) {
      _profile.theme = color[500].value;
      notifyListeners();
    }
  }
}

#APP語(yǔ)言狀態(tài)

當(dāng) APP 語(yǔ)言選為跟隨系統(tǒng)(Auto)時(shí),在系通語(yǔ)言改變時(shí),APP 語(yǔ)言會(huì)更新;當(dāng)用戶在 APP 中選定了具體語(yǔ)言時(shí)(美國(guó)英語(yǔ)或中文簡(jiǎn)體),則 APP 便會(huì)一直使用用戶選定的語(yǔ)言,不會(huì)再隨系統(tǒng)語(yǔ)言而變。語(yǔ)言狀態(tài)類定義如下:

class LocaleModel extends ProfileChangeNotifier {
  // 獲取當(dāng)前用戶的APP語(yǔ)言配置Locale類,如果為null,則語(yǔ)言跟隨系統(tǒng)語(yǔ)言
  Locale getLocale() {
    if (_profile.locale == null) return null;
    var t = _profile.locale.split("_");
    return Locale(t[0], t[1]);
  }


  // 獲取當(dāng)前Locale的字符串表示
  String get locale => _profile.locale;


  // 用戶改變APP語(yǔ)言后,通知依賴項(xiàng)更新,新語(yǔ)言會(huì)立即生效
  set locale(String locale) {
    if (locale != _profile.locale) {
      _profile.locale = locale;
      notifyListeners();
    }
  }
}
以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)