potatoxchip
potatoxchip

Reputation: 565

How to interact with db from widget in MVVM pattern

I have an application which fetches the data from api and caches in db. I want to be able to show this data (from db) in the widget and also add a button to update the data, which when pressed will fetch the data and update the db and hence update the widget. The problem is since I'm using mvvm, Im not sure if at all it is possible to make use of livedata and fancy supports of jetpack components.

So I just created a Dao reference in widget update fun and fetched the db. That does fetch the data but it is not updating the textview, i logged it and it showed me the data correctly.

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

the dao has a suspended fun for this.

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

Can you please tell me how to interact with the db and the proper means to interact with the repository of application?

Upvotes: 6

Views: 2456

Answers (2)

miecio
miecio

Reputation: 335

Since You're using suspended functions for Room, You can use it in CoroutineScope with Dispatchers.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)
}

In case You don't use suspend functions or even Room, You can use Dispatchers.IO to load data async and update RemoteViews in the same thread, without withContext(Dispatchers.Main) because RemoteViews is not 'ours', we just telling launcher that he should set this data on that view. This is by default done in another, no-UI thread - no even in thread created/owned by app.

This is the only way I found, because since AppWidgetProvider is BroadcastReceiver, You cannot use Architecture Components in it (https://stackoverflow.com/a/47292260/4265500). Just force update widget whenever You update DB (in JobIntentService, Worker (WorkManager), Coroutines, wheatever) and get the latest record in updateAppWidget() (like You're doing right now).

Here's handy extension function to force widget update:

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

Upvotes: 8

solaza
solaza

Reputation: 1291

If you want text to be updated you can use LiveData in your DAO.

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

Why you shouldn't use coroutines with LiveData

If you hame multiple CurrentMetric you sould use LiveData<List<CurrentMetric>>

Then everything should be feched from model.

In model you should have something like

fun getCurrentMetricLD() = dao.getCurrentMetricLD()

So it only gets data from database.

Then in ViewModel you need LiveData that is then observed in Fragment/Activity.

val currentMetricLD: LiveData<CurrentMetric> = model.getCurrentMetricLD()

In Fragment/Activity

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

I would recommend you checking this free tutorial made by Google.

Upvotes: 0

Related Questions