【问题标题】:Read shared preferences in Flutter app that were previously stored in a native app在 Flutter 应用程序中读取以前存储在本机应用程序中的共享首选项
【发布时间】:2020-09-20 16:31:28
【问题描述】:

我有以下问题:现在 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');

它返回 null 因为它正在寻找

/data/data/patavinus.patavinus/shared_prefs/FlutterSharedPreferences.xml 这是在 Flutter 中使用 shared_preferences 存储共享首选项时写入的文件。因为共享首选项是在本机应用程序上下文中编写的,所以文件显然不存在。

有没有什么方法可以让 Flutter 在不使用平台通道的情况下寻找/data/data/patavinus.patavinus/shared_prefs/storage.xml

我知道它是如何工作的,就像这里提到的那样:How to access flutter Shared preferences on the android end (using java)。这种方式很简单,因为在 Android 中你可以选择在 Flutter 的前缀前面加上前缀。但是,在 Flutter 中你不能。

我也知道这个插件:https://pub.dev/packages/native_shared_preferences 但是,我不敢相信第三方插件是推荐的方式。此外,我将相关的共享首选项分布在多个资源文件中。在这个插件中,你只能设置一个(通过指定字符串资源flutter_shared_pref_name)。

【问题讨论】:

  • 您可以使用MethodChannels。见this article
  • 他说他不想使用PlatformChannel
  • Flutter 使用它自己的 sharedprefs 名称 github.com/flutter/plugins/blob/master/packages/… 在这里查看。如果您使用存储在 Android 首选项中的相同名称,那么我相信颤振 shared_prefs 可以读取。虽然没有测试

标签: flutter migration sharedpreferences flutter-platform-channel


【解决方案1】:

按照here 的建议,您可以获取本机文件的内容并将其复制到新文件中。当用户第一次升级到你的 Flutter 应用时,你可以将内容复制到 Flutter 的存储文件中。

【讨论】:

  • 这对于安卓平台来说确实是一个不错的建议。然而,主要的缺点是它不适用于 iOS :(.
  • 在 iOS 上,它们似乎只在键前面加上 flutter.。所以创建一个平台频道比你想象的要容易,你可以创建一个类似this的函数,不用flutter前缀。
【解决方案2】:

最好的选择是将共享首选项存储在与 SharedPreferences 插件相同的文件中的本机部分。所以这意味着这样做:

  1. 在 Java 代码中替换您的 SharedPreferences 代码,使用与插件中相同的键:SharedPreferences sharedPref = context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE);
  2. 在原生部分保存所有带有前缀“flutter.”的首选项。因此,在本机部分,您将获得所需的首选项,例如:String test = sharedPref.getString("flutter.test", "");

【讨论】:

  • 这不是一个选项,因为我无权访问本机应用程序并且无法迁移密钥。我只有现有的原生应用和迁移到 Flutter 的愿望。
  • @Schnodderbalken 您只能通过在 Flutter 上创建应用程序来迁移到 Flutter。如果你在 Flutter 上做应用,你仍然可以编写原生代码。
  • 您建议“将本机部分的共享首选项存储在与 SharedPreferences 插件相同的文件中”。但偏好已存储。有一些本地应用程序没有使用颤振前缀存储它。那么如何访问它们呢?这就是我的问题所在。
  • @Schnodderbalken 您可以使用特定的首选项键读取它们并将它们存储在新文件中,该文件使用与颤振使用相同的键。
【解决方案3】:

由于对我的问题没有令人满意的答案,适用于两个主要操作系统并考虑到多个资源文件的分布,我自己编写了一个平台通道解决方案。

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 包可以添加省略颤振前缀的可能性,使开发人员能够从原生应用无缝迁移内容。

我还写了一篇博文,更详细地解释了问题和解决方案:https://www.flutterclutter.dev/flutter/tutorials/read-shared-preferences-from-native-apps/2021/9753/

【讨论】:

    【解决方案4】:

    我也知道这个插件:https://pub.dev/packages/native_shared_preferences 但是,我不敢相信第三方插件是推荐的方式。

    我认为第三方插件很好。 Flutter 最棒的地方在于它令人惊叹的社区,它不断为生态系统做出贡献。也就是说,我不建议继续使用非官方库。因此,您可以测试在幕后迁移您的共享偏好。

    • 在您的initState 查询中获取颤振共享偏好。
    • 如果存在,请跳过。
    • 如果不存在,则查询本机共享首选项,如果存在则复制

    完成了。这将有助于迁移恕我直言。

    【讨论】:

    • 说实话,这不是理想的做事方式。我有兴趣为此制作一个插件。
    【解决方案5】:

    我的选择是将其视为文件。
    在flutter中读取android共享首选项文件

    /data/data/patavinus.patavinus/shared_prefs/storage.xml
    

    以您处理其他 xml 文件的方式获取值。

    如果要进行一些更改,不要使用sharedPrefs.put,而是写入文件(/data/data/patavinus.patavinus/shared_prefs/storage.xml )在flutter中使用文件的write方法

    【讨论】:

    • @Schnodderbalken 我不了解iOS,因为我没有研究过它?
    • 我明白了。但这就是我的问题所在。我正在寻找一种适用于两种操作系统的解决方案。
    • 如果您知道如何在 android 和 iOS 中读写文件就足够了。设置一个 if 条件来检查应用程序是否在 android 上运行,并使用本地方法(java)和方法通道将其写入文件中,在 else 块中执行 iOS 本地方式和方法通道
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多