こんにちは。
BPSの福岡拠点として一緒にお仕事させてもらっています、ウイングドアのウメバヤシです。
Flutterでローカルに値を保存したい時はFlutter公式のshared_preferencesパッケージを使うと思いますが、実はこのパッケージではネイティブアプリをリプレイスする際に、以前のネイティブアプリバージョンで保存していたローカルデータまでは取得することができません。
⚓ 前提
対象のshared_preferencesパッケージは現時点で最新Verの0.5.12+4
です。
shared_preferences | Flutter Package - Pub.Dev
⚓ なぜネイティブアプリで保存されたデータが取得できないのか?
結論からいうと、shared_preferencesパッケージでは読み書きの際に「flutter.」というプレフィックスをキーに付与してデータを扱っているからです。
「hoge」というキーで取得しようとしても、実際には「flutter.hoge」というキーで値を取得しにいきます。また、「hoge」というキーで書き込みしても実際には「flutter.hoge」というキーで端末に保存されます。
ネイティブアプリバージョンで保存された値のキーは、もちろんプレフィックスなしで保存されていますので、純粋な「hoge」というキーで保存されている値はshared_preferencesパッケージでは取得できないのです。
⚓ プレフィックスが定義されている場所
GitHubでパッケージのソースコードを読んでみました。
以下のようにプレフィックスがプライベートでconst
定義されています。
static const String _prefix = 'flutter.';
plugins/shared_preferences.dart at e014c208909772cee2328a91b7225e667a2681a9 · flutter/pluginsより
⚓ setter
以下のコードの_store
がネイティブ側のローカルデータを操作するオブジェクトで、_preferenceCache
はFlutter側で扱う値を保持しているオブジェクトです。
setterではネイティブ側にプレフィックス付きのキーで値を保存していることがわかります。
Future<bool> _setValue(String valueType, String key, Object value) {
final String prefixedKey = '$_prefix$key';
if (value == null) {
_preferenceCache.remove(key);
return _store.remove(prefixedKey);
} else {
if (value is List<String>) {
// Make a copy of the list so that later mutations won't propagate
_preferenceCache[key] = value.toList();
} else {
_preferenceCache[key] = value;
}
return _store.setValue(valueType, prefixedKey, value);
}
}
plugins/shared_preferences.dart at e014c208909772cee2328a91b7225e667a2681a9 · flutter/pluginsより
⚓ getter
以下の_getSharedPreferencesMap()
メソッドで返されているpreferencesMap
は、SharedPreferences
のgetInstance()
呼び出し時に、Flutterでのデータの出し入れに使う_preferenceCache
に格納されることになるのですが、その際に全てのキーからプレフィックスが削除されているのがわかります。
また、ネイティブ側と直接通信している_store.getAll()
メソッドでは、それぞれのOSのMethod Channelで、キーにプレフィックスが付与されてる値のみが返ってくるようになっています。
static Future<Map<String, Object>> _getSharedPreferencesMap() async {
final Map<String, Object> fromSystem = await _store.getAll();
assert(fromSystem != null);
// Strip the flutter. prefix from the returned preferences.
final Map<String, Object> preferencesMap = <String, Object>{};
for (String key in fromSystem.keys) {
assert(key.startsWith(_prefix));
preferencesMap[key.substring(_prefix.length)] = fromSystem[key];
}
return preferencesMap;
}
plugins/shared_preferences.dart at e014c208909772cee2328a91b7225e667a2681a9 · flutter/pluginsより
⚓ どうしたらいいのか?
このプレフィックスについてはプライベートで定数定義されているので、削除したり変更したりできません。
プレフィックスを自由に設定できるように改修したプルリクエストもいくつかあるようですが、まだ採用はされていないようです。
解決法としてはnative_shared_preferencesというパッケージを使って、ネイティブからプレフィックスなしで値を取って来るのが今のところ一番手っ取り早そうです。
参考: native_shared_preferences | Flutter Package
このパッケージの説明にもありますが、version_migrationというパッケージを併用して、以前のネイティブアプリバージョンからの取得のみに使用してくださいとのことなので、ネイティブアプリからFlutterアプリへのリプレイス時に一度だけマイグレーションさせるのが良さそうです。
参考: version_migration | Flutter Package
⚓ 注意点
native_shared_preferencesですが、iOSでDate型で保存していた値をミリ秒のUNIX時間としてDouble型で取得してきたり、Double型のデータに計算誤差のようなものがついてきたり(例:199.9→199.89999389648438)するみたいなので、少し癖があるようです。
実際にマイグレーションする際には正確に値が移行できているかテストする必要がありそうです。
おそらく上記のように正確に値が取得できない部分があるので、常用するのはやめましょうということなんだと思います。
⚓ まとめ
リプレイス自体そこまでケースとして多くはないと思いますが、すんなりshared_preferencesパッケージでそのまま利用できると思っていると、意外と面倒なことになっているので、一つ知識として持っておくと良いかもしれません。
株式会社ウイングドアでは、Ruby on RailsやPHPを活用したwebサービス、webサイト制作を中心に、
スマホアプリや業務系システムなど様々なシステム開発を承っています。