在 Flutter 应用程序中读取以前存储在本机应用程序中的共享首选项
Read shared preferences in Flutter app that were previously stored in a native app
我有以下问题:现在 PlayStore 中有一个用本机代码(iOS 和 Android)编写的应用程序,我正计划将其迁移到 flutter。我的目标是用户不会注意到引擎盖下的变化,但可以像以前一样继续使用该应用程序。为此,我还需要迁移共享首选项。然而,这是相当困难的。在本机 Android 应用程序中,我存储了相关的共享首选项,如下所示:
SharedPreferences sharedPrefs = context.getSharedPreferences(
"storage",
Context.MODE_PRIVATE
);
sharedPrefs.putString('guuid', 'guuid_value');
editor.apply();
这会导致在此路径中创建一个文件:
/data/data/patavinus.patavinus/shared_prefs/storage.xml
内容如下:
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<int name="guuid" value="guuid_value" />
</map>
如果我在Flutter中使用shared_prefences通过这样做来获取这个值:
final sharedPreferences = await SharedPreferences.getInstance();
sharedPreferences.getString('guuid');
它 returns null 因为它寻找
/data/data/patavinus.patavinus/shared_prefs/FlutterSharedPreferences.xml
是在 Flutter 中使用 shared_preferences 存储共享首选项时写入的文件。因为共享首选项是在本机应用程序上下文中编写的,所以该文件显然不存在。
有什么办法可以让Flutter在不使用平台通道的情况下寻找/data/data/patavinus.patavinus/shared_prefs/storage.xml
?
我知道它是如何反过来工作的,就像这里提到的:。这种方式很简单,因为在 Android 中你可以选择添加 Flutter 的前缀。然而,在 Flutter 中你不能。
我也知道这个插件:https://pub.dev/packages/native_shared_preferences 但是,我不敢相信第三方插件是推荐的方式。此外,我已经将相关的共享首选项分布在多个资源文件中。在这个插件中,你只能设置一个(通过指定字符串资源flutter_shared_pref_name
)。
按照建议here,您可以获取本机文件的内容并将其复制到新文件中。当用户第一次升级到你的flutter应用时,你可以将内容复制到flutter的存储文件中。
I am also aware of this plugin: https://pub.dev/packages/native_shared_preferences however, I can't believe that a third party plugin is the recommended way.
我觉得第三方插件就好了。 Flutter 最好的地方在于它令人惊叹的社区,它不断地为生态系统做出贡献。也就是说,我不建议继续使用非官方图书馆。因此,您可以测试在幕后迁移您的共享首选项。
- 在您的
initState
查询中查找 flutter 共享首选项。
- 如果存在,请跳过。
- 如果不存在,查询本机共享首选项,如果存在则复制
完成了。这将有助于迁移恕我直言。
最好的选择是将本机部分的共享首选项存储在与 SharedPreferences 插件相同的文件中。所以这意味着这样做:
- 在 Java 代码中用相同的密钥替换您的 SharedPreferences 代码,就像在插件中一样:
SharedPreferences sharedPref = context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE);
- 在本机部分中,保存前缀为“flutter.”的所有首选项。所以在本地部分你会得到像这样的需要的偏好:
String test = sharedPref.getString("flutter.test", "");
我的选择是将其视为一个文件。
在 flutter 中读取 android 共享首选项文件
/data/data/patavinus.patavinus/shared_prefs/storage.xml
按照您处理其他 xml 文件的方式获取值。
如果你想做一些改变,不要使用sharedPrefs.put,而是写入文件(/data/data/patavinus.patavinus/shared_prefs/storage.xml
) 在flutter中使用文件的write方法
由于我的问题没有对两个主要操作系统都适用的令人满意的答案,并且考虑到多个资源文件的分布,我自己编写了一个平台渠道解决方案。
在 Android (Kotlin) 上,我让调用者提供一个“文件”参数,因为可以将数据分布在多个资源文件中(就像我描述的那样在我的问题中)。
package my.test
import android.content.*
import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity: FlutterActivity() {
private val CHANNEL = "testChannel"
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
call, result ->
when (call.method) {
"getStringValue" -> {
val key: String? = call.argument<String>("key");
val file: String? = call.argument<String>("file");
when {
key == null -> {
result.error("KEY_MISSING", "Argument 'key' is not provided.", null)
}
file == null -> {
result.error("FILE_MISSING", "Argument 'file' is not provided.", null)
}
else -> {
val value: String? = getStringValue(file, key)
result.success(value)
}
}
}
else -> {
result.notImplemented()
}
}
}
}
private fun getStringValue(file: String, key: String): String? {
return context.getSharedPreferences(
file,
Context.MODE_PRIVATE
).getString(key, null);
}
}
在 iOS (Swift) 这不是必需的,因为我正在使用 UserDefaults
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let platformChannel = FlutterMethodChannel(
name: "testChannel",
binaryMessenger: controller.binaryMessenger
)
platformChannel.setMethodCallHandler({
[weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in
guard call.method == "getStringValue" else {
result(FlutterMethodNotImplemented)
return
}
if let args: Dictionary<String, Any> = call.arguments as? Dictionary<String, Any>,
let number: String = args["key"] as? String, {
self?.getStringValue(key: key, result: result)
return
} else {
result(
FlutterError.init(
code: "KEY_MISSING",
message: "Argument 'key' is not provided.",
details: nil
)
)
}
})
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
private func getStringValue(key: String, result: FlutterResult) -> String? {
let fetchedValue: String? = UserDefaults.object(forKey: key) as? String
result(fetchedValue)
}
我希望 SharedPreferences
包能够添加省略 flutter 前缀的可能性,使开发人员能够从本机应用程序无缝迁移内容。
我还写了一篇博客post,更详细地解释了问题和解决方案:https://www.flutterclutter.dev/flutter/tutorials/read-shared-preferences-from-native-apps/2021/9753/
我有以下问题:现在 PlayStore 中有一个用本机代码(iOS 和 Android)编写的应用程序,我正计划将其迁移到 flutter。我的目标是用户不会注意到引擎盖下的变化,但可以像以前一样继续使用该应用程序。为此,我还需要迁移共享首选项。然而,这是相当困难的。在本机 Android 应用程序中,我存储了相关的共享首选项,如下所示:
SharedPreferences sharedPrefs = context.getSharedPreferences(
"storage",
Context.MODE_PRIVATE
);
sharedPrefs.putString('guuid', 'guuid_value');
editor.apply();
这会导致在此路径中创建一个文件:
/data/data/patavinus.patavinus/shared_prefs/storage.xml
内容如下:
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<int name="guuid" value="guuid_value" />
</map>
如果我在Flutter中使用shared_prefences通过这样做来获取这个值:
final sharedPreferences = await SharedPreferences.getInstance();
sharedPreferences.getString('guuid');
它 returns null 因为它寻找
/data/data/patavinus.patavinus/shared_prefs/FlutterSharedPreferences.xml
是在 Flutter 中使用 shared_preferences 存储共享首选项时写入的文件。因为共享首选项是在本机应用程序上下文中编写的,所以该文件显然不存在。
有什么办法可以让Flutter在不使用平台通道的情况下寻找/data/data/patavinus.patavinus/shared_prefs/storage.xml
?
我知道它是如何反过来工作的,就像这里提到的:
我也知道这个插件:https://pub.dev/packages/native_shared_preferences 但是,我不敢相信第三方插件是推荐的方式。此外,我已经将相关的共享首选项分布在多个资源文件中。在这个插件中,你只能设置一个(通过指定字符串资源flutter_shared_pref_name
)。
按照建议here,您可以获取本机文件的内容并将其复制到新文件中。当用户第一次升级到你的flutter应用时,你可以将内容复制到flutter的存储文件中。
I am also aware of this plugin: https://pub.dev/packages/native_shared_preferences however, I can't believe that a third party plugin is the recommended way.
我觉得第三方插件就好了。 Flutter 最好的地方在于它令人惊叹的社区,它不断地为生态系统做出贡献。也就是说,我不建议继续使用非官方图书馆。因此,您可以测试在幕后迁移您的共享首选项。
- 在您的
initState
查询中查找 flutter 共享首选项。 - 如果存在,请跳过。
- 如果不存在,查询本机共享首选项,如果存在则复制
完成了。这将有助于迁移恕我直言。
最好的选择是将本机部分的共享首选项存储在与 SharedPreferences 插件相同的文件中。所以这意味着这样做:
- 在 Java 代码中用相同的密钥替换您的 SharedPreferences 代码,就像在插件中一样:
SharedPreferences sharedPref = context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE);
- 在本机部分中,保存前缀为“flutter.”的所有首选项。所以在本地部分你会得到像这样的需要的偏好:
String test = sharedPref.getString("flutter.test", "");
我的选择是将其视为一个文件。
在 flutter 中读取 android 共享首选项文件
/data/data/patavinus.patavinus/shared_prefs/storage.xml
按照您处理其他 xml 文件的方式获取值。
如果你想做一些改变,不要使用sharedPrefs.put,而是写入文件(/data/data/patavinus.patavinus/shared_prefs/storage.xml ) 在flutter中使用文件的write方法
由于我的问题没有对两个主要操作系统都适用的令人满意的答案,并且考虑到多个资源文件的分布,我自己编写了一个平台渠道解决方案。
在 Android (Kotlin) 上,我让调用者提供一个“文件”参数,因为可以将数据分布在多个资源文件中(就像我描述的那样在我的问题中)。
package my.test
import android.content.*
import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity: FlutterActivity() {
private val CHANNEL = "testChannel"
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
call, result ->
when (call.method) {
"getStringValue" -> {
val key: String? = call.argument<String>("key");
val file: String? = call.argument<String>("file");
when {
key == null -> {
result.error("KEY_MISSING", "Argument 'key' is not provided.", null)
}
file == null -> {
result.error("FILE_MISSING", "Argument 'file' is not provided.", null)
}
else -> {
val value: String? = getStringValue(file, key)
result.success(value)
}
}
}
else -> {
result.notImplemented()
}
}
}
}
private fun getStringValue(file: String, key: String): String? {
return context.getSharedPreferences(
file,
Context.MODE_PRIVATE
).getString(key, null);
}
}
在 iOS (Swift) 这不是必需的,因为我正在使用 UserDefaults
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let platformChannel = FlutterMethodChannel(
name: "testChannel",
binaryMessenger: controller.binaryMessenger
)
platformChannel.setMethodCallHandler({
[weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in
guard call.method == "getStringValue" else {
result(FlutterMethodNotImplemented)
return
}
if let args: Dictionary<String, Any> = call.arguments as? Dictionary<String, Any>,
let number: String = args["key"] as? String, {
self?.getStringValue(key: key, result: result)
return
} else {
result(
FlutterError.init(
code: "KEY_MISSING",
message: "Argument 'key' is not provided.",
details: nil
)
)
}
})
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
private func getStringValue(key: String, result: FlutterResult) -> String? {
let fetchedValue: String? = UserDefaults.object(forKey: key) as? String
result(fetchedValue)
}
我希望 SharedPreferences
包能够添加省略 flutter 前缀的可能性,使开发人员能够从本机应用程序无缝迁移内容。
我还写了一篇博客post,更详细地解释了问题和解决方案:https://www.flutterclutter.dev/flutter/tutorials/read-shared-preferences-from-native-apps/2021/9753/