Android ViewModel inside RecyclerView Adapter for lazy database downloads
Asked Answered
H

2

5

I have a question that is more related to proper design and architecture of MVVM than to coding itself. In my project I have a situation that on ViewModel is suplying data, that are later used in RecyclerView.Adapter to create a proper view.

However I wonder if this would be also correct (from proper 'way of doing things' POV) if some of the data would be supplied in form of id's to be further fetched from Room or external server? For instance during onBindViewHolder use some LiveData with observe() to update certain fields on succesfull load.

Homework answered 22/8, 2018 at 20:39 Comment(1)
Nice idea to go forDomella
N
6

Doing data fetch in the views is a no-go. It defeats the very purpose of MVVM and in particular the Android Jetpack efforts. One of the big reason is the data needs to survive configurations. Putting "data fetching" mechanism in the view defeats that as the view can be destroyed and recreated anytime when need be.

So I would suggest you make sure all calls to the network or any other source of data for that matter revolve around the ViewModel and never a view. Let the VM feed the data to the View through observer.

Exception to what I have just said is such use case as loading images with Picasso or Glide where by you feed them URL and they load image. But that's a different thing as they are designed to handle that.

UPDATE with followup Questions

it's ok to put observe() still inside Adapter, during the binding process?

No! Data sent to the adapter must be complete in the purpose it is supposed to serve. For example, if you have to do list app and your Top-Level Activity displays all Todos, then you must feed adapter will complete data (Title, Created time, et al).

The data that are not going to be displayed (like descriptions or sub-to-do-lists) and aren't necessary to identify specific to do should not be fetched (unless you want to store them for sole purpose of avoiding a second network call and pass them to the next activity as serialized data).

When user clicks specific To-Do, then launch new activity with its own ViewModel that will fetch details for that activity (assuming you passed some ID with intent).

If the first, then I understand that observe() should not only update data, but also populate it later to Observer and call notifyDataSetChanged(), right?

Observe is a way to post data to the view when either it have changed or the old view was destroyed and so the new view is being given the same old data that survived the "view death". So it is in the same method where updating data of the Adapter should be done and hence call to notifyDataSetChanged() or similar methods.

I hope that makes it clear.

Nedrud answered 22/8, 2018 at 21:0 Comment(6)
Thanks for answer. I think I did not understand entirelly so one more question - You say that, ModelView should fetch the data, right? But should do that, as suggested by crymson, before Adapter is created (in this case Adapter is populated already with complete data), or it's ok to put observe() still inside Adapter, during the binding process? If the first, then I understand that observe() should not only update data, but also populate it later to Observer and call notifyDataSetChanged(), right?Homework
The data sent to the adapter must be complete in itself. Its difficult to explain without knowing what you plan to accomplish. Let me expand the answerNedrud
Thank You for the answer and follow up!Homework
Glad that I helped!Nedrud
I'm not convinced regarding your exception for loading images. Just because another library is doing the work is irrelevant. There is some argument for lazily loading data when an item appears in view (i.e. triggered by adapter). An image is an example of this but not because some library is doing the dirty work. I can imagine certain other UI elements (ones that are expensive to load) in the item could also be lazily loaded in much the same way.Enriquetaenriquez
Since the OP mentions Room, I recommending using more LiveData and less notifyDataSetChanged(),.Abort
I
2

I think it's best to keep the ViewModel separate from the Adapter. From what I'm gathering you want to basically have each list item load it's own data from Room by having each list item observe on a particular ID for that item. This sounds rather inefficient as you're basically having each item execute a query on the database (or network call) to retrieve just one item for all items that are going to be displayed, think about how it will scale if you were displaying a 100 items. I think it's best that you just execute one query to get the list of data and set the data to the list items in the adapter, keep it simple. Note that onBindViewHolder() doesn't just get called once but multiple times when you're scrolling the screen, so it could get quite ugly if you're trying to lazily load every list item.

Illbred answered 22/8, 2018 at 21:1 Comment(4)
Thank You. The performance related topic, I think, is a key. Is there any good/proper way to 'show what is available' and update with network fetched information later?Homework
Well on a high level explanation, you can initially query the database for items and show that data first in your Adapter. At the same time, you do a network fetch and once that network fetch completes, insert that data into your database via Room. Now to update your Adapter there are several ways to do it but since your using ViewModels I assume you are also familiar with LiveData. You can observe the Room database using LiveData, and once you insert the newly fetched network data into the database, Room will notify your Adapter to update with the new data (automatically if set up correctly).Illbred
Thank You for the answer and follow up!Homework
@Illbred The API which I need to query in my case needs me to do the query one by one for each item. So I need to do the query when onBind is called. So, suggestions on implementing it.Highspirited

© 2022 - 2024 — McMap. All rights reserved.