【问题标题】:I want to use Kotlin Flow to update my UI when SharedPreferences have changed当 SharedPreferences 发生变化时,我想使用 Kotlin Flow 来更新我的 UI
【发布时间】:2021-11-19 15:12:56
【问题描述】:

好的,我想像所有酷孩子一样开始使用 Kotlin-Flow。看起来我想做的事情符合这个 reactive 模式。所以我在后台收到一条 Firebase 消息

...

override fun onMessageReceived(remoteMessage: RemoteMessage) {
    super.onMessageReceived(remoteMessage)
    val msg = gson.fromJson(remoteMessage.data["data"], MyMessage::class.java)
    // persist to SharedPreferences
    val flow = flow<MyMessage> { emit(msg) }

我有一个仪表板用户界面,它可以简单地刷新带有此消息的横幅。不确定如何从我的 DashboardViewModel观察收集这条消息。示例和教程似乎都在同一个类中发出和收集。听起来我需要更多的指导和更多的经验,但找到更多真实世界的例子并不多。

【问题讨论】:

  • 您可能还想查看一些更酷的东西:Android Datastore。它是 SharedPrefs 的替代品。这会发出流,因此您可以轻松地在 UI 中查询和收集它。如果您不想迁移,可以讨论其他解决方案。
  • @ArpitShukla 我确实计划在接下来的几个月内迁移到 DataStore。花了几个小时查看 Preferences DataStore,我喜欢它。

标签: android kotlin sharedpreferences kotlin-flow


【解决方案1】:

查看 Kotlin 文档:https://kotlinlang.org/docs/flow.html#flows

基本思想是您创建一个Flow,它可以随着时间的推移产生价值。你在协程中运行collect(),它允许你在更新时异步处理它们。

通常,该流程在内部进行大量工作,并在生成值时发出值。您可以在类中将其用作一种工作任务,但很多时候您会将流公开为数据源,以供其他组件观察。因此,例如,当您尝试获取某个东西时,您会看到返回 Flow 的存储库 - 基本上是“好吧,我们还没有,但它会在这里实现”。


我不是这方面的专家,而且我知道关于不同的构建器和流程类型以及您如何 emit 对他们有一些注意事项 - 这并不总是像“创建流程,交回引用它,当它进来时向它发出数据”。实际上有一个 callbackFlow 构建器专门设计用于将回调与流模式接口,这可能值得一试: https://developer.android.com/kotlin/flow#callback

这个例子也是关于 Firebase 的——看起来这个想法广义上是用户请求一些数据,然后你返回一个在内部执行 Firebase 请求并提供回调的流。当它获取数据时,它使用offeremit 的特殊版本,处理来自不同协程上下文的回调)将数据输出到观察者。但这是相同的一般概念 - 流程所做的所有工作都封装在其中。它就像一个独立运行的任务,产生并输出值。

希望对您有所帮助!我认为,一旦您掌握了总体思路,就更容易遵循示例,然后了解StateFlowSharedFlow 之类的更专业的内容是做什么用的。这可能是一些有用的读物​​(来自 Android 开发者):

edit- 当我在寻找那些时,我看到了一个关于 Flows 的新开发峰会视频,它非常好!它很好地概述了它们的工作原理以及如何在您的应用中实现它们(尤其是对于需要考虑一些事项的 UI 内容):https://youtu.be/fSB6_KE95bU

【讨论】:

    【解决方案2】:

    flow&lt;MyMessage&gt; { emit(msg) } 可能只是flowOf(msg),但是将单个项目包装在 Flow 中很奇怪。如果您要对单个事物进行手动请求,则更适合使用返回该事物的挂起函数来处理。您可以使用 suspendCoroutine() 将异步回调代码转换为挂起函数,但 Firebase 已经提供了可以用来代替回调的挂起函数。如果您对随时间变化的数据进行重复请求,流将是合适的,但您需要通过使用callbackFlow 转换异步代码来将其做得更高。

    在这种情况下,您似乎正在使用 FirebaseMessagingService,它是一个 Android 服务,它使用此 onMessageReceived 函数直接充当回调。

    您可以做的(我之前没有尝试过)将本地 BroadcastReceiver 调整为您可以在应用程序的其他地方使用的 Flow。 FirebaseMessangingService 可以重新广播可以被此类 Flow 拾取的本地 Intent。因此,您可以使用这样的函数从本地广播中创建流。

    fun localBroadcastFlow(context: Context, action: String) = callbackFlow {
        val receiver = object : BroadcastReceiver() {
            override fun onReceive(context: Context, intent: Intent) {
                intent.extras?.run(::trySend)
            }
        }
        LocalBroadcastManager.getInstance(context).registerReceiver(receiver, IntentFilter(action))
        awaitClose { LocalBroadcastManager.getInstance(context).unregisterReceiver(receiver) }
    }
    

    然后在您的服务中,您可以通过伴随对象公开流,映射到您的数据类类型。

    class MyMessageService: FirebaseMessagingService() {
        companion object {
            private const val MESSAGE_ACTION = "mypackage.MyMessageService.MyMessage"
            private const val DATA_KEY = "MyMessage key"
            private val gson: Gson = TODO()
    
            fun messages(context: Context): Flow<MyMessage> =
                localBroadcastFlow(context, MESSAGE_ACTION)
                    .mapNotNull { bundle ->
                        val messageData = bundle.getString(DATA_KEY) ?: return@mapNotNull null
                        gson.fromJson(messageData, MyMessage::class.java)
                    }
        }
    
        override fun onMessageReceived(remoteMessage: RemoteMessage) {
            val intent = Intent(MESSAGE_ACTION)
            intent.putExtra(DATA_KEY, remoteMessage.data["data"])
            LocalBroadcastManager.getInstance(applicationContext).sendBroadcast(intent)
        }
    }
    

    然后在您的 Fragment 或 Activity 中,您可以从MyMessageService.messages() 收集。

    请注意,LocalBroadcastManager 最近已被弃用,因为它提倡将数据公开到应用的所有层的做法。我真的不明白为什么这应该被认为总是不好的。来自系统的任何广播对应用程序的所有层都是可见的。任何 http 地址对应用程序的所有层都是可见的,等等。他们建议公开可观察或 LiveData 作为替代方案,但这仍会将数据公开给应用程序的所有层。

    【讨论】:

    • 其实我已经弃用任何广播并且重新引入它不是我想做的。
    【解决方案3】:

    我创建了一个类来帮助我持久化我的数据,但还添加了一个可观察的流来发出当前接收到的消息。

    class MessagePersistence(
        private val gson: Gson,
        context: Context
    ) {
        private val sharedPreferences = context.getSharedPreferences(
            "Messaging", MODE_PRIVATE
        )
        private val _MyMessageFlow = MutableStateFlow<Message?>(null)
        var myMessageFlow: StateFlow<Message?> = __MyMessageFlow
    
    
        data class Message(
            val msg: String
        )
    
        var message: Message?
            get() = sharedPreferences
                .getString("MyMessages", null)
                ?.let { gson.fromJson(it, Message::class.java) }
            set(value) = sharedPreferences
                .edit()
                .putString("MyMessages", value?.let(gson::toJson))
                .apply()
                _MyMessageFlow.value = message
                myMessageFlow = _MyMessageFlow
    }
    
    

    在我的 viewModel 中,我通过它的构造函数注入这个类并将其定义为

    class MyViewModel(
    
        private val messagePersistence: MessagePersistence
    
    ) : ViewModel() {
    
        val myMessage = messagePersistence.myMessageFlow
    ...
    }
    

    然后在我的片段中,我可以使用观察者收集它。

    class MyFragment : Fragment() {
    
    ...
    
    viewModel.myMessage.observe(viewLifecycleOwner.lifecycleScope) {
    
                'update the UI with new message
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-01-28
      • 2021-10-27
      相关资源
      最近更新 更多