FlutterでローカルにKey-Valueでデータ管理するshared_preferencesを試してみた

2024.03.19

こんにちは、ゲームソリューション部のsoraです。
今回は、FlutterでローカルにKey-Valueでデータ管理するshared_preferencesを試してみたことについて書いていきます。

実装した画面

・ローカルに保存しているデータを取得して、現在の名前として表示する。(初期値は「あなた」に設定)
・入力欄に名前を入力して、ボタンを押下すると、ローカルに保存しているデータを書き換えて表示する。
・アプリを再起動してみても、ローカルに保存しているデータを取得して表示しているため、変更後の名前で表示される。

利用する主要なパッケージ

shared_preferences
shared_preferences | Flutter package
使用したバージョン:shared_preferences: ^2.2.2
Key-Value形式でローカルにデータを保存するパッケージ
Providerなどの状態管理はアプリを落とすとリセットされるが、ローカルにデータを保存することで再起動時もデータが保持される。
暗号化されずに保存されるため、暗号化が必要なデータはflutter_secure_storageを使用するか、サーバ側で保存してください。
shared_preferencesはアプリを削除するとデータも削除されますが、flutter_secure_storageはiOSとAndroidで挙動が異なるらしいです。
Androidはアプリは削除するとデータも削除されますが、iOSはアプリを削除してもデータは残るらしいです。

flutter_riverpod
flutter_riverpod | Flutter package
使用したバージョン:flutter_riverpod: ^2.5.1
状態管理パッケージ
多くの情報が落ちている人気なパッケージのため、詳しい説明は割愛します。

コードの解説

コードは以下です。
今回はテストのため、ファイル分けせずに全てmain.dartに書いています。

main.dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';

// shared_preferencesインスタンス
final sharedPreferencesProvider = Provider<SharedPreferences>(
        (_) => throw UnimplementedError()
);

// NotifierProviderでデータの取得・登録
class SharedPrefTest extends Notifier<String> {
  @override
  String build(){
    // Notifierクラスのbuild内でrefを使用可能
    final prefs = ref.watch(sharedPreferencesProvider);
    String prefName = prefs.getString('my_name') ?? 'あなた';
    return prefName;
  }
  void setName(WidgetRef ref, String name) {
    final prefs = ref.watch(sharedPreferencesProvider);
    prefs.setString('my_name', name);
  }
  void getName(WidgetRef ref) {
    final prefs = ref.watch(sharedPreferencesProvider);
    // my_nameキーの値を取得(空だった場合は空文字を返す)
    String prefName = prefs.getString('my_name') ?? '';
    state = prefName;
  }
}
final sharedPrefTestProvider = NotifierProvider<SharedPrefTest, String>(() {
  return SharedPrefTest();
});


// main
Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(
    ProviderScope(
      // 初期起動時にSharedPreferencesでインスタンスの取得
      overrides:[
        sharedPreferencesProvider.overrideWithValue(
            await SharedPreferences.getInstance()
        ),
      ],
      child: const MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends ConsumerWidget {
  MyHomePage({super.key});
  final _messageController = TextEditingController();

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final providerValue = ref.watch(sharedPrefTestProvider);
    final providerNotifier = ref.watch(sharedPrefTestProvider.notifier);

    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: const Text('shared_preferences test'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Container(
              padding: const EdgeInsets.only(
                left: 25,
                right: 25,
              ),
              child: Text(
                '現在の名前:${providerValue}',
                style: const TextStyle(
                  fontSize: 18,
                ),
              ),
            ),
            const SizedBox(height: 10),
            Container(
              padding: const EdgeInsets.only(
                left: 25,
                right: 25,
              ),
              child: TextField(
                controller: _messageController,
                maxLines: 1,
                decoration: const InputDecoration(
                  hintText: '名前を入力',
                  hintStyle: TextStyle(color: Colors.black54),
                ),
              ),
            ),
            Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  ElevatedButton(
                    child: const Text('名前変更'),
                    onPressed: (){
                      var msg = _messageController.text.trim();
                      if(msg.isEmpty){
                        return;
                      }
                      // shared_preferences周りの処理
                      // 入力した名前の登録
                      providerNotifier.setName(ref, msg);
                      // 入力した名前で状態を更新
                      providerNotifier.getName(ref);
                      _messageController.clear();
                    },
                  ),
                ]
            ),
          ],
        ),
      ),
    );
  }
}

SharedPreferencesのインスタンス取得

まずSharedPreferencesのインスタンスを取得する必要があって、本来であれば以下のように記述します。

// SharedPreferencesインスタンスの取得
final pref = await SharedPreferences.getInstance();
// 値の登録
pref.setString('key', value);

このインスタンス取得の際に、awaitが入っていて非同期処理で記述しなければならず、毎回記載するのは面倒で使いづらいため、最初に1度だけインスタンスを取得するように記述しています。

final sharedPreferencesProvider = Provider<SharedPreferences>(
        (_) => throw UnimplementedError()
);
// ...
// main
Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(
    ProviderScope(
      // 初期起動時にSharedPreferencesのインスタンスを取得
      overrides:[
        sharedPreferencesProvider.overrideWithValue(
            await SharedPreferences.getInstance()
        ),
      ],
      child: const MyApp(),
    ),
  );
}

shared_preferencesでのデータの設定・取得

shared_preferencesでローカルのデータを取得したり、設定したりします。
Notifierクラス内のメソッドとして、データの取得・登録を定義しています。
今回はStirngのため、setString, getStringです。
intであればsetInt, getIntなど、型に応じて変わります。

// NotifierProviderでデータの取得・登録
class SharedPrefTest extends Notifier<String> {
  @override
  String build(){
    // Notifierクラスのbuild内でrefを使用可能
    final prefs = ref.watch(sharedPreferencesProvider);
    String prefName = prefs.getString('my_name') ?? 'あなた';
    return prefName;
  }
  void setName(WidgetRef ref, String name) {
    final prefs = ref.watch(sharedPreferencesProvider);
    // my_nameキーに値を設定
    prefs.setString('my_name', name);
  }
  void getName(WidgetRef ref) {
    final prefs = ref.watch(sharedPreferencesProvider);
    // my_nameキーの値を取得(空だった場合は空文字を返す)
    String prefName = prefs.getString('my_name') ?? '';
    state = prefName;
  }
}
final sharedPrefTestProvider = NotifierProvider<SharedPrefTest, String>(() {
  return SharedPrefTest();
});
// ...
ElevatedButton(
    child: const Text('名前変更'),
    onPressed: (){
        var msg = _messageController.text.trim();
        if(msg.isEmpty){
            return;
        }
        // shared_preferences周りの処理
        // 入力した名前の登録
        providerNotifier.setName(ref, msg);
        // 入力した名前で状態を更新
        providerNotifier.getName(ref);
        _messageController.clear();
    },
),

最後に

今回は、FlutterでローカルにKey-Valueでデータ管理するshared_preferencesを試してみたことを記事にしました。
どなたかの参考になると幸いです。