when im using getData method from my local database DAO via ViewModel and try to receive data in activity im getting nulls, but when i use exactly same function(getData) implemented inside activity it works properly. My data doesn't survive 1st approach somehow. Simplified code below.
FormDao
class FormDao(val realm: Realm) {
fun findById(id: Int): LiveData<Form> {
val liveData = MutableLiveData<Form>()
val form = realm
.where(Form::class.java)
.equalTo("id", id)
.findFirst()!!
liveData.postValue(form)
return liveData
}
}
I made my FormDao extension of the realm object so anytime I have an instance of the realm object I can easily access the db queries in the FormDao class.
RealmDao.kt
fun Realm.formDao() : FormDao = FormDao(this)
ViewModel
open class NewFormViewModel(private val repository: FormRepository) : ViewModel() {
val realm: Realm by lazy {
Realm.getDefaultInstance()
}
fun getForm(): LiveData<Form> {
return realm.formDao().findById(53)
}
override fun onCleared() {
realm.close()
super.onCleared()
}
}
Its factory class
class NewFormViewModelFactory(private val repository: FormRepository) :
ViewModelProvider.NewInstanceFactory(){
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return NewFormViewModel(repository) as T
}
}
Activity
class NewFormActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_new_form)
val newFormViewModelFactory = NewFormViewModelFactory(repository = FormRepository())
val newFormViewModel = ViewModelProvider(this, newFormViewModelFactory).get(NewFormViewModel::class.java)
val button: Button = findViewById(R.id.addFormButton)
button.setOnClickListener {
var test1 = newFormViewModel.getForm()
Log.v("LOGG", test1.value?.id.toString())
}
}
}
There is exisiting data with id = 53 in local DB so im expecting to get 53 as answer here, but its null for some reason. Local db work ok itself. Same thing happens when i try to get data from remote db with retrfoit. Thanks a million!
Okay, i found out what was the problem with my DAO. Im not used to work with LiveData so i made little mess with binding my custom class to LiveData.
fun findById(id: Int): LiveData<Form> {
val liveData = MutableLiveData<Form>()
val form = realm
.where(Form::class.java)
.equalTo("id", id)
.findFirst()!!
liveData.value = form <--- was liveData.pastValue(form)
return liveData
}
Now DAO works fine. But as i said earlier i had problem with my Retrofit call aswell (that's what made me think it's about passing data between view and viewmodel etc), but i should probably make different question about that.
Related
I get page data from a database, I have a repository that returns a flow.
class RepositoryImpl (private val db: AppDatabase) : Repository {
override fun fetchData (page: Int) = flow {
emit(db.getData(page))
}
}
In the ViewModel, I call the stateIn(), the first page arrives, but then how to request the second page? By calling fetchData(page = 2) I get a new flow, and I need the data to arrive on the old flow.
class ViewModel(private val repository: Repository) : ViewModel() {
val dataFlow = repository.fetchData(page = 1).stateIn(viewModelScope, WhileSubscribed())
}
How to get the second page in dataFlow?
I don't see the reason to use a flow in the repository if you are emitting only one value. I would change it to a suspend function, and in the ViewModel I would update a variable of type MutableStateFlow with the new value. The sample code could look like the following:
class RepositoryImpl (private val db: AppDatabase) : Repository {
override suspend fun fetchData (page: Int): List<Data> {
return db.getData(page)
}
}
class ViewModel(private val repository: Repository) : ViewModel() {
val _dataFlow = MutableStateFlow<List<Data>>(emptyList())
val dataFlow = _dataFlow.asStateFlow()
fun fetchData (page: Int): List<Data> {
viewModelScope.launch {
_dataFlow.value = repository.fetchData(page)
}
}
}
The "proper" way to update views with Android seems to be LiveData. But I can't determine the "proper" way to connect that to a model. Most of the documentation I have seen shows connecting to Room which returns a LiveData object. But (assuming I am not using Room), returning a LiveData object (which is "lifecycle aware", so specific to the activity/view framework of Android) in my model seems to me to violate the separation of concerns?
Here is an example with Activity...
class MainActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_activity);
val viewModel = ViewModelProvider(this).get(UserViewModel::class.java)
val nameText = findViewById<TextView>(R.id.nameTextBox)
viewModel.getName().observe(this, { name ->
nameText.value = name
})
}
}
And ViewModel...
class UserViewModel(): ViewModel() {
private val name: MutableLiveData<String> = MutableLiveData()
fun getName() : LiveData<String> {
return name
}
}
But how do I then connect that to my Model without putting a "lifecycle aware" object that is designed for a specific framework in my model (LiveData)...
class UserModel {
val uid
var name
fun queryUserInfo() {
/* API query here ... */
val request = JSONObjectRequest( ...
{ response ->
if( response.name != this.name ) {
this.name = response.name
/* Trigger LiveData update here somehow??? */
}
}
)
}
}
I am thinking I can maybe put an Observable object in my model and then use that to trigger the update of the LiveData in my ViewModel. But don't find any places where anyone else says that is the "right" way of doing it. Or, can I instantiate the LiveData object in the ViewModel from an Observable object in my model?
Or am I just thinking about this wrong or am I missing something?
This is from official documentation. Check comments in code...
UserModel should remain clean
class UserModel {
private val name: String,
private val lastName: String
}
Create repository to catch data from network
class UserRepository {
private val webservice: Webservice = TODO()
fun getUser(userId: String): LiveData<UserModel > {
val data = MutableLiveData<UserModel>() //Livedata that you observe
//you can get the data from api as you want, but it is important that you
//update the LiveDate that you will observe from the ViewModel
//and the same principle is in the relation ViewModel <=> Fragment
webservice.getUser(userId).enqueue(object : Callback<UserModel > {
override fun onResponse(call: Call<User>, response: Response<UserModel >) {
data.value = response.body()
}
// Error case is left out for brevity.
override fun onFailure(call: Call<UserModel >, t: Throwable) {
TODO()
}
})
return data //you will observe this from ViewModel
}
}
The following picture should explain to you what everything looks like
For more details check this:
https://developer.android.com/jetpack/guide
viewmodels-and-livedata-patterns-antipatterns
I'm working with MVVM, and I have made different implementations of it, but one thing that is still making me doubt is how do I get data from a Repository (Firebase) from my ViewModel without attaching any lifecycle to the ViewModel.
I have implemented observeForever() from the ViewModel, but I don't think that is a good idea because I think I should communicate from my repository to my ViewModel either with a callback or a Transformation.
I leave here an example where I fetch a device from Firebase and update my UI, if we can see here, I'm observing the data coming from the repo from the UI, but from the ViewModel I'm also observing data from the repo, and here is where I really doubt if I'm using the right approach, since I don't know if observeForever() will be cleared on onCleared() if my view is destroyed, so it won't keep the observer alive if the view dies.
UI
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button.setOnClickListener {
val deviceId = editText.text.toString().trim()
observeData(deviceId)
}
}
fun observeData(deviceId:String){
viewModel.fetchDeviceData(deviceId).observe(this, Observer {
textView.text = "Tipo: ${it.devType}"
})
ViewModel
class MainViewmodel: ViewModel() {
private val repo = Repo()
fun fetchDeviceData(deviceId:String):LiveData<Device>{
val mutableData = MutableLiveData<Device>()
repo.getDeviceData(deviceId).observeForever {
mutableData.value = it
}
return mutableData
}
}
Repository
class Repo {
private val db = FirebaseDatabase.getInstance().reference
fun getDeviceData(deviceId:String):LiveData<Device>{
val mutableData = MutableLiveData<Device>()
db.child(deviceId).child("config/device").addListenerForSingleValueEvent(object: ValueEventListener{
override fun onDataChange(dataSnapshot: DataSnapshot) {
val device = dataSnapshot.getValue(Device::class.java)
mutableData.value = device
}
override fun onCancelled(dataError: DatabaseError) {
Log.e("Error","handle error callback")
}
})
return mutableData
}
}
This example just shows how to fetch the device from Firebase, it works, but from my ViewModel, it keeps making me think that observeForever() is not what I'm looking for to communicate data between the repository to the ViewModel.
I have seen Transformations, but I, in this case, I just need to deliver the entire Device object to my UI, so I don't need to transform the Object I'm retrieving to another Object
What should be here the right approach to communicate the repository and the ViewModel properly?
is observeForever lifecycle aware?
No, that's why it's called observeForever.
I have implemented observeForever() from the ViewModel, but I don't think that is a good idea
No, it's not, you should be using Transformations.switchMap {.
since I don't know if observeForever() will be cleared on onCleared() if my view is destroyed, so it won't keep the observer alive if the view dies.
Well if you're not clearing it in onCleared() using removeObserver(observer), then it won't clear itself, because it observes forever.
here is where I really doubt if I'm using the right approach,
No, you can do much better than this following a reactive approach.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button.setOnClickListener {
val deviceId = editText.text.toString().trim()
viewModel.onSelectedDeviceChanged(deviceId)
}
viewModel.selectedDevice.observe(this, Observer { device ->
textView.text = "Tipo: ${device.devType}"
})
}
And
class MainViewModel(
private val savedStateHandle: SavedStateHandle,
): ViewModel() {
private val repo = Repo() // TODO: move to Constructor Argument with ViewModelProvider.Factory
private val selectedDeviceId: MutableLiveData<String> = savedStateHandle.getLiveData<String>("selectedDeviceId")
fun onSelectedDeviceChanged(deviceId: String) {
selectedDeviceId.value = deviceId
}
val selectedDevice = Transformations.switchMap(selectedDeviceId) { deviceId ->
repo.getDeviceData(deviceId)
}
}
And
class Repo {
private val db = FirebaseDatabase.getInstance().reference // TODO: move to constructor arg? Probably
fun getDeviceData(deviceId:String) : LiveData<Device> {
return object: MutableLiveData<Device>() {
private val mutableLiveData = this
private var query: Query? = null
private val listener: ValueEventListener = object: ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
val device = dataSnapshot.getValue(Device::class.java)
mutableLiveData.value = device
}
override fun onCancelled(dataError: DatabaseError) {
Log.e("Error","handle error callback")
}
}
override fun onActive() {
query?.removeEventListener(listener)
val query = db.child(deviceId).child("config/device")
this.query = query
query.addValueEventListener(listener)
}
override fun onInactive() {
query?.removeEventListener(listener)
query = null
}
}
}
}
This way, you can observe for changes made in Firebase (and therefore be notified of future changes made to your values) using LiveData, rather than only execute a single fetch and then not be aware of changes made elsewhere to the same data.
To use ObserveForever, you need to remove the observer inside onClear in the ViewModel.
In this case, I would suggest to use Transformation even though you just need a direct mapping without any processing of the data, which is actually the same as what you are doing with the observer for observerForever.
observeForever() is not Lifecycle aware and will continue to run until removeObserver() is called.
In your ViewModel do this instead,
class MainViewmodel: ViewModel() {
private val repo = Repo()
private var deviceData : LiveData<Device>? = null
fun fetchDeviceData(deviceId:String):LiveData<Device>{
deviceData = repo.getDeviceData(deviceId)
return deviceData!!
}
}
I'm rewriting an app that involves retrieving data from a server via REST, saving that to the database on each Android device, and then displaying that data to the user. The data being retrieved from the server has a "since" parameter, so it won't return all data, just data that has changed since the last retrieval.
I have the retrieval from the server working fine, but I'm not sure the best way to save that data to the database, then show it to the user. I'm using Kotlin, Retrofit, Room and LiveData.
The code below is a simplified version of what I'm actually doing, but it gets the point across.
MyData.kt (model)
#Entity(tableName = "MyTable")
data class MyData(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "id")
var id Int? = null,
#SerializedName("message")
#ColumnInfo(name = "message")
var message: String? = null
) {
companion object {
fun fromContentValues(values: ContentValues): MyData {
val data = MyData()
// Do this for id and message
if (values.containsKey("id") {
data.id = values.getAsInteger("id")
}
}
}
}
DataViewModel.kt
class DataViewModel(application: Application) : AndroidViewModel(application) {
private val repository = DataRepository()
fun data(since: Long) =
liveData(Dispatchers.IO) {
val data = repository.getDataFromServer(since)
emit(data)
}
fun saveData(data: List<MyData>) =
liveData(Dispatchers.Default) {
val result = repository.saveDataToDatabase(data)
emit(result)
}
fun data() =
liveData(Dispatchers.IO) {
val data = repository.getDataFromDatabase()
emit(data)
}
}
DataRepository.kt
class DataRepository(application: Application) {
// I won't add how the Retrofit client is created, it's standard
private var client = "MyUrlToGetDataFrom"
private var myDao: MyDao
init {
val myDatabase = MyDatabase.getDatabase(application)
myDao = myDatabase!!.myDao()
}
suspend fun getDataFromServer(since: Long): List<MyData> {
try {
return client.getData(since)
} catch (e: Exception) {
}
}
fun getDataFromDatabase(): List<MyData> = myDao.getAll()
suspend fun insertData(data: List<MyData>) =
myDao.insertData(data)
}
MyDao.kt
#Dao
interface PostsDao {
#Query("SELECT * FROM " + Post.TABLE_NAME + " ORDER BY " + Post.COLUMN_ID + " desc")
suspend fun getAllData(): List<MyData>
#Insert
suspend fun insertData(data: List<MyData>)
}
ListActivity.kt
private lateinit var mDataViewModel: DataViewModel
override fun onCreate(savedInstanceBundle: Bundle?) {
super.onCreate(savedInstanceBundle)
mDataViewModel = ViewModelProvider(this, DataViewModelFactory(contentResolver)).get(DataViewModel::class.java)
getData()
}
private fun getData() {
mDataViewModel.data(getSince()).observe(this, Observer {
saveData(it)
})
}
private fun saveData(data: List<MyData>) {
mDataViewModel.saveData(data)
mDataViewModel.data().observe(this, Observer {
setupRecyclerView(it)
})
}
ListActivity.kt, and possibly the ViewModel and Repository classes where it uses coroutines, are where I'm stuck. getData() retrieves the data from the server without a problem, but when it comes to saving it in the database, then taking that saved data from the database and displaying it to the user I'm unsure of the approach. As I mentioned I'm using Room, but Room will not let you access the database on the main thread.
Remember, I have to save in the database first, then retrieve from the database, so I don't want to call mDataViewModel.data().observe until after it saves to the database.
What is the proper approach to this? I've tried doing CoroutineScope on the mDataViewModel.saveData() then .invokeOnCompletion to do mDataViewModel.data().observe, but it doesn't save to the database. I'm guessing I'm doing my Coroutines incorrectly, but not sure where exactly.
It will also eventually need to delete and update records from the database.
Updated Answer
After reading comments and updated question I figured out that you want to fetch a small list of data and store it to database and show all the data stored in the database. If this is what you want, you can perform the following (omitted DataSouce for brevity) -
In PostDao You can return a LiveData<List<MyData>> instead of List<MyData> and observe that LiveData in the Activity to update the RecyclerView. Just make sure you remove the suspend keyword as room will take care of threading when it returns LiveData.
#Dao
interface PostsDao {
#Query("SELECT * FROM " + Post.TABLE_NAME + " ORDER BY " + Post.COLUMN_ID + " desc")
fun getAllData(): LiveData<List<MyData>>
#Insert
suspend fun insertData(data: List<MyData>)
}
In Repository make 2 functions one for fetching remote data and storing it to the database and the other just returns the LiveData returned by the room. You don't need to make a request to room when you insert the remote data, room will automatically update you as you are observing a LiveData from room.
class DataRepository(private val dao: PostsDao, private val dto: PostDto) {
fun getDataFromDatabase() = dao.getAllData()
suspend fun getDataFromServer(since: Long) = withContext(Dispatchers.IO) {
val data = dto.getRemoteData(since)
saveDataToDatabase(data)
}
private suspend fun saveDataToDatabase(data: List<MyData>) = dao.insertData(data)
}
Your ViewModel should look like,
class DataViewModel(private val repository : DataRepository) : ViewModel() {
val dataList = repository.getDataFromDatabase()
fun data(since: Long) = viewModelScope.launch {
repository.getDataFromServer(since)
}
}
In the Activity make sure you use ListAdapter
private lateinit var mDataViewModel: DataViewModel
private lateinit var mAdapter: ListAdapter
override fun onCreate(savedInstanceBundle: Bundle?) {
...
mDataViewModel.data(getSince())
mDataViewModel.dataList.observe(this, Observer(adapter::submitList))
}
Initial Answer
First of all, I would recommend you to look into Android Architecture Blueprints v2. According to Android Architecture Blueprints v2 following improvements can be made,
DataRepository should be injected rather than instantiating internally according to the Dependency Inversion principle.
You should decouple the functions in the ViewModel. Instead of returning the LiveData, the data() function can update an encapsulated LiveData. For example,
class DataViewModel(private val repository = DataRepository) : ViewModel() {
private val _dataList = MutableLiveData<List<MyData>>()
val dataList : LiveData<List<MyData>> = _dataList
fun data(since: Long) = viewModelScope.launch {
val list = repository.getData(since)
_dataList.value = list
}
...
}
Repository should be responsible for fetching data from remote data source and save it to local data source. You should have two data source i.e. RemoteDataSource and LocalDataSource that should be injected in the Repository. You can also have an abstract DataSource. Let's see how can you improve your repository,
interface DataSource {
suspend fun getData(since: Long) : List<MyData>
suspend fun saveData(list List<MyData>)
suspend fun delete()
}
class RemoteDataSource(dto: PostsDto) : DataSource { ... }
class LocalDataSource(dao: PostsDao) : DataSource { ... }
class DataRepository(private val remoteSource: DataSource, private val localSource: DataSource) {
suspend fun getData(since: Long) : List<MyData> = withContext(Dispatchers.IO) {
val data = remoteSource.getData(since)
localSource.delete()
localSource.save(data)
return#withContext localSource.getData(since)
}
...
}
In your Activity, you just need to observe the dataList: LiveData and submit it's value to ListAdapter.
private lateinit var mDataViewModel: DataViewModel
private lateinit var mAdapter: ListAdapter
override fun onCreate(savedInstanceBundle: Bundle?) {
...
mDataViewModel.data(since)
mDataViewModel.dataList.observe(this, Observer(adapter::submitList))
}
I'm fairly new to Kotlin/Android development, and am trying to figure out the best way to update data in a Room database. After following some tutorials, I currently have an architecture that looks like this:
Room Database with tables and DAOs -> Repository -> ViewModel -> Activity
So the activity has a ViewModel that calls the Repository, which in turn updates the database.
The ViewModel for the activity has a LiveData list of the object (there's also a factory to create the ViewModel, but that's just to allow the bookId to be passed in):
class ViewBookViewModel(application: Application, bookId: Int) : AndroidViewModel(application) {
private val repository: AppRepository
internal val flashCards: LiveData<List<FlashCard>>
init {
val flashCardDao = AppDatabase.getDatabase(application, viewModelScope).flashCardDao()
val bookDao = AppDatabase.getDatabase(application, viewModelScope).bookDao()
repository = AppRepository(flashCardDao, bookDao)
flashCards = flashCardDao.getByBookId(bookId)
}
fun insert(flashCard: FlashCard) = viewModelScope.launch(Dispatchers.IO){
repository.insert(flashCard)
}
fun setIsFavorited(cardUid: Long, favorited: Boolean) = viewModelScope.launch(Dispatchers.IO) {
repository.setIsFavorited(cardUid, favorited)
}
}
//The actual query that gets called eventually
#Query("UPDATE flashcard SET is_favorited = :favorited WHERE uid LIKE :cardUid")
fun setFavorited(cardUid: Long, favorited: Boolean)
And the Activity sets up the viewModel and also creates an observer on the
class ViewBookActivity : AppCompatActivity() {
private lateinit var flashCards: LiveData<List<FlashCard>>
private var layoutManager: RecyclerView.LayoutManager? = null
private lateinit var viewModel: ViewBookViewModel
private var bookId: Int = 0
private lateinit var bookTitle: String
override fun onCreate(savedInstanceState: Bundle?) {
...
bookId = intent.extras["bookId"] as Int
bookTitle = intent.extras["bookTitle"].toString()
layoutManager = LinearLayoutManager(this)
flashCardRecyclerView.layoutManager = layoutManager
viewModel = ViewModelProviders.of(this, ViewBookViewModelFactory(application, bookId as Int)).get(ViewBookViewModel::class.java)
flashCards = viewModel.flashCards
flashCards.observe(this, Observer { flashCards:List<FlashCard> ->
flashCardRecyclerView.adapter = FlashCardRecyclerAdapter(flashCards, viewModel)
})
}
}
Finally, I have a custom RecyclerAdapter, which is where I'm running into trouble. I have it set up so that when the user taps the "favorite" button on the Flash Card, it updates the database. However, this also causes the Activity to "refresh", scrolling to the top. I assume this is because it is observing LiveData, and that data is being changed.
custom RecylcerAdapter with ViewHolder code (stripped not-relevant code):
class FlashCardRecyclerAdapter(val flashCards: List<FlashCard>, val viewModel: ViewBookViewModel) : RecyclerView.Adapter<FlashCardRecyclerAdapter.FlashCardViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FlashCardViewHolder {
val v: View = LayoutInflater
.from(parent.context)
.inflate(R.layout.flash_card, parent, false)
return FlashCardViewHolder(v)
}
override fun onBindViewHolder(holder: FlashCardViewHolder, position: Int) {
val card = flashCards[position]
holder.isFavorited = card.isFavorited
holder.uid = card.uid
holder.modifyFavoriteButtonImage(holder.isFavorited)
}
override fun getItemCount(): Int {
return flashCards.size
}
inner class FlashCardViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
var mFavorited: Button
var frontShowing: Boolean
var isFavorited: Boolean = false
var uid: Long = 0
init {
mFavorited = itemView.findViewById(R.id.favoriteButton)
mFavorited.setOnClickListener { _ ->
isFavorited = !isFavorited
viewModel.setIsFavorited(uid, isFavorited) // Here is the database call
modifyFavoriteButtonImage(isFavorited)
}
}
fun modifyFavoriteButtonImage(isFavorited: Boolean){
// Code removed, just updates the image to be a filled/empty star based on favorited status
}
}
I feel like I am probably doing something wrong, as passing the ViewModel into the recylcer adapter in order to update the DB does not seem correct. Is there a pattern I should be using for this sort of situation, or should I change the code to not be using LiveData? Any help would be greatly appreciated.
flashCards.observe(this, Observer { flashCards:List<FlashCard> ->
flashCardRecyclerView.adapter = FlashCardRecyclerAdapter(flashCards, viewModel)
}
you should not be making a new adapter instance here, instead, assign the values you get from the live data to the existing adapter (adapter.flashCards = flashCards, LiveData value) and call adapter.notifyDataSetChanged, this will tell your adapter that new data came in and it needs to update.
you should not be passing your ViewModel to your adapter (or anything).
you can do something like this instead:
class FlashCardRecyclerAdapter(val flashCards: List<FlashCard>, val callback:(FlashCard) -> Unit)
then, where you declare your adapter, you do this :
val adapter = FlashCardRecyclerAdapter(...) {
viewModel.update(it)
}
and then :
override fun onBindViewHolder(holder: FlashCardViewHolder, position: Int) {
val card = flashCards[position]
holder.isFavorited = card.isFavorited
holder.uid = card.uid
holder.itemView.setOnClickListener {
callback.invoke(card)
}
holder.modifyFavoriteButtonImage(holder.isFavorited)
}
In your repository method, I am not sure what you are doing there but rather than passing in a livedata instance, you should pass in the underlying data of the livedata instance. That way, the observer in the main activity doesn't get triggered everytime you call setIsFavorited(). If you do want to trigger the observer, then you can just call postValue() on the livedata instance. As for the adapter question, I do not know the best practices but I usually create a listener interface so I don't have to pass around my viewmodels everywhere. All of my viewmodels are contained within my fragments and never goes anywhere else. Let me know if this answers your questions.
Also, if you are using viewmodels with recyclerview, consider using list adapters. They are made to work seamlessly with viewmodels. https://developer.android.com/reference/android/support/v7/recyclerview/extensions/ListAdapter
It makes it much simpler to use viewmodels with recyclerview.