📦

【Flutter】シェアプリの使い方

に公開

— SharedPreferencesはレガシー、これからは Async / WithCache を使い分ける —

要約

SharedPreferencesはレガシーAPIとなっており、今後はSharedPreferencesAsyncかSharedPreferencesWithCacheを使うことが推奨されています。
確実に最新値が欲しいであったり、複数Isolateが関与するならAsync
同期的な高速getが必要であったり、複数Isolateが関与しないならWithCache(必要に応じて reloadCache())

2つの新APIの設計思想

SharedPreferencesAsync

すべての呼び出しが非同期で、キャッシュを使わない。
毎回ホストプラットフォームへ問い合わせるため、常にネイティブ側の最新値を取得できる。
その分、読み取りはキャッシュ型より遅くなる可能性がある。

SharedPreferencesWithCache

初期ロード後、ローカルキャッシュに設定を保持。
取得系メソッドは同期で即返るため、UI応答が速い。
他コンテキストで値が変わる可能性がある場合はreloadCache()で明示的に最新化することで担保可能。

使い分けの目安

シナリオ 推奨API 理由
複数Isolateを使用するようなアプリ Async キャッシュ不整合が起きにくく、毎回プラットフォームへ問い合わせるため、一貫性が高い。SSOT(Single Source of Truth)にも沿っている。
UIの即時反映が重要なアプリ。複数Isolateで処理を行うような重い処理を行うアプリではない。 WithCache getが同期的に実行可能。必要な箇所だけreloadCache()を呼べば良い。
Android端末アプリでJetpack DataStore推奨の流れに乗りたい 両方可 どちらもデフォルトでバックエンドはJetpack DataStore

実装例

おおまかな使い方は変化ありませんが、WithCacheは一部見慣れない機能があるのでチェックしてみてください。

SharedPreferencesWithCache


import 'package:shared_preferences/shared_preferences.dart';

class AppPrefsWithCache {
  late final SharedPreferencesWithCache _prefs;

  Future<void> init() async {
    _prefs = await SharedPreferencesWithCache.create(
      // 取得・保存・削除可能なキーを制限したい場合はallowListを指定することができる
      // allowListを指定しなければ、どんなキーでも使用可能
      cacheOptions: const SharedPreferencesWithCacheOptions(
        allowList: <String>{'dark_mode', 'language'},
      ),
    );
  }

  // getメソッドは同期的に呼び出せる
  // riverpodのbuild()内で呼び出す際など、非同期ではなく同期的に値を取得できるのはメリット
  bool isDarkMode() => _prefs.getBool('dark_mode') ?? false;
  String? language() => _prefs.getString('language');

  // 書き込みは今まで通り非同期
  Future<void> setDarkMode(bool value) async => await _prefs.setBool('dark_mode', value);
  Future<void> setLanguage(String value) async => await _prefs.setString('language', value);

  // 必要なタイミングでキャッシュ更新
  Future<void> refresh() => _prefs.reloadCache();
}

SharedPreferencesAsync

こちらは見慣れた実装だと思います。

import 'package:shared_preferences/shared_preferences.dart';

class AppPrefsAsync {
  final SharedPreferencesAsync _prefs = SharedPreferencesAsync();

  Future<bool> isDarkMode() async => (await _prefs.getBool('dark_mode')) ?? false;
  Future<String?> language() async => await _prefs.getString('language');

  Future<void> setDarkMode(bool value) async => await _prefs.setBool('dark_mode', value);
  Future<void> setLanguage(String value) async => await _prefs.setString('language', value);
}

レガシーSharedPreferencesからの移行

移行する上での注意点、移行方法についてはドキュメントに記載されてるので、そちらをご確認ください。
保存先が変わるので、データを移す必要があります。

ベストプラクティス

■元からそうでしたが、重要データは保存しないことが重要です。
シェアプリは永続化の保証がないため、クリティカルな情報は然るべき場所に保存することを検討しましょう。
■WithCacheを使う場合の注意
アプリ起動直後、バックグラウンド復帰直後に関して、外部で更新されることが想定されるならreloadCache()を呼び出すことを忘れないでください。

まとめ

シェアプリは、今どきの使い方としてSharedPreferencesAsyncSharedPreferencesWithCacheを適材適所で使い分けるのが基本です。
一貫性・最新性が最優先ならAsyncだし、UI応答速度が最優先ならWithCacheを採用です。
引き続き、クリティカルなデータ保管には使わないことを心がけましょう。

Discussion