9

i wanted to access Room DB inside my widget class to update the widget according to the stored data.

the problem here is the ViewModelProvider require a fragment or activity to work with..

ViewModelProviders.of(fragment).get(YourViewModel.class);

which is not available in the app widget class so how to do it?

Amr Ahmed
  • 203
  • 3
  • 12

8 Answers8

6

If you use a RemoteViewsService to populate your widget UI, then you can avoid using an AsyncTask by including a DAO query in its onDataSetChanged() method, rather than in the AppWidgetProvider's onUpdate() method. As stated in the onDataSetChanged() documentation,

...expensive tasks can be safely performed synchronously within this method. In the interim, the old data will be displayed within the widget.

The other advantage here is that you can include a call to AppWidgetManager.notifyAppWidgetViewDataChanged() in your app's ViewModel observer's onChanged() method; this will trigger the onDataSetChanged() and the widget will update each time the Room database is changed, rather than just at the fixed interval for onUpdate().

Aaron Dunigan AtLee
  • 1,860
  • 7
  • 18
  • Can you please elaborate on where the `notifyAppWidgetViewDataChanged()` should be called? If it should be called within the ViewModel class, how are references to widgetIds retreived? – Aleksandar Stefanović Oct 23 '21 at 16:14
  • @AleksandarStefanović, I'm really sorry, but it's been a while since I worked on this, and I don't recall specific details. Maybe someone else can chime in with an answer. I recommend you post it as a new question. – Aaron Dunigan AtLee Oct 25 '21 at 02:42
6

I know it has been a while, but I just came across this issue and wanted to add something useful following the inspiration of fellow member Aaron-dunigan-atlee.

Basically showing where the database and Dao object go... and more importantly, the onDataSetChanged() method that has the call to the Room query. The example is for a Notetaking the app to illustrate, and as it was commented above, no need of AsyncTasks anywhere.

public class MyWidgetRemoteViewsService extends RemoteViewsService
{
    @Override
    public RemoteViewsFactory onGetViewFactory(Intent intent)
    {
        return new MyRemoteViewsFactory(this.getApplicationContext(), intent);
    }

    class MyRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory
    {
        private NotesDao notesDao;
        private List<Note> allNotes;

        MyRemoteViewsFactory(Context context, Intent intent)
        {
            MyDb db;
            db = MyDatabase.getDatabase(context);
            notesDao = db.notesDao();
        }

        @Override
        public void onCreate() { }

        @Override
        public void onDataSetChanged()
        {
            allNotes = notesDao.getAllNotes();
        }

        @Override
            public int getCount()
            { ...

sasa

JAY PATEL
  • 559
  • 4
  • 17
JaviMar
  • 375
  • 5
  • 18
4

So answering my self here..

the only way that worked is to use the context to get the DB instance from the abstract class and run the DAO queries directly in background thread without the ViewModelProviders class

    new AsyncTask<Context, Void, List<Data>>(){
        @Override
        protected List<Quote> doInBackground(Context... context) {

            List<Data> List=null;
                AppDatabase db = AppDatabase.getDatabase(context[0]);
                List = db.DataModel().getData();


            return List;
        }

        @Override
        protected void onPostExecute(List<Quote> List) {
            super.onPostExecute(List);

            if(List != null){

                final Random random=new Random();
                for (int appWidgetId : appWidgetIds) {
                    updateAppWidget(context, appWidgetManager, appWidgetId, quoteList.get(random.nextInt(List.size())));
                }

            }

        }

    }.execute(context);

}
Amr Ahmed
  • 203
  • 3
  • 12
2

you can access Room without passing through ViewModel.. ViewModel is just a component of the MVVM architecture, just like MVC or MVP.. without ViewModel, you can still access Room, you just need to have a context

0

Exactly, you can do that.. you already need to make this DAO query from a background thread, so passing a context to an AsyncTask to execute the query is a good solution, but try to pass context in the constructor of the AsyncTask instead of passing it in the params.

0

You can write a Repository class, and use it to access your Room database asynchronously.

Example: https://codelabs.developers.google.com/codelabs/android-room-with-a-view/#7

Guido Cardinali
  • 366
  • 1
  • 13
0

You don't need ViewModel to access the data from Room. You can access the data from anywhere, only catch is that the awesome feature of getting notified when data changes won't work. To do so simply add a method to your Dao object which return a simple List not the LiveData>. And that is it.

Uzair
  • 1,529
  • 14
  • 17
0

Added a little more info of how some things can fit together to JaviMar's answer in Kotlin:

class RoomWidgetService : RemoteViewsService() {
    override fun onGetViewFactory(intent: Intent): RemoteViewsFactory {
        return RoomRemoteViewsFactory(this.applicationContext, intent)
    }
}

class RoomRemoteViewsFactory(private val context: Context, intent: Intent) : RemoteViewsService.RemoteViewsFactory {
    private var todos: List<TodoItem> = listOf()
    private lateinit var todoDao: TodoDao


    override fun onCreate() {
        val database = TodoRoomDatabase.getDatabase(context)
        todoDao = database.todoDao()
    }

    override fun onDataSetChanged() {
        todos = todoDao.getTodosSync()
    }
    // rest of class...
}

I notify the widget of an update by observing something. You could observe the ViewModel in the main activity, or the Application. Not sure the best place to observe the data.

        todoViewModel.todoItems.observe(this) {
            val appWidgetManager = AppWidgetManager.getInstance(applicationContext)
            val thisAppWidget = ComponentName(
                applicationContext.packageName,
                RoomWidget::class.java.name
            )
            val appWidgetIds = appWidgetManager.getAppWidgetIds(thisAppWidget)
            appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.list_view )
        }
bahalbach
  • 136
  • 1
  • 5