Caching is not working in Android Paging 3 - android

I have implemented application using codelabs tutorial for new Paging 3 library, which was release week ago.
The problem is application is not working in offline mode. It does not retrieve data from Room database.
Tutorial Repo link :- https://github.com/googlecodelabs/android-paging
Code:-
RepoDao.kt
#Dao
interface RepoDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(repos: List<Repo>)
#Query("SELECT * FROM repos WHERE " +
"name LIKE :queryString OR description LIKE :queryString " +
"ORDER BY stars DESC, name ASC")
fun reposByName(queryString: String): PagingSource<Int, Repo>
#Query("DELETE FROM repos")
suspend fun clearRepos()
}
GithubRepository.kt
class GithubRepository(
private val service: GithubService,
private val database: RepoDatabase
) {
fun getSearchResultStream(query: String): Flow<PagingData<Repo>> {
val dbQuery = "%${query.replace(' ', '%')}%"
val pagingSourceFactory = { database.reposDao().reposByName(dbQuery) }
return Pager(
config = PagingConfig(pageSize = NETWORK_PAGE_SIZE),
remoteMediator = GithubRemoteMediator(
query,
service,
database
),
pagingSourceFactory = pagingSourceFactory
).flow
}
companion object {
private const val NETWORK_PAGE_SIZE = 50
}
}
SearchRepositoriesViewModel.kt
#ExperimentalCoroutinesApi
class SearchRepositoriesViewModel(private val repository: GithubRepository) : ViewModel() {
private var currentQueryValue: String? = null
private var currentSearchResult: Flow<PagingData<Repo>>? = null
fun searchRepo(queryString: String): Flow<PagingData<Repo>> {
val lastResult = currentSearchResult
if (queryString == currentQueryValue && lastResult != null) {
return lastResult
}
currentQueryValue = queryString
val newResult: Flow<PagingData<Repo>> = repository.getSearchResultStream(queryString).cachedIn(viewModelScope)
currentSearchResult = newResult
return newResult
}
}
SearchRepositoriesActivity.kt
#ExperimentalCoroutinesApi
class SearchRepositoriesActivity : AppCompatActivity() {
.....
private lateinit var viewModel: SearchRepositoriesViewModel
private val adapter = ReposAdapter()
private var searchJob: Job? = null
// this is where adapter get flow data from viewModel
// initially this is called with **Android** as a query
private fun search(query: String) {
searchJob?.cancel()
searchJob = lifecycleScope.launch {
viewModel.searchRepo(query).collectLatest {
adapter.submitData(it)
}
}
}
.....
}
Output:- It is just showing the empty recyclerview when application is open in offline mode.

If you're able to share your code or how you reached that conclusion I could probably help pinpoint the problem a bit better, but the codelab does load data from Room on the branch: step13-19_network_and_database
There are two components here:
PagingSource: Provided by Room by declaring a #Query with a PagingSource return type, will create a PagingSource that loads from Room. This function is called in the pagingSourceFactory lambda in Pager which expects a new instance each call.
RemoteMediator: load() called on boundary conditions where the local cache is out of data, this will fetch from network and store in the Room db, which automatically propagates updates to PagingSource implementation generated by Room.
One other issue you might be seeing could be related to loadStateListener/Flow, essentially the codelab shows an error state by checking for CombinedLoadStates.refresh, but this always defers to the RemoteMediator's load state when available and if you want to show the locally cached data, even when RemoteMediator errors out, you'll need to disable hiding of the list in that case.
Note that you can access individual LoadState with CombinedLoadStates.source or CombinedLoadStates.mediator.
Hopefully this is enough to help you, but it's hard to guess your issue without some more concrete example / information about what you're seeing.
Edit: While the above are still good things to check for, it looks like there's an underlying issue with the library that I'm chasing down here: https://android-review.googlesource.com/c/platform/frameworks/support/+/1341068
Edit2: This is fixed now and will be released with alpha02.

Related

LiveData list of objects from Room query not showing up in the view

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

How to call different api resource from paging source or remote mediator Kotlin

Hey I want to call two different api for my Paging Library 3. I want to ask what is best suit for me to use Paging Source or Remote Mediator?. What is the use case of both? Can someone please explain me.
For 1st api call only for single time
#GET("/movie?min=20")
Above api call returns this response
data class movie(
var id: Int?,
var name: String?,
var items : List<Genre>?
}
Now for 2nd api call its loop to call again and again
#GET("/movie?count=20&&before={time}")
Above api call retrun this
data class movie(
var items : List<Genre>?
}
Genre
data class Genre(
var type: String?,
var date: String?,
var cast: String?
}
Genre have data in both api call. I tried to google this and found this Example. But inside this both api return same data. But in my case both returns little bit different. Also id, name is only used in UI component else list will go to adapter. But I didn't understand how to achieved this. I am new in Flow, it too difficult to understand, to be honest I am trying to learning CodeLab. Another important thing when 1st time api call, in which the last item contains date will send to 2nd api call in time parameter and then 2nd api last item date call again 2nd api, this will go in loop. So how can I track this again in loop condition. Third I want to update data at top of list, can we store data in memory than we can update value on that list? Thanks for advance. Sorry for my wrong english.
UPDATE
After #dlam suggestion, I tried to practice some code
MainActivity
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val viewModel by viewModels<ActivityViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
lifecycleScope.launchWhenCreated {
viewModel.getMovie().collectLatest {
// setupAdapter()
}
}
}
}
ActivityViewModel
class ActivityViewModel(app: Application) : AndroidViewModel(app) {
fun getMovie(): Flow<PagingData<Genre>> {
return Pager(
config = PagingConfig(
pageSize = 20
),
pagingSourceFactory = {
MultiRequestPagingSource(DataSource())
}
).flow
}
}
MultiRequestPagingSource
class MultiRequestPagingSource(private val dataSource: DataSource) : PagingSource<String, Genre>() {
override fun getRefreshKey(state: PagingState<String, Genre>): String? {
return state.anchorPosition?.let { anchorPosition ->
state.closestPageToPosition(anchorPosition)?.nextKey
}
}
override suspend fun load(params: LoadParams<String>): LoadResult<String, Genre> {
val key = params.key ?: ""
return try {
val data = when (params) {
is LoadParams.Refresh -> {
dataSource.fetchInitialMovie()
}
is LoadParams.Append -> {
dataSource.fetchMovieBefore(key)
}
is LoadParams.Prepend -> null
}
LoadResult.Page(
data = data.result,
prevKey = null,
nextKey = data?.nextKey,
)
} catch (exception: IOException) {
LoadResult.Error(exception)
}
}
}
I am getting error on data = data.result
Type mismatch.
Required:
List<TypeVariable(Value)>
Found:
ArrayDeque<Genre>?
DataSource
package com.example.multirequestpaging
class DataSource {
data class MovieResult(
val result: ArrayDeque<Genre>?,
val nextKey: String?
)
fun fetchInitialMovie(): MovieResult {
val response = ApiInterface.create().getMovieResponse(20)
return MovieResult(
addInArrayDeque(response),
response.items?.last()?.date
)
}
fun fetchMovieBefore(key: String): MovieResult {
val response = ApiInterface.create().getMovieResponseBefore(20, key)
return MovieResult(
addInArrayDeque(response),
response.items?.last()?.date
)
}
private fun addInArrayDeque(response: MovieResponse): ArrayDeque<Genre> {
val result: ArrayDeque<Genre> = ArrayDeque()
response.items?.forEach {
result.add(it)
}
return result
}
}
For Full code Project Link
1. I want to add an item to the top of the list. How can I use invalidate function? Sorry I didn't understand where I can use.
2. I want to use id,name in other place so how can i get those variable value in my activity class.
3. Is my code structure is good?. Do I need to improved, please give an example. It will also help beginner, who is learning Paging Library.
Thanks
PagingSource is the main driver for Paging, it's responsible for loading items that get displayed and represents the single source of truth of data.
RemoteMediator is for layered sources, it is essentially a callback which triggers when PagingSource runs out of data, so you can fetch from a secondary source. This is primarily useful in cases where you fetching from both DB + Network, where you want locally cached data to power Paging, and then use RemoteMediator as a callback to fetch more items into the cache from network.
In this scenario you have two APIs, but they both fetch from the same Network source, so you only need PagingSource here. If I'm understanding correctly, you essentially want to call the first API on initial load and the second API on subsequent prepend / append page loads, which you can check / switch on by the type of LoadParams you get. See the subtypes here: https://developer.android.com/reference/kotlin/androidx/paging/PagingSource.LoadParams

java.util.ConcurrentModificationException while inserting data into room database

I am trying to download data from firebase firestore and insert it into the room DB for some offline use and avoid time-lag using the MVVM architecture pattern but when I do that I get an java.util.ConcurrentModificationException error I am inserting the data into the room DB inside a coroutine.
My code
class HomeFragmentViewModel(application: Application): AndroidViewModel(application) {
private var mDatabase: AppDatabase = AppDatabase.getInstance(application)!!
private val postListRoom: MutableList<PostRoomEntity> = mutableListOf()
private val postList: LiveData<MutableList<PostRoomEntity>>? = getPostList2()
private val firebaseAuth: FirebaseAuth = FirebaseAuth.getInstance()
private val db: FirebaseFirestore = FirebaseFirestore.getInstance()
private val myTAG: String = "MyTag"
#JvmName("getPostList")
fun getPostList(): LiveData<MutableList<PostRoomEntity>>? {
return postList
}
#JvmName("getPostList2")
fun getPostList2(): LiveData<MutableList<PostRoomEntity>>? {
var postsDao: PostsDao? = null
Log.d(myTAG, "postDao getPost is " + postsDao?.getPosts())
return mDatabase.postsDao()?.getPosts()
// return postList
}
fun loadDataPost() {
val list2 = mutableListOf<PostRoomEntity>()
db.collection("Posts")
.addSnapshotListener { snapshots, e ->
if (e != null) {
Log.w(myTAG, "listen:error", e)
return#addSnapshotListener
}
for (dc in snapshots!!.documentChanges) {
when (dc.type) {
DocumentChange.Type.ADDED -> {
dc.document.toObject(PostRoomEntity::class.java).let {
list2.add(it)
}
postListRoom.addAll(list2)
viewModelScope.launch(Dispatchers.IO) {
mDatabase.postsDao()?.insertPost(postListRoom)
}
// mDatabase.let { saveDataRoom(postListRoom, it) }
}
DocumentChange.Type.MODIFIED -> {
}
DocumentChange.Type.REMOVED -> {
Log.d(myTAG, "Removed city: ${dc.document.data}")
}
}
}
}
}
}
PostsDao
#Dao
interface PostsDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertPost(PostEntity: MutableList<PostRoomEntity>)
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAllPosts(PostEntity :List<PostRoomEntity>)
#Query("Select * from PostRoomEntity")
fun getPosts(): LiveData<MutableList<PostRoomEntity>>
// #Query("SELECT * FROM notes WHERE id= :id")
// open fun getNoteById(id: Int): NoteEntity?
}
It is very error-prone to use MutableLists with asynchronous tasks or to expose them to outside functions. You are doing both, and this can result in them being modified from two different places in code simultaneously, which can cause a ConcurrentModificationException.
You should use read-only Lists to eliminate this risk. For example, use vars of type List instead of vals of type MutableList.
Some other issues with your code:
You are adding the whole contents of the list to the main list on each step of iteration, so the last item is added once, the second-to-last item is added twice, and so on. You are also inserting that whole exploded list in your local database on each step of iteration, so it is even more exponentially multiplied with redundancies. If you are just trying to update your local database with changes, you should only be inserting a single row at a time anyway.
Unnecessary nullability used in a few places. There's no reason for the DAO or your LiveData to ever be null.
Unnecessary intermediate variables that serve no purpose. Like you create a variable var postsDao: PostsDao? = null and log the null value and never use it.
Redundant and non-idiomatic getters for properties you could expose as public directly.
Redundant backing property for the value that's already held in a LiveData.
You can make your DAO functions suspend so you don't have to worry about which dispatchers you're using to call them.
There's no reason for the DAO to have an insert overload for a MutableList instead of a List. I think the parameter should just be a single item.
You can have a single coroutine iterate the list of changes instead of launching separate coroutines to handle each individual change.
I also recommend not mixing Hungarian and non-Hungarian member names. Actually I don't recommend using Hungarian naming at all, but it's a matter of preference.
And it's a little confusing that you have two databases, but there is nothing about their names to distinguish them.
Fixing these problems, your code will look like this, but there might be other issues because I can't test it or see what it's hooked up to. Also, I don't use Firebase, but I feel like there must be a more robust way of keeping your local database in sync with Firestore than trying to make individual changes with a listener.
class HomeFragmentViewModel(application: Application): AndroidViewModel(application) {
private val localDatabase: AppDatabase = AppDatabase.getInstance(application)!!
private val mutablePostList = MutableLiveData<List<PostRoomEntity>>()
val postList: LiveData<List<PostRoomEntity>> = mutablePostList
private val firebaseAuth: FirebaseAuth = FirebaseAuth.getInstance()
private val firestore: FirebaseFirestore = FirebaseFirestore.getInstance()
private val myTAG: String = "MyTag"
fun loadDataPost() {
db.collection("Posts")
.addSnapshotListener { snapshot, e ->
if (e != null) {
Log.w(myTAG, "listen:error", e)
return#addSnapshotListener
}
mutablePostList.value = snapshot!!.documents.map {
it.toObject(PostRoomEntity::class.java)
}
viewModelScope.launch {
for (dc in snapshot!!.documentChanges) {
when (dc.type) {
DocumentChange.Type.ADDED -> {
val newPost = dc.document.toObject(PostRoomEntity::class.java)
localDatabase.postsDao().insertPost(newPost)
}
DocumentChange.Type.MODIFIED -> {
}
DocumentChange.Type.REMOVED -> {
Log.d(myTAG, "Removed city: ${dc.document.data}")
}
}
}
saveDataRoom(postListRoom, localDatabase) // don't know what this does
}
}
}
}
#Dao
interface PostsDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertPost(postEntity: PostRoomEntity)
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAllPosts(postEntity: List<PostRoomEntity>)
#Query("Select * from PostRoomEntity")
fun getPosts(): LiveData<MutableList<PostRoomEntity>>
// #Query("SELECT * FROM notes WHERE id= :id")
// open fun getNoteById(id: Int): NoteEntity?
}

Android Jetpack Paging 3: PagingSource with Room

I'm using latest Jetpack libraries.
Pagination3 version: 3.0.0-alpha05
Room Version : 2.3.0-alpha02
My entities have Long as PrimaryKey and Room can generate PagingSource for other than Int type.
error: For now, Room only supports PagingSource with Key of type Int.
public abstract androidx.paging.PagingSource<java.lang.Long, com.example.myEntity>` getPagingSource();
Therefore I tried to implement my custom PagingSource, like docs suggest.
The problem is Data Refresh, since Room's generated code handles data refresh and with my code I'm not being able to handle this scenario.
Any suggestions how to implement custom PagingSource for Room that also handles Data Refresh?
Since you have 'refresh' scenario and using Room db, I am guessing you are using Paging3 with network+local db pattern(with Room db as local cache).
I had a similar situation with network + local db pattern. I am not sure if I understand your question correctly, or your situation is the same as the one I had, but I'll share what I did anyway.
What I was using:
Paging3: 3.0.0-beta01
Room: 2.3.0-beta02
What I did was let Room library to create PagingSource (with the key of Int), and let RemoteMediator handle all the other cases, such as fetching the data from network when refreshing and/or appending, and inserting them into db right after fetch success.
My dao function for creating PagingSource from Room Library:
#Query("SELECT * FROM article WHERE isUnread = 1")
fun getUnreadPagingSource(): PagingSource<Int, LocalArticle>
In my case I defined Repository class to have dao class in its constructor to call the function above from repository when creating Pager class.
My custom RemoteMediator class looks something like this below:
Note: In my case, there is no PREPEND case so RemoteMediator#load function always returns true when the value of the argument loadType is LoadType.PREPEND.
class FeedMediator(
private val repository: FeedRepository
) : RemoteMediator<Int, LocalArticle>() {
...
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, LocalArticle>
): MediatorResult = runCatching {
when (loadType) {
LoadType.PREPEND -> true
LoadType.REFRESH -> {
feedRepository.refresh()
false
}
LoadType.APPEND -> {
val continuation = feedRepository.continuation()
if (continuation.isNullOrEmpty()) {
true
} else {
loadFeedAndCheckContinuation(continuation)
}
}
}
}.fold(
onSuccess = { endOfPaginationReached -> MediatorResult.Success(endOfPaginationReached) },
onFailure = {
Timber.e(it)
MediatorResult.Error(it)
}
)
private suspend fun loadFeedAndCheckContinuation(continuation: String?): Boolean {
val feed = feedRepository.load(continuation)
feedRepository.insert(feed)
return feed.continuation.isNullOrEmpty()
}
Finally you can create Pager class.
fun createFeedPager(
mediator: FeedMediator<Int, LocalArticle>,
repository: FeedRepository
) = Pager(
config = PagingConfig(
pageSize = FETCH_FEED_COUNT,
enablePlaceholders = false,
prefetchDistance = PREFETCH_DISTANCE
),
remoteMediator = mediator,
pagingSourceFactory = { repository.getUnreadPagingSource() }
)
I hope it helps in some way..
Other references:
https://developer.android.com/topic/libraries/architecture/paging/v3-network-db
https://android-developers.googleblog.com/2020/07/getting-on-same-page-with-paging-3.html
https://www.youtube.com/watch?v=1cwqGOku2a4
EDIT:
After reading the doc again, I found a statement where the doc clearly states:
RemoteMediator to use for loading the data from the network into the local database.

Room Database entries in RecyclerView

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

Categories

Resources