【Flutter】SharedPreferences におけるキー管理のベストプラクティス

【Flutter】SharedPreferences におけるキー管理のベストプラクティス
公開日 :

はじめに

モバイルアプリケーション開発において、アプリケーションの設定やユーザーの状態をローカルに保存するために、Flutter では shared_preferences プラグインを利用することが多いと思います。その際、みなさんはキー管理をどのように行っていますでしょうか。この記事では、shared_preferences を利用する際のキー管理について筆者が考える方法を紹介します。

shared_preferences プラグインとは

shared_preferences プラグインとは、プラットフォーム固有の永続ストレージをラップした Flutter プラグインです。shared_preferences という名前ですが実際の保存先はプラットフォームによって異なります。Android では SharedPreferences、iOS では UserDefaults が利用されます。以降、Flutter の shared_preferences プラグインとして、shared_preferences と表記します。

shared_preferences の利用例

サポートされているデータ型は intbooldoubleStringList<String> です。Write と Read、Remove の操作が可能で、データの Write および Read 操作時には、「引数でキー」を「メソッドで対応するデータ型」を指定する必要があります。以下のコードは、公式のexampleです。

// Obtain shared preferences.
final SharedPreferences prefs = await SharedPreferences.getInstance();

// Save an integer value to 'counter' key.
await prefs.setInt('counter', 10);
// Save an boolean value to 'repeat' key.
await prefs.setBool('repeat', true);
// Save an double value to 'decimal' key.
await prefs.setDouble('decimal', 1.5);
// Save an String value to 'action' key.
await prefs.setString('action', 'Start');
// Save an list of strings to 'items' key.
await prefs.setStringList('items', <String>['Earth', 'Moon', 'Sun']);

// Try reading data from the 'counter' key. If it doesn't exist, returns null.
final int? counter = prefs.getInt('counter');
// Try reading data from the 'repeat' key. If it doesn't exist, returns null.
final bool? repeat = prefs.getBool('repeat');
// Try reading data from the 'decimal' key. If it doesn't exist, returns null.
final double? decimal = prefs.getDouble('decimal');
// Try reading data from the 'action' key. If it doesn't exist, returns null.
final String? action = prefs.getString('action');
// Try reading data from the 'items' key. If it doesn't exist, returns null.
final List<String>? items = prefs.getStringList('items');

// Remove data for the 'counter' key.
await prefs.remove('counter');

キー管理について

前章の利用例のとおり、shared_preferences ではデータ取得、保存、削除にはキーが必要で、取得、保存時にはそのキーに対応するデータ型を指定する必要があります。また、キーをタイポしたり、異なるデータ型を指定すると、予期せぬバグが発生する可能性があります。そのため、キーと対応する型は、アプリケーション全体で一貫性を持たせることが重要です。

キー管理の方法とその実装

キーの管理方法は様々あると思いますが、筆者が考える方法を以下に示します。重要なのは、キーと対応するデータ型を一元管理することです。

enum SharedPreferencesKey<T> {
  counter<int>('counter'),
  repeat<bool>('repeat'),
  decimal<double>('decimal'),
  action<String>('action'),
  items<List<String>>('items'),

  const SharedPreferencesKey(this.str);
  final String str;
}

上記のように、SharedPreferencesKey というEnumを定義し、キーと対応するデータ型を一元管理します。このようにすることで、キーと対応するデータ型を一元管理することができます。また、キーのタイポを防ぐことができます。

SharedPreferences のラッパークラスの作成

先ほど定義した SharedPreferencesKey を利用して、SharedPreferences のラッパークラスを作成します。以下のコードは、SharedPreferences のラッパークラスの例です。

class MySharedPreferences {
  MySharedPreferences(this._prefs);

  final SharedPreferences _prefs;

  @override
  Future<T?> get<T>(SharedPreferencesKey<T> key) async {
    late final dynamic value;
    if (T == String) {
      value = _prefs.getString(key.str);
    } else if (T == int) {
      value = _prefs.getInt(key.str);
    } else if (T == double) {
      value = _prefs.getDouble(key.str);
    } else if (T == bool) {
      value = _prefs.getBool(key.str);
    } else if (T == List<String>) {
      value = _prefs.getStringList(key.str);
    } else {
      throw Exception('Unsupported type');
    }

    if (value is T?) {
      return value;
    }
    return null;
  }

  @override
  Future<void> set<T>(SharedPreferencesKey<T> key, T value) async {
    if (value is String) {
      await _prefs.setString(key.str, value);
    } else if (value is int) {
      await _prefs.setInt(key.str, value);
    } else if (value is double) {
      await _prefs.setDouble(key.str, value);
    } else if (value is bool) {
      await _prefs.setBool(key.str, value);
    } else if (value is List<String>) {
      await _prefs.setStringList(key.str, value);
    } else {
      throw Exception('Unsupported type');
    }
  }

  @override
  Future<void> remove<T>(SharedPreferencesKey<T> key) async {
    await _prefs.remove(key.str);
  }

  @override
  Future<void> clear() async {
    await _prefs.clear();
  }
}

この実装では、generics を利用することで、データ型ごとにメソッドを定義する必要がなくなり、コードの冗長性を減らすことができます。また、型パラメータ TSharedPreferencesKey で定義した型や引数として受け取った型から、推論されるため、MySharedPreferences の利用時には保存、取得するデータ型を指定する必要がありません。

MySharedPreferences の利用例

では、# shared_preferences の利用例 で紹介したコードを MySharedPreferences を利用して書き換えてみましょう。

final prefs = await SharedPreferences.getInstance();
final myPrefs = MySharedPreferences(prefs);

// Save an integer value to 'counter' key.
await myPrefs.set(SharedPreferencesKey.counter, 10);
// Save an boolean value to 'repeat' key.
await myPrefs.set(SharedPreferencesKey.repeat, true);
// Save an double value to 'decimal' key.
await myPrefs.set(SharedPreferencesKey.decimal, 1.5);
// Save an String value to 'action' key.
await myPrefs.set(SharedPreferencesKey.action, 'Start');
// Save an list of strings to 'items' key.
await myPrefs.set(SharedPreferencesKey.items, <String>['Earth', 'Moon', 'Sun']);

// Try reading data from the 'counter' key. If it doesn't exist, returns null.
final int? counter = await myPrefs.get(SharedPreferencesKey.counter);
// Try reading data from the 'repeat' key. If it doesn't exist, returns null.
final bool? repeat = await myPrefs.get(SharedPreferencesKey.repeat);
// Try reading data from the 'decimal' key. If it doesn't exist, returns null.
final double? decimal = await myPrefs.get(SharedPreferencesKey.decimal);
// Try reading data from the 'action' key. If it doesn't exist, returns null.
final String? action = await myPrefs.get(SharedPreferencesKey.action);
// Try reading data from the 'items' key. If it doesn't exist, returns null.
final List<String>? items = await myPrefs.get(SharedPreferencesKey.items);

// Remove data for the 'counter' key.
await myPrefs.remove(SharedPreferencesKey.counter);

キーやデータ型を意識せずに、SharedPreferencesKey を利用して、データの保存、取得、削除を行うことができます。また、キーとデータ型の一元管理により、キーのタイポを防ぐことができます。

まとめ

この記事では、shared_preferences を利用する際のキー管理について紹介しました。キーと対応するデータ型を一元管理することで、キーのタイポを防ぎ、アプリケーション全体で一貫性を持たせることができます。また、MySharedPreferences のようなラッパークラスを作成することで、コードの冗長性を減らすことができます。キー管理について悩んでいる方は、ぜひ参考にしてみてください。