Broadcast Receiver中如何观察Flow?

How to observe Flow in Broadcast Receiver?

我有一个使用共享首选项实现的应用程序小部件。
现在我正在努力将其迁移到 Data Store.

这里的问题是如何observe/collect AppWidgetProvider(BroadCastReceiver 的子class)中的流数据?

重现问题的最少代码。

MyAppWidgetProvider:

class MyAppWidgetProvider : AppWidgetProvider() {

    override fun onUpdate(
        context: Context,
        appWidgetManager: AppWidgetManager,
        appWidgetIds: IntArray
    ) {
        for (appWidgetId in appWidgetIds) {
            updateAppWidget(context, appWidgetManager, appWidgetId)
        }
    }
}

internal fun updateAppWidget(
    context: Context,
    appWidgetManager: AppWidgetManager,
    appWidgetId: Int
) {

    RemoteViews(context.packageName, R.layout.widget_layout).also { views ->

        val data = loadDataFromPreferences(context, appWidgetId)
        views.setTextViewText(
            R.id.textview_title,
            data.title
        )
        appWidgetManager.updateAppWidget(appWidgetId, views)
    }
}

DataStoreUtil:

internal fun loadDataFromPreferences(context: Context, appWidgetId: Int): Flow<Data> {
    val dataStore: DataStore<Preferences> = context.createDataStore(
        name = PREFS_NAME,
        migrations = listOf(SharedPreferencesMigration(context, PREFS_NAME))
    )
    val PREF_TITLE = stringPreferencesKey(PREF_PREFIX_KEY + appWidgetId + PREF_SUFFIX_TITLE)

    return dataStore.data
        .catch {
            if (it is IOException) {
                it.printStackTrace()
                emit(emptyPreferences())
            } else {
                throw it
            }
        }
        .map { preferences ->
            // No type safety.
            val title = preferences[PREF_TITLE] ?: ""
            Data(title)
        }
}

注:

  1. 数据 - 自定义模型 class
  2. loadDataFromPreferences() return 使用共享首选项时类型为 Data。将其更改为 DataStore 的 Flow<Data>,这会导致 updateAppWidget() 行中的错误:
    val data = loadDataFromPreferences(context, appWidgetId) - 因为数据类型已更改为 Flow。

您可以使用 collectFlow

获取数据
val result = loadDataFromPreferences(context, appWidgetId)
CoroutineScope(Dispatchers.Main).launch{
   result.collect{ data ->
       views.setTextViewText(
            R.id.textview_title,
            data.title
   }
}

感谢 rajan kt 的回答。
CoroutineScope(Dispatchers.Main).launch 开始,这是解决方案的主要方面。

但是,collect() 没有按预期工作。尝试使用 first() 来解决问题。

发布下面的工作代码:

MyAppWidgetProvider:

class MyAppWidgetProvider : AppWidgetProvider() {

    override fun onUpdate(
        context: Context,
        appWidgetManager: AppWidgetManager,
        appWidgetIds: IntArray
    ) {
        for (appWidgetId in appWidgetIds) {
            CoroutineScope(Dispatchers.Main).launch {
                updateAppWidget(context, appWidgetManager, appWidgetId)
            }
        }
    }
}

internal suspend fun updateAppWidget(
    context: Context,
    appWidgetManager: AppWidgetManager,
    appWidgetId: Int
) {

    RemoteViews(context.packageName, R.layout.widget_layout).also { views ->
        loadDataFromPreferences(context, appWidgetId)
            .first {
                views.setTextViewText(
                    R.id.textview_title,
                    data.title
                )
                appWidgetManager.updateAppWidget(appWidgetId, views)
                true
            }
    }
}

DataStoreUtil:

internal suspend fun loadDataFromPreferences(context: Context, appWidgetId: Int): Flow<Data> {
    val dataStore: DataStore<Preferences> = context.createDataStore(
        name = PREFS_NAME,
        migrations = listOf(SharedPreferencesMigration(context, PREFS_NAME))
    )
    val PREF_TITLE = stringPreferencesKey(PREF_PREFIX_KEY + appWidgetId + PREF_SUFFIX_TITLE)
    return dataStore.data
        .catch {
            if (it is IOException) {
                it.printStackTrace()
                emit(emptyPreferences())
            } else {
                throw it
            }
        }
        .map { preferences ->
            // No type safety.
            val title = preferences[PREF_TITLE] ?: ""
            Data(title)
        }
}