I have a RecyclerView which displays LiveData<List<Item>> returned from a Room Database. Everything works fine, however, the Item order needs to be randomized every time the app is open for a more dynamic feel.
The Item's are displayed in AllItemFragment. When an item is clicked, it will be added to the users favourites. This will then add the Item to the FavouriteFragment.
Ordering the SQL query by RANDOM() would be called every time the data is changed (i.e. when an item is clicked) and therefore wont work.
List.shuffle cannot be called on LiveData object for obvious reasons.
Data is retrieved in the following format:
DAO -> Repository -> SharedViewholder -> Fragment -> Adapter
DAO
#Query("SELECT * from items_table")
fun getAllItems(): LiveData<MutableList<Item>>
Repository
val mItemList: LiveData<MutableList<Item>> = itemDoa.getAllItems()
SharedViewHolder
init {
repository = ItemRepository(itemDao)
itemList = repository.mItemList
}
fun getItems(): LiveData<MutableList<Item>> {
return itemList
}
Fragment
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
mSharedViewModel = activity?.run {
ViewModelProviders.of(this).get(SharedViewModel::class.java)
} ?: throw Exception("Invalid Activity")
mSharedViewModel.getItems().observe(viewLifecycleOwner, Observer { item ->
// Update the UI
item.let { mAdapter.setItems(it!!) }
})
}
Adapter
internal fun setItems(items: MutableList<Item>) {
val diffCallback = ItemDiffCallback(this.mItems, items)
val diffResult = DiffUtil.calculateDiff(diffCallback)
this.mItems.clear()
this.mItems.addAll(items)
diffResult.dispatchUpdatesTo(this)
}
EDIT
Using switchMap() still shuffles the entire list when a user presses the favourite button
fun getItems(): LiveData<MutableList<Item>> {
return Transformations.switchMap(mItemList) { list ->
val newLiveData = MutableLiveData<MutableList<Item>>()
val newList = list.toMutableList()
Collections.shuffle(newList)
newLiveData.setValue(newList)
return#switchMap newLiveData }
}
Just use .shuffled() with seeded Random instance. The idea is to randomize the list, but the randomize in the same way, until the process dies and the user relaunches the app to generate a new seed.
Repository
private val seed = System.currentTimeMillis()
val mItemList: LiveData<MutableList<Item>> = Transformations.map(itemDoa.getAllItems()) {
it.shuffled(Random(seed))
}
The seed must be consistent throughout the application's process. I think keeping the seed in the repository is pretty safe, assuming that your repository is implemented in a singleton pattern. If it is not the case, just find yourself a singleton object and cache the seed.
You should consider using switchMap transformation operator on LiveData.
return liveData.switchMap(list -> {
var newLiveData = LiveData<MutableList<Item>>()
var newList = list.toMutableList()
Collections.shuffle(newList)
newLiveData.setValue(newList)
return newLiveData
})
For creating new LiveData you can use LiveData constructor and setValue(T value) method.
As value you can set Collections.shuffle(list)
You could use it in your repository or in the view model.
Related
I'm currently trying to use a SQLite database via the Room library on my Jetpack Compose project to create a view that does the following:
display a list of entries from the database that are filtered to only records with the current user's ID
allow the user to create new records and insert those into the database
update the list to include any newly created records
My issue is that I cannot get the list to show when the view is loaded even though the database has data in it and I am able to insert records into it successfully. I've seen a lot of examples that show how do this if you are just loading all the records, but I cannot seem to figure out how to do this if I only want the list to include records with the user's ID.
After following a few of tutorials and posts it is my understanding that I should have the following:
A DAO, which returns a LiveData object
A repository which calls the DAO method and returns the same LiveData object
A viewholder class, which will contain two objects: one private MutableLiveData variable and one public LiveData variable (this one is the one we observe from the view)
My view, a Composable function, that observes the changes
However, with this setup, the list still will not load and I do not see any calls to the database to load the list from the "App Inspection" tab. The code is as follows:
TrainingSet.kt
#Entity(tableName = "training_sets")
data class TrainingSet (
#PrimaryKey() val id: String,
#ColumnInfo(name = "user_id") val userId: String,
TrainingSetDao.kt
#Dao
interface TrainingSetDao {
#Insert(onConflict = OnConflictStrategy.ABORT)
suspend fun insert(trainingSet: TrainingSet)
#Query("SELECT * FROM training_sets WHERE user_id = :userId")
fun getUserTrainingSets(userId: String): LiveData<List<TrainingSet>>
}
TrainingSetRepository.kt
class TrainingSetRepository(private val trainingSetDao: TrainingSetDao) {
fun getUserTrainingSets(userId: String): LiveData<List<TrainingSet>> {
return trainingSetDao.getUserTrainingSets(userId)
}
suspend fun insert(trainingSet: TrainingSet) {
trainingSetDao.insert(trainingSet)
}
}
TrainingSetsViewModel.kt
class TrainingSetsViewModel(application: Application): ViewModel() {
private val repository: TrainingSetRepository
private val _userTrainingSets = MutableLiveData<List<TrainingSet>>(emptyList())
val userTrainingSets: LiveData<List<TrainingSet>> get() = _userTrainingSets
init {
val trainingSetDao = AppDatabase.getDatabase(application.applicationContext).getTrainingSetDao()
repository = TrainingSetRepository(trainingSetDao)
}
fun getUserTrainingSets(userId: String) {
viewModelScope.launch {
_userTrainingSets.value = repository.getUserTrainingSets(userId).value
}
}
fun insertTrainingSet(trainingSet: TrainingSet) {
viewModelScope.launch(Dispatchers.IO) {
try {
repository.insert(trainingSet)
} catch (err: Exception) {
println("Error!!!!: ${err.message}")
}
}
}
}
RecordScreen.kt
#Composable
fun RecordScreen(navController: NavController, trainingSetsViewModel: TrainingSetsViewModel) {
// observe the list
val trainingSets by trainingSetsViewModel.userTrainingSets.observeAsState()
// trigger loading of the list using the userID
// note: hardcoding this ID for now
trainingSetsViewModel.getUserTrainingSets("20c1256d-0bdb-4241-8781-10f7353e5a3b")
// ... some code here
Button(onClick = {
trainingSetsViewModel.insertTrainingSet(TrainingSet(// dummy test data here //))
}) {
Text(text = "Add Record")
}
// ... some more code here
LazyColumn() {
itemsIndexed(trainingSets) { key, item ->
// ... list row components here
}
}
NavGraph.kt** **(including this in case it's relevant)
#Composable
fun NavGraph(navController: NavHostController) {
NavHost(
navController = navController,
startDestination = Screens.Record.route,
) {
composable(route = Screens.Record.route) {
val owner = LocalViewModelStoreOwner.current
owner?.let {
val trainingSetsViewModel: TrainingSetsViewModel = viewModel(
it,
"TrainingSetsViewModel",
MainViewModelFactory(LocalContext.current.applicationContext as Application)
)
// note: I attempted to load the user training sets here in case it needed to be done before entering the RecordScreen, but that did not affect it (commenting this line out for now)
// trainingSetsViewModel.getUserTrainingSets("20c1256d-0bdb-4241-8781-10f7353e5a3b")
RecordScreen(
navController = navController,
trainingSetsViewModel= TrainingSetsViewModel,
)
}
}
}
}
What somewhat worked...
I was able to get the list to eventually load by making the following two changes (see comments in code), but it still did not load in the expected sequence and this change did not seem to align from all the examples I've seen. I will note that with this change, once the list showed up, the newly created records would be properly displayed as well.
*TrainingSetsViewModel.kt *(modified)
private val _userTrainingSets = MutableLiveData<List<TrainingSet>>(emptyList())
/ ***************
// change #1 (change this variable from a val to a var)
/ ***************
var userTrainingSets: LiveData<List<TrainingSet>> = _userTrainingSets
... // same code as above example
fun getUserTrainingSets(userId: String) {
viewModelScope.launch {
// ***************
// change #2 (did this instead of: _userTrainingSets.value = repository.getUserTrainingSets(userId).value)
// ***************
userTrainingSets = repository.getUserTrainingSets(userId)
}
}
... // same code as above example
I'm simply trying to select from a list of Artists in my room database in an AlertDialog. Calling getValue() on the LiveData object from the viewModel consistently gives me null. Do I really need to make a ListAdapter for something this simple?! Why is it so hard to get some strings from the database?
ArtistDao
#Dao
interface ArtistDao {
#Query("SELECT * FROM artist_table")
fun getAllArtists(): Flow<List<Artist>>
#Query("SELECT name FROM artist_table")
fun getArtistList(): Flow<List<String>>
#Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(artist: Artist)
}
Menu Selection Option in Main Activity
R.id.action_choose_artist -> {
//create artist list
val testList = songViewModel.artists.value
val artistList = testList?.toTypedArray()
// User chose choose artist action
val alertDialog: AlertDialog? = this.let {
val builder = AlertDialog.Builder(it)
builder.apply {
setTitle(R.string.choose_artist)
setItems(artistList, DialogInterface.OnClickListener { dialog, which ->
// The 'which' argument contains the index position
// of the selected item
artistName = artistList!![which]
})
}
// Create the AlertDialog
builder.create()
}
alertDialog?.show()
true
}
SongViewModel
val artists: LiveData<List<String>> = repository.artists.asLiveData()
SongRepository
val artists: Flow<List<String>> = artistDao.getArtistList()
You always get null because nobody is observing your Flow->LiveData-chain. LiveData itself will only trigger and perform its work if somebody is observing it.
In your case I think you want a one-time request to receive the data from your DB and should use a suspend function and coroutines to achieve it. Flow in context of Room DB is only useful if you are interested in changes to the DB for this Query and want to react to it, e.g. by displaying the updated data in your RecyclerView.
Dao:
#Dao
interface ArtistDao {
#Query("SELECT name FROM artist_table")
fun getArtistList(): List<String>
}
Repo:
suspend fun getArtistList(): List<String> = artistDao.getArtistList()
ViewModel:
fun getArtistList(onResult: (List<String>) -> Unit) {
viewModelScope.launch{
val artist = withContext(Dispatchers.IO){repository.getArtistList()}
// maybe validate data, e.g. not empty
onResult(artist)
}
}
Activity:
R.id.action_choose_artist -> {
//create artist list
songViewModel.getArtistList { testList ->
val artistList = testList.toTypedArray()
// User chose choose artist action
...
}
}
Right now, my method of updating my jetpack compose UI on database update is like this:
My Room database holds Player instances (or whatever they're called). This is my PlayerDao:
#Dao
interface PlayerDao {
#Query("SELECT * FROM player")
fun getAll(): Flow<List<Player>>
#Insert
fun insert(player: Player)
#Insert
fun insertAll(vararg players: Player)
#Delete
fun delete(player: Player)
#Query("DELETE FROM player WHERE uid = :uid")
fun delete(uid: Int)
#Query("UPDATE player SET name=:newName where uid=:uid")
fun editName(uid: Int, newName: String)
}
And this is my Player Entity:
#Entity
data class Player(
#PrimaryKey(autoGenerate = true) val uid: Int = 0,
#ColumnInfo(name = "name") val name: String,
)
Lastly, this is my ViewModel:
class MainViewModel(application: Application) : AndroidViewModel(application) {
private val db = AppDatabase.getDatabase(application)
val playerNames = mutableStateListOf<MutableState<String>>()
val playerIds = mutableStateListOf<MutableState<Int>>()
init {
CoroutineScope(Dispatchers.IO).launch {
db.playerDao().getAll().collect {
playerNames.clear()
playerIds.clear()
it.forEach { player ->
playerNames.add(mutableStateOf(player.name))
playerIds.add(mutableStateOf(player.uid))
}
}
}
}
fun addPlayer(name: String) {
CoroutineScope(Dispatchers.IO).launch {
db.playerDao().insert(Player(name = name))
}
}
fun editPlayer(uid: Int, newName: String) {
CoroutineScope(Dispatchers.IO).launch {
db.playerDao().editName(uid, newName)
}
}
}
As you can see, in my ViewHolder init block, I 'attach' a 'collector' (sorry for my lack of proper terminology) and basically whenever the database emits a new List<Player> from the Flow, I re-populate this playerNames list with new MutableStates of Strings and the playerIds list with MutableStates of Ints. I do this because then Jetpack Compose gets notified immediately when something changes. Is this really the only good way to go? What I'm trying to achieve is that whenever a change in the player table occurs, the list of players in the UI of the app gets updated immediately. And also, I would like to access the data about the players without always making new requests to the database. I would like to have a list of Players at my disposal at all times that I know is updated as soon as the database gets updated. How is this achieved in Android app production?
you can instead use live data. for eg -
val playerNames:Livedata<ListOf<Player>> = db.playerDao.getAll().asliveData
then you can set an observer like -
viewModel.playerNames.observe(this.viewLifecycleOwner){
//do stuff when value changes. the 'it' will be the changed list.
}
and if you have to have seperate lists, you could add a dao method for that and have two observers too. That might be way more efficient than having a single function and then seperating them into two different lists.
First of all, place a LiveData inside your data layer (usually ViewModel) like this
val playerNamesLiveData: LiveData<List<Player>>
get() = playerNamesMutableLiveData
private val playerNamesMutableLiveData = MutableLiveData<List<Player>>
So, now you can put your list of players to an observable place by using playerNamesLiveData.postValue(...).
The next step is to create an observer in your UI layer(fragment). The observer determines whether the information is posted to LiveData object and reacts the way you describe it.
private fun observeData() {
viewModel.playerNamesLiveData.observe(
viewLifecycleOwner,
{ // action you want your UI to perform }
)
}
And the last step is to call the observeData function before the actual data posting happens. I prefer doing this inside onViewCreated() callback.
I have the following fuction -
private fun fetchGroupData(callback: (groupModelList: List<GroupModel>) -> Unit) {
val groupModelList = mutableListOf<GroupModel>()
groupViewmodel.getAllGroupEntities().observeOnce(requireActivity(), Observer { groupEntityList ->
groupEntityList.forEach { groupEntity ->
/*
We iterate though all of the available groups,
for each group we get all of it's groupMembers models
*/
val groupName = groupEntity.groupName
val groupId = groupEntity.id
taskViewmodel.getGroupTaskCounter(groupId).observeOnce(requireActivity(), Observer { groupTaskCount ->
/*
For each group we observe it's task counter
*/
groupViewmodel.getGroupMembersForGroupId(groupId).observeOnce(requireActivity(), Observer { groupMembers ->
/*
For each group, we iterate through all of the groupMembers and for each of them we use it's userId
to fetch the user model, getting it's full name and adding it to a list of group users full name.
*/
val groupUsersFullNames = mutableListOf<String>()
groupMembers.forEach { groupMember ->
val memberId = groupMember.userId
groupViewmodel.getGroupParticipantForUserId(memberId).observeOnce(requireActivity(), Observer { groupUser ->
groupUsersFullNames.add(groupUser.fullName)
/*
When the groupUsersFullNames size matches the groupMembers size, we can add a model to our list.
*/
if (groupUsersFullNames.size == groupMembers.size)
groupModelList.add(GroupModel(groupId, groupName, groupTaskCount, groupUsersFullNames))
/*
When the new list matches the size of the group list in the DB we call the callback.
*/
if (groupModelList.size == groupEntityList.size)
callback(groupModelList)
})
}
})
})
}
})
}
That is being used by the following function -
private fun initAdapter() {
fetchGroupData { groupModelList ->
if (groupModelList.isEmpty()) {
binding.groupsListNoGroupsMessageTitle.setAsVisible()
binding.groupsListNoGroupsMessageDescription.setAsVisible()
return#fetchGroupData
}
binding.groupsListNoGroupsMessageTitle.setAsGone()
binding.groupsListNoGroupsMessageDescription.setAsGone()
val newList = mutableListOf<GroupModel>()
newList.addAll(groupModelList)
adapter.submitList(groupModelList)
Log.d("submitList", "submitList")
binding.groupsListRecyclerview.setAdapterWithItemDecoration(requireContext(), adapter)
}
}
These 2 functions represent group list fetch from my local DB into a RecyclerView.
In order to be notified when a new group has been created, I am holding a shared ViewModel object with a boolean indicating if a new group has been created.
In the same Fragment that these 2 functions ^ are written, I am observing this Boolean, and if the value is true I trigger a re-fetch for the entire list -
private fun observeSharedInformation() {
sharedInformationViewModel.value.groupCreatedFlag.observe(requireActivity(), Observer { hasGroupBeenCreated ->
if (!hasGroupBeenCreated) return#Observer
sharedInformationViewModel.value.groupCreatedFlag.value = false
Log.d("submitList", "groupCreatedFlag")
initAdapter()
})
}
At some point in my code in a different Fragment that also has an instance of my shared ViewModel, I trigger a value change for my Boolean LiveData -
sharedInformationViewModel.value.groupCreatedFlag.value = true
Which in turn triggers the observer, and does a re-fetch for my group list.
The issue I am facing is that when re-fetching for a new list (because a new group has been added) I do get the current information and everything should work 100% fine, but the new data - the newly created group - does not appear.
The newly added data appears in the list under 2 circumstances -
I restart the app
The function is triggered again - what happens now is that I see the list with the previous newly added group, but the newest group to be added does not appear.
There is one exception to this issue - if the group list is empty, the first group to be added does indeed appear when I submit the list with one group.
What is it that I am missing?
Edit -
Here is my adapter.
I am using a custom call called DefaultAdapterDiffUtilCallback, which expects a model that implements an interface that defines the unique ID for each model so I can compare new and old models.
class GroupsListAdapter(
private val context: Context,
private val onClick: (model : GroupModel) -> Unit
) : ListAdapter<GroupModel, GroupsListViewHolder>(DefaultAdapterDiffUtilCallback<GroupModel>()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GroupsListViewHolder {
val binding = GroupsListViewHolderBinding.inflate(LayoutInflater.from(context), parent, false)
return GroupsListViewHolder(binding)
}
override fun onBindViewHolder(holder: GroupsListViewHolder, position: Int) {
holder.bind(getItem(position), onClick)
}
override fun submitList(list: List<GroupModel>?) {
super.submitList(list?.let { ArrayList(it) })
}
}
/**
* Default DiffUtil callback for lists adapters.
* The adapter utilizes the fact that all models in the app implement the "ModelWithId" interfaces, so
* it uses it in order to compare the unique ID of each model for `areItemsTheSame` function.
* As for areContentsTheSame we utilize the fact that Kotlin Data Class implements for us the equals between
* all fields, so use the equals() method to compare one object to another.
*/
class DefaultAdapterDiffUtilCallback<T : ModelWithId> : DiffUtil.ItemCallback<T>() {
override fun areItemsTheSame(oldItem: T, newItem: T) =
oldItem.fetchId() == newItem.fetchId()
#SuppressLint("DiffUtilEquals")
override fun areContentsTheSame(oldItem: T, newItem: T) =
oldItem == newItem
}
/**
* An interface to determine for each model in the app what is the unique ID for it.
* This is used for comparing the unique ID for each model for abstracting the DiffUtil Callback
* and creating a default general one rather than a new class for each new adapter.
*/
interface ModelWithId {
fun fetchId(): String
}
data class GroupModel(val id: String, val groupName: String, var tasksCounter: Int, val usersFullNames: List<String>) : ModelWithId {
override fun fetchId(): String = id
}
edit 2.0 -
my observeOnce() extension -
fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observer<T>) {
observe(lifecycleOwner, object : Observer<T> {
override fun onChanged(t: T?) {
observer.onChanged(t)
removeObserver(this)
}
})
}
Are you using the "new" ListAdapter?
import androidx.recyclerview.widget.ListAdapter
In this case, I can think of an answer to your problem. But since I do not know more about your exact implementation it is based on my assumptions and you have to verify if it applies or not.
For this ListAdapter you have to implement areItemsTheSame and areContentsTheSame methods.
I've once had a similar problem. I was submitting the list but it just didn't update the list in the view.
I could resolve this issue by checking carefully how I was comparing if the contents are the same or not.
For your comparison function, consider the following:
override fun areContentsTheSame(oldItem: GroupModel, newItem: GroupModel): Boolean {
// assuming GroupModel is a class
// this comparison is most likely not getting the result you want
val groupModelsAreMatching = oldItem == newItem // don't do this
// for data classes it usually gives the expected result
val exampleDataClassesMatch = oldItem.dataClass == newItem.dataClass
// But: the properties that need to be compared need to be declared in the primary constructor
// and not in the function body
// compare all relevant custom properties
val groupIdMatches = oldItem.groupId == newItem.groupId
val groupNameMatches = oldItem.groupName == newItem.groupName
val groupTaskCountMatches = oldItem.groupTaskCount == newItem.groupTaskCount
val groupUsersFullNamesMatches = oldItem.groupUsersFullNames == newItem.groupUsersFullNames
return groupIdMatches && groupNameMatches && groupTaskCountMatches && groupUsersFullNamesMatches
}
And of course you need to make sure that areItemsTheSame. Here you only need to compare the groupIds.
Did you do it like this already?
I figured out the problem.
And it has nothing to do with my fetch logics.
The issue is the following -
When creating a group, I am adding a new Fragment to the backstack and popping it off when completed.
When deleting a group, I am navigating forward to the main Fragment of mine while using popUpTo and popUpToInclusive - that works fine.
I needed to use the navigation rather than popping backwards the stack in order to see the new list.
This took me 3 days of work to figure out. jeez
I want to show Room Database entries in a RecyclerView. So far I have the Room skeleton and I can show some dummy content (not from Room) in the RecyclerView:
However I have struggles showing Room DB entries instead of the dummy content. In Arduino the EEPROM I/O stuff used to be almost a oneliner but within Android Room this conceptually easy task seems to be a code-intense and not-so-forward challenge. This brings me to my first question:
1) As in my case the database is pretty slim and simple, is there any simpler approach than Room using less overhead and classes?
Regarding the Room approach, I believe that I am pretty close. I have difficulties implementing the following:
2) How can I substitute the for-loop in init DummyContent by the Room-DB entries (allJumps from ViewModel)?
Here is what I got so far (I didn't post anything below the ViewModel such as Repository and DAO's as it should not of interest right now):
DummyItems (dummy contents to be replaced by Room DB entries)
object DummyContent {
// An array of sample (dummy) items.
val ITEMS: MutableList<DummyItem> = ArrayList()
// A map of sample (dummy) items, by ID.
val ITEM_MAP: MutableMap<String, DummyItem> = HashMap()
private val COUNT = 25
init {
// Add some sample items.
// TO BE REPLACED BY ROOM DB ENTRIES <----------------------------------------------------
for (i in 1..COUNT) {
addItem(createDummyItem(i))
}
}
private fun addItem(item: DummyItem) {
ITEMS.add(item)
ITEM_MAP.put(item.id, item)
}
private fun createDummyItem(position: Int): DummyItem {
return DummyItem(position.toString(), "Item " + position, makeDetails(position))
}
private fun makeDetails(position: Int): String {
val builder = StringBuilder()
builder.append("Details about Item: ").append(position)
for (i in 0..position - 1) {
builder.append("\nMore details information here.")
}
return builder.toString()
}
// A dummy item representing a piece of content.
data class DummyItem(val id: String, val content: String, val details: String) {
override fun toString(): String = content
}
}
allJumps / JumpData
// allJumps is of type LiveData<List<JumpData>>
#Entity
data class JumpData (
#PrimaryKey var jumpNumber: Int,
var location: String?
}
ViewModel
class JumpViewModel(application: Application) : AndroidViewModel(application) {
// The ViewModel maintains a reference to the repository to get data.
private val repository: JumpRepository
// LiveData gives us updated words when they change.
val allJumps: LiveData<List<JumpData>>
init {
// Gets reference to WordDao from WordRoomDatabase to construct
// the correct WordRepository.
val jumpsDao = JumpRoomDatabase.getDatabase(application, viewModelScope).jumpDao()
repository = JumpRepository(jumpsDao)
allJumps = repository.allJumps // OF INTEREST <----------------------------------------------------
}
fun insert(jump: JumpData) = viewModelScope.launch {
repository.insert(jump)
}
fun getJumps() : LiveData<List<JumpData>> {
return allJumps
}
}
You can try to add this to object DummyContent
object DummyContent {
val jumpsLiveData = MutableLiveData<List<JumpData>>()
private val observedLiveData: LiveData<List<JumpData>>? = null
private val dataObserver = object : Observer<List<JumpData>> {
override fun onChanged(newList: List<JumpData>) {
// Do something with new data set
}
}
fun observeJumpsData(jumpsLiveData: LiveData<List<JumpData>>) {
observedLiveData?.removeObserver(dataObserver)
observedLiveData = jumpsLiveData.apply {
observeForever(dataObserver)
}
}
}
And this to viewModel's init block:
init {
val jumpsDao = JumpRoomDatabase.getDatabase(application, viewModelScope).jumpDao()
repository = JumpRepository(jumpsDao)
allJumps = repository.allJumps
DummyContent.observeJumpsData(getJumps())
}
By this code, DummyContent will automatically subscribe to new data after ViewModel creation
And in 'Activity', where you created RecyclerView, add this text to end of onCreate:
override fun onCreate(savedState: Bundle?) {
DummyContent.jumpsLiveData.observe(this, Observer {
recyclerAdapter.changeItemsList(it)
}
}
changeItemsList - method that changes your recycler's data, i believe, you already created it