アプリを作る上で、ユーザーが変更したアプリ内設定や、フラグなどの値を保存したいことは多いのではないでしょうか?
複数デバイスや複数ユーザーで共有したい設定はサーバーやクラウドDBなど、何かしらの記憶領域に保存する必要がありますが、
ローカル(ユーザーのデバイス上)に保存する場合は、「shared_preferences
」が使われているのを多く見かけます。
iOSでは UserDefaults
、 Androidでは SharedPreferences
をラップしたプラグイン(パッケージ)です。
保存できる型には制限がありますが、お手軽に使えて便利です。
サンプルリポジトリのリンク
なぜProviderか
詳しくは後述しますが、 SharedPreferences
のインスタンス取得は非同期です。
SharedPreferences
を使いたいとき、毎回非同期でインスタンスを取得せずに使用するためにも、 Provider
を使ってインスタンスをキャッシュします。
また、一度取得したインスタンスは基本的には一貫して使うもののため State
も必要ありません。
なので変化する状態を持たない、一番基本的な「Provider」を使用します。
SharedPreferencesのインスタンス取得は非同期
SharedPreferencesのインスタンスを取得する方法は以下の通りです。
final prefs = await SharedPreferences.getInstance();
そう、インスタンスの取得が非同期処理となっています。
なので、これをそのままProviderでキャッシュして使おうとすると…
final sharedPreferencesProvider = FutureProvider((_) => SharedPreferences.getInstance());
のように FutureProvider
を使わざるをえなくなってしまいます。
しかし、FutureProviderを使用すると呼び出すときに毎回非同期になってしまいます。
このままでは、SharedPreferencesで保存した値を読み込む get
メソッドは非同期ではないのに、 Future
で使わないといけなくなります。
非同期でのインスタンス取得は初回だけにする
まず以下のように、そのままアクセスすると例外を投げる(スローする)Providerを作成します。
final sharedPreferencesProvider = Provider<SharedPreferences>((_) => throw UnimplementedError());
main.dart
の main()
関数を以下のように書き換えます。
Future<void> main() async {
// `runApp` 関数が終わる前に何か処理を実行する場合は `ensureInitialized()` メソッドを追記する必要がある
WidgetsFlutterBinding.ensureInitialized();
runApp(
ProviderScope(
overrides: [
sharedPreferencesProvider.overrideWithValue(
// ここでインスタンス化し、Providerの値を上書きします
await SharedPreferences.getInstance(),
),
],
child: App(),
),
);
}
ProviderScopeのoverridersを使って値を上書きする
ProviderScopeでは、 overriders
パラメータを使用して、Providerやその値を上書き(差し替え)することができます。
上記の例では、 sharedPreferencesProvider
の値を 非同期で取得した SharedPreferences
のインスタンスに差し替えています。
これで、SharedPreferencesのインスタンスをキャッシュして使用できるようになりました🎉