如何以 MVVM 模式与小部件中的数据库进行交互

How to interact with db from widget in MVVM pattern

我有一个从 api 获取数据并缓存在数据库中的应用程序。我希望能够在小部件中显示此数据(来自数据库)并添加一个按钮来更新数据,按下该按钮将获取数据并更新数据库,从而更新小部件。问题是因为我使用的是 mvvm,所以我不确定是否可以使用 livedata 和对 jetpack 组件的精美支持。

所以我刚刚在 widget update fun 中创建了一个 Dao 引用并获取了数据库。那确实获取了数据,但它没有更新文本视图,我记录了它并且它正确地向我显示了数据。

companion object {

        internal fun updateAppWidget(
            context: Context, appWidgetManager: AppWidgetManager,
            appWidgetId: Int
        ) {
            val db = AppDatabase.getInstance(context.applicationContext).weatherDao()
            val views = RemoteViews(context.packageName, R.layout.weather_widget)
            GlobalScope.launch {
                val weather = db.getCurrentWeatherMetricAsync()
                Log.d("TAG_TAG_TAG", "weather: " + weather.temperature);
                withContext(Dispatchers.Main) {
                    views.setTextViewText(R.id.tv_counter, " asd " + weather.temperature)
                }
            }
            appWidgetManager.updateAppWidget(appWidgetId, views)
        }
    }

道对此有悬念

    @Query("select * from current_weather where id = 0")
    suspend fun getCurrentWeatherMetricAsync(): CurrentMetric

你能告诉我如何与数据库交互以及与应用程序存储库交互的正确方法吗?

如果你想更新文本,你可以在你的 DAO 中使用 LiveData。

@Query("select * from current_weather where id = 0")
fun getCurrentWeatherMetricAsync(): LiveData<CurrentMetric>

Why you shouldn't use coroutines with LiveData

如果您有多个 CurrentMetric,您应该使用 LiveData<List<CurrentMetric>>

那么一切都应该从模型中获取。

在模型中你应该有这样的东西

fun getCurrentMetricLD() = dao.getCurrentMetricLD()

所以它只从数据库中获取数据。

然后在 ViewModel 中您需要 LiveData,然后在 Fragment/Activity.

中观察到它
val currentMetricLD: LiveData<CurrentMetric> = model.getCurrentMetricLD()

在Fragment/Activity

settingsViewModel.sifrBl.observe(this, Observer {
    //what happens on change
})

我建议您查看 Google 制作的 this 免费教程。

由于您正在为 Room 使用挂起功能,因此您可以在 CoroutineScopeDispatchers.Main (https://medium.com/androiddevelopers/room-coroutines-422b786dc4c5, https://medium.com/androiddevelopers/coroutines-on-android-part-i-getting-the-background-3e0e54d20bb) 中使用它。

CoroutineScope(Dispatchers.Main.immediate).launch {
     val weather = AppDatabase.getInstance(context.applicationContext).weatherDao().getCurrentWeatherMetricAsync()
     val views = RemoteViews(context.packageName, R.layout.weather_widget)
     views.setTextViewText(R.id.tv_counter, " asd " + weather.temperature)
     appWidgetManager.updateAppWidget(appWidgetId, views)
}

如果您不使用挂起功能甚至 Room,您可以使用 Dispatchers.IO 异步加载数据并在同一线程中更新 RemoteViews,无需 withContext(Dispatchers.Main) 因为 RemoteViews 不是 'ours',我们只是告诉启动器他应该在那个视图上设置这个数据。默认情况下,这是在另一个非 UI 线程中完成的 - 甚至在 app.created/owned 线程中也没有。

这是我找到的唯一方法,因为 AppWidgetProviderBroadcastReceiver,您不能在其中使用架构组件 ()。只需在更新数据库时强制更新小部件(在 JobIntentServiceWorkerWorkManager)、Coroutines、无所不在)并在 updateAppWidget() 中获取最新记录(例如你现在正在做)。

这是强制更新小部件的便捷扩展功能:

fun Context.updateWidget() {
    val widgetUpdateIntent = Intent(this, Widget::class.java).apply {
        action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
        putExtra(
            AppWidgetManager.EXTRA_APPWIDGET_IDS,
            AppWidgetManager.getInstance(this@updateWidget).getAppWidgetIds(
                ComponentName(
                    this@updateWidget,
                    Widget::class.java
                )
            )
        )
    }
    sendBroadcast(widgetUpdateIntent)
}