🌐

SharedPreferencesWithCacheOptionsを使ってみた

2025/02/15に公開

Note(ノート)

shared_preferencesが使用が変わったようだ?
新しい機能が推奨されているようだ??

https://pub.dev/packages/shared_preferences

翻訳して読んでみた。以前からある方法が非推奨になるらしい。

単純なデータ用のプラットフォーム固有の永続ストレージをラップします (iOS および macOS の NSUserDefaults、Android の SharedPreferences など)。データは非同期的にディスクに永続化される可能性があり、戻り後に書き込みがディスクに永続化される保証はないため、このプラグインは重要なデータの保存には使用しないでください。

Supported data types are int, double, bool, String and List<String>.


SharedPreferences と SharedPreferencesAsync と SharedPreferencesWithCache

バージョン 2.3.0 以降、このパッケージで使用できる API が 3 つあります。[SharedPreferences] は、将来廃止される予定のレガシー API です。プラグインの新規ユーザーには、代わりに新しい [SharedPreferencesAsync] または [SharedPreferencesWithCache] API を使用することを強くお勧めします。

既存のコードを新しい API の 1 つに移行することを検討してください。 詳細については、以下を参照してください。

キャッシュと非同期または同期ゲッター
[SharedPreferences] と [SharedPreferencesWithCache] はどちらも、設定を保存するためにローカル キャッシュを使用します。これにより、初期セットアップ呼び出しがプラットフォームから設定を取得した後、同期 get 呼び出しが可能になります。ただし、キャッシュによって問題が発生することもあります。

複数の isolate から使用している場合shared_preferences、各 isolate には独自のシングルトンとキャッシュがあるためです。
shared_preferences複数のエンジン インスタンス ( など、モバイル デバイス上でバックグラウンド コンテキストを作成するプラグインによって作成されたインスタンスを含む) でを使用している場合firebase_messaging。
shared_preferencesネイティブ コードなど、プラグイン以外のものを使用して、基礎となるシステム設定ストアを変更する場合。
reloadこれは、必要に応じてゲッターを使用する前にメソッドを呼び出すことで解決できます。ほとんどの get 呼び出しでリロードが必要な場合は、代わりに [SharedPreferencesAsync] を使用することを検討してください。

[SharedPreferencesAsync] はローカル キャッシュを利用しないため、すべての呼び出しがホスト プラットフォームのストレージ ソリューションへの非同期呼び出しになります。これによりパフォーマンスが低下する可能性がありますが、保存に使用されたプロセスに関係なく、ネイティブ プラットフォームに保存された最新のデータが常に提供されるはずです。

Android プラットフォーム ストレージ
[SharedPreferencesAsync] および [SharedPreferencesWithCache] API は、DataStore 設定またはAndroid SharedPreferencesを使用してデータを保存できます。ほとんどの場合、プラットフォームで推奨される設定ストレージ システムである DataStore 設定のデフォルト オプションを使用する必要があります。ただし、場合によっては、制御できないコードによって SharedPreferences に書き込まれた設定を操作する必要があることがあります。

バックエンドを使用するにはAndroid SharedPreferences、SharedPreferencesAsyncAndroidOptionsAndroid で [SharedPreferencesAsync] を使用するときに を使用します。

途中までは以前と同じなので省略

共有設定とキャッシュ

final SharedPreferencesWithCache prefsWithCache =
    await SharedPreferencesWithCache.create(
  cacheOptions: const SharedPreferencesWithCacheOptions(
    // When an allowlist is included, any keys that aren't included cannot be used.
    allowList: <String>{'repeat', 'action'},
  ),
);

await prefsWithCache.setBool('repeat', true);
await prefsWithCache.setString('action', 'Start');

final bool? repeat = prefsWithCache.getBool('repeat');
final String? action = prefsWithCache.getString('action');

await prefsWithCache.remove('repeat');

// Since the filter options are set at creation, they aren't needed during clear.
await prefsWithCache.clear();

移行とプレフィックス

SharedPreferences から SharedPreferencesAsync/WithCache への移行
新しい API に移行するにはSharedPreferencesAsync、SharedPreferencesWithCache移行ユーティリティをインポートし、SharedPreferences以前使用されていたインスタンスと、必要な新しい API オプションのオプションを指定します。

変更または削除されない限り、データ損失なしで起動のたびにこれを実行できますmigrationCompletedKey。

import 'package:shared_preferences/util/legacy_to_async_migration_util.dart';
// ···
    const SharedPreferencesOptions sharedPreferencesOptions =
        SharedPreferencesOptions();
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    await migrateLegacySharedPreferencesToSharedPreferencesAsyncIfNecessary(
      legacySharedPreferencesInstance: prefs,
      sharedPreferencesAsyncOptions: sharedPreferencesOptions,
      migrationCompletedKey: 'migrationCompleted',
    );

SharedPreferences のプレフィックスの追加、削除、または変更
デフォルトでは、SharedPreferencesクラスはプレフィックスで始まる設定のみを読み取り (および書き込み) しますflutter.。これはすべてプラグインによって内部的に処理され、このプレフィックスを手動で追加する必要はありません。

または、のインスタンスがインスタンス化される前SharedPreferencesに の呼び出しを追加することで、任意のプレフィックスを使用するように設定できます。のインスタンスが作成された後に呼び出すと失敗します。プレフィックスを空の文字列に設定すると、アプリの非 Flutter バージョンによって作成されたすべての設定にアクセスできるようになります (ネイティブ アプリから Flutter に移行するため)。setPrefixSharedPreferencessetPrefixSharedPreferences''

プレフィックスが などの値に設定され、''によって元々格納されていなかった値が読み取られる場合SharedPreferences、SharedPreferences のいずれかの値が でサポートされていない型であれば初期化が失敗する可能性がありますSharedPreferences。この場合、allowListサポートされている型の設定のみを含む を設定できます。

プレフィックスを完全に削除することにした場合でも、以前のプレフィックスをflutter.設定キーの先頭に手動で追加することで、以前に作成した設定にアクセスできます。

デフォルトのプレフィックスを使用していたがSharedPreferences、新しいプレフィックスに変更したい場合は、現在の設定を手動で変換して新しいプレフィックスを追加する必要があります。そうしないと、古い設定にアクセスできなくなります。

example

公式のサンプルコードをそのまま実行するとエラーが出ました😱

Future.valueを追加すると解決した。
Future.value を使用する理由は、Future 型の変数を初期化するためです。Future<int> 型の _counter 変数を初期化する際に、Future.value(0) を使用することで、初期値として 0 を持つ Future を作成します。これにより、FutureBuilder が _counter を使用する際に、未初期化のエラーを防ぐことができます。

以下に、Future.value を使用して _counter を初期化するコードを示します。

main.dart
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// ignore_for_file: public_member_api_docs

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
// #docregion migrate
import 'package:shared_preferences/util/legacy_to_async_migration_util.dart';
// #enddocregion migrate

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'SharedPreferencesWithCache Demo',
      home: SharedPreferencesDemo(),
    );
  }
}

class SharedPreferencesDemo extends StatefulWidget {
  const SharedPreferencesDemo({super.key});

  
  SharedPreferencesDemoState createState() => SharedPreferencesDemoState();
}

class SharedPreferencesDemoState extends State<SharedPreferencesDemo> {
  final Future<SharedPreferencesWithCache> _prefs =
      SharedPreferencesWithCache.create(
        cacheOptions: const SharedPreferencesWithCacheOptions(
          // This cache will only accept the key 'counter'.
          allowList: <String>{'counter'},
        ),
      );
  late Future<int> _counter = Future<int>.value(0);
  int _externalCounter = 0;

  Future<void> _incrementCounter() async {
    final SharedPreferencesWithCache prefs = await _prefs;
    final int counter = (prefs.getInt('counter') ?? 0) + 1;

    setState(() {
      _counter = prefs.setInt('counter', counter).then((_) {
        return counter;
      });
    });
  }

  /// 別のインスタンス、スレッドで発生する可能性のある、外部からのボタン押下を取得する、
  /// あるいはネイティブシステム経由で発生する可能性のある、外部からのボタン押下を取得します。
  Future<void> _getExternalCounter() async {
    final SharedPreferencesAsync prefs = SharedPreferencesAsync();
    final int externalCounter = (await prefs.getInt('externalCounter')) ?? 0;
    setState(() {
      _externalCounter = externalCounter;
    });
  }

  Future<void> _migratePreferences() async {
    // #docregionマイグレーション
    const SharedPreferencesOptions sharedPreferencesOptions =
        SharedPreferencesOptions();
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    await migrateLegacySharedPreferencesToSharedPreferencesAsyncIfNecessary(
      legacySharedPreferencesInstance: prefs,
      sharedPreferencesAsyncOptions: sharedPreferencesOptions,
      migrationCompletedKey: 'migrationCompleted',
    );
    // #enddocregion マイグレーション
  }

  
  void initState() {
    super.initState();
    _migratePreferences().then((_) {
      _counter = _prefs.then((SharedPreferencesWithCache prefs) {
        return prefs.getInt('counter') ?? 0;
      });
      _getExternalCounter();
    });
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('SharedPreferencesWithCache Demo')),
      body: Center(
        child: FutureBuilder<int>(
          future: _counter,
          builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
            switch (snapshot.connectionState) {
              case ConnectionState.none:
              case ConnectionState.waiting:
                return const CircularProgressIndicator();
              case ConnectionState.active:
              case ConnectionState.done:
                if (snapshot.hasError) {
                  return Text('Error: ${snapshot.error}');
                } else {
                  return Text(
                    'Button tapped ${snapshot.data ?? 0 + _externalCounter} time${(snapshot.data ?? 0 + _externalCounter) == 1 ? '' : 's'}.\n\n'
                    'This should persist across restarts.',
                  );
                }
            }
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

カウントアップする。

再起動すると保存されていた🙌

Que(きっかけ)

Xで仕様変わる話題を見て気になって試してみました。破壊的変更なのかなと気になり試してみた。Riverpodで使う場合はどんな感じになるのか。。。
Signalsならまだ簡単に書けそうだが。

Summary(要約)

変更点を要約してみるとこんな感じかな。


SharedPreferences、SharedPreferencesAsync、SharedPreferencesWithCache
バージョン 2.3.0 以降、このパッケージには 3 つの API が含まれています。

  • SharedPreferences: 将来廃止予定のレガシー API。
  • SharedPreferencesAsync: 非同期 API。
  • SharedPreferencesWithCache: キャッシュを利用する API。
    新規ユーザーには、SharedPreferencesAsync または SharedPreferencesWithCache の使用が推奨されます。既存のコードを新しい API に移行することを検討してください。

キャッシュと非同期または同期ゲッター

  • SharedPreferences と SharedPreferencesWithCache: ローカルキャッシュを使用し、同期的な get 呼び出しが可能。
  • SharedPreferencesAsync: ローカルキャッシュを使用せず、すべての呼び出しが非同期でホストプラットフォームのストレージにアクセス。

Discussion