在Flutter应用中读取之前在原生应用中存储的共享首选项

14

我有以下问题:目前在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_preferences通过这样做获取值:

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)。


您可以使用MethodChannel。请参见此文章 - creativecreatorormaybenot
1
他说他不想使用 PlatformChannel - Michel Feinstein
Flutter使用自己的名称来命名sharedprefs https://github.com/flutter/plugins/blob/master/packages/shared_preferences/shared_preferences/android/src/main/java/io/flutter/plugins/sharedpreferences/MethodCallHandlerImpl.java 在这里查看。如果您使用与存储在Android首选项中相同的名称,那么我相信Flutter shared_prefs可以读取它。尽管没有经过测试。 - Hemanth S
5个回答

6

按照此处建议,您可以获取本地文件的内容并将其复制到新文件。当用户首次升级到您的Flutter应用程序时,您可以将内容复制到Flutter的存储文件中。


这确实是针对Android平台的不错建议。然而,主要缺点是它不能在iOS上以这种方式工作 :(. - Schnodderbalken
在iOS上,它似乎只是在键前缀中加入 flutter.。因此,创建平台通道比您想象的要容易,您可以创建类似于 的函数,而不需要 flutter 前缀。 - Sami Haddad

4

最佳选择是将共享偏好存储在本地部分中,与SharedPreferences插件存储在同一文件中相同。因此,要这样做:

  1. 在Java代码中,用插件中相同的键替换您的SharedPreferences代码:SharedPreferences sharedPref = context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE);
  2. 在本地部分中,使用前缀“flutter.”保存所有偏好设置。因此,在本地部分中,您可以像这样获取所需的偏好设置:String test = sharedPref.getString("flutter.test","");

这不是一个可选项,因为我无法访问原生应用程序并且无法迁移密钥。我只有现有的原生应用程序和迁移到Flutter的愿望。 - Schnodderbalken
@Schnodderbalken,你可以通过在Flutter上创建应用程序来迁移到Flutter。如果你正在使用Flutter制作应用程序,你仍然可以编写本地代码。 - Andris
你建议“将共享首选项存储在本地部分中,与SharedPreferences插件存储方式相同”。但是这些首选项已经被存储了。有一些本地应用程序没有使用flutter前缀进行存储。那么我该如何访问它们呢?这就是我的问题所在。 - Schnodderbalken
@Schnodderbalken 你可以使用特定的偏好键读取它们,并将它们存储在一个新文件中,该文件使用与Flutter相同的键。 - Andris

3

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

Android(Kotlin)中,我让调用者提供一个"file"参数,因为数据可以分散在多个资源文件中(就像我在问题中描述的那样)。

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前缀的可能性,使开发者能够轻松地从原生应用程序迁移内容。
我还撰写了一篇博客文章,更详细地解释了问题和解决方案:https://www.flutterclutter.dev/flutter/tutorials/read-shared-preferences-from-native-apps/2021/9753/

1
这确实很烦人,我不敢相信你和我是地球上唯一从原生Android迁移到Flutter并想要保留偏好设置的两个人?这是开发者的一个重大缺陷,尤其是考虑到SharedPreferences被广泛使用。感谢您提出这个问题并提供答案。 - johnw182

0

我的选择是将其视为一个文件。
在Flutter中读取Android共享偏好文件。

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

按照你处理其他XML文件的方式取值。

如果你想做一些改动,不要使用sharedPrefs.put,而是使用文件的写入方法将内容写入文件(/data/data/patavinus.patavinus/shared_prefs/storage.xml)中,在Flutter中进行操作。


@Schnodderbalken 我不知道iOS,因为我没有在上面工作过。 - prabhu r
我理解。但这正是我的问题所在。我正在寻找适用于两个操作系统的解决方案。 - Schnodderbalken
如果您知道如何在Android和iOS中读写文件,那就足够了。在应用程序运行于Android时,加入一个if条件语句,使用本地方法(Java)和方法通道将其写入文件;否则,在else块中使用iOS本地方式和方法通道进行操作。 - prabhu r

0
我也知道这个插件:https://pub.dev/packages/native_shared_preferences,但是我不相信第三方插件是推荐的方式。
我认为第三方插件还是挺好的。Flutter最好的一点就是它拥有一个很棒的社区,这个社区一直在为生态系统做出全面的贡献。但是话虽如此,我不建议继续使用非官方库。所以你可以尝试在后台迁移你的共享参数。
  • 在你的initState中查询Flutter共享参数。
  • 如果存在,则跳过。
  • 如果不存在,则查询原生共享参数,如果存在则复制。
完成。我认为这样做将有助于迁移。

1
说实话,这不是做事的理想方式。我有兴趣为此制作一个插件。 - Raveesh Agarwal

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接