How to restore recyclerview scroll position when using PagingDataAdapter? - android

I have an App that fetches a list of 158 Items from an API, stores it in Room, and displays it to the user. RoomDB is the source of truth.
This is the code on my ViewModel that gets the result from the database:
private val pagingConfig =
PagingConfig(pageSize = 20, enablePlaceholders = false, maxSize = 300)
fun getList(filters: Filters): Flow<PagingData<Character>> {
return Pager(pagingConfig) {
repository.getCharacters(filters)
}.flow.map {
it.asDomainModel()
}
}
This is the code on my fragment that populates the adapter:
private fun fetchData(filters: Filters) {
lifecycleScope.launch {
charactersViewModel.getList(filters).collectLatest { pagedList ->
characterAdapter.submitData(pagedList)
}
}
}
Current behaviour:
When a configuration change occurs after my 60th Item, the scroll position is lost, I've found out that increasing the pageSize on my pagingConfig from 20 to 55, it fixes this issue.
What I have already tried:
Since I'm fetching the data asynchronously I've tried using this piece of code from this article to prevent loading the data when adapter is empty. but it didn't work
characterAdapter.stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY
What I expect to achieve:
Be able to scroll to the bottom of the list and do configuration changes without loosing the scroll position "without having the need to increase my pageSize as the list gets bigger"
https://github.com/doilio/DC-Characters

Don't return a new instance of Flow<PagingData> evertime from your getList method.
Do something like this:
class YourViewModel: ViewModel() {
private mPagingData = Flow<PagingData<Character>>? = null;
fun getList(filters: Filters): Flow<PagingData<Character>> {
if(mPagingData != null) return mPagingData; //This is important
else
mPagingData = Pager(pagingConfig) {
repository.getCharacters(filters)
}.flow.map {
it.asDomainModel()
}
return mPagingData;
}
}
Apart from this, make sure you initialize your adapter in onCreate of your fragment.

The method you're looking for is PagingSource.getRefreshKey(). It's given the previous state, a PagingState, which has the field PagingState.anchorPosition, the last accessed index (including placeholders).
https://developer.android.com/topic/libraries/architecture/paging/v3-network-db#refresh-in-place
EDIT: I see now that you're using Room's implementation. There was actually a few bugs related to Remote REFRESH and its getRefreshKey implementation that should be resolved in the next release (alpha07). See https://issuetracker.google.com/issues/167260236

Had the same issue. In my situation, I changed at PagingConfig enablePlaceholders to true and set initialLoadSize to be dividable to pageSize (for example, pageSize=10, initialLoadSize = 20).

PagingDataAdapter has a flow attribute called loadStateFlow that contains state of the processed data inside.
You can try like this:
lifecycleScope.launchWhenCreated {
adapter.loadStateFlow.map { it.refresh }
.distinctUntilChanged()
.collect {
if (it is LoadState.NotLoading) {
// reset to position you wanted
}
}
}
LoadState.NotLoading is the specific state of loadStateFlow that corresponds to the state that processing data is finished.
LoadState.NotLoading

Related

How to stop collectAsLazyPagingItems() from firing when itemCount is 0?

I have a screen where I display some items using pagination. Here is what I have tried:
fun ItemsScreen(
viewModel: ItemsViewModel = hiltViewModel()
) {
Box(
modifier = Modifier.fillMaxSize()
) {
val items = viewModel.getItems().collectAsLazyPagingItems()
Log.d(TAG, "itemCount is ${items.itemCount}")
}
}
Here is how I call it from the ViewModel class:
fun getItems() = repo.getItems()
And here is the repo:
override fun getItems() = Pager(
config = config
) {
AppPagingSource(
query = db.collection("items").limit(12)
)
}.flow
When I open the page, I get:
itemCount is 0
itemCount is 12
So, first time I get zero. When the data becomes available, I get 12. How can I stop collectAsLazyPagingItems() from firing when the itemCount is zero? I only want to fire when the data is received. How to solve this?
Edit:
Why do I need to stop collectAsLazyPagingItems() from firing?
Because the same code as above is used in a pagination. So, each time I type a character, a new request is performed, and until I get the page results, I get zero, and after 2 seconds I get 12.
What I want to achieve, is when I get no results because of a wrong search, I want to display a message, "No items found". But only then, not each time I load new data. With the code above, until I'm getting new results, that message is displayed, because items.itemCount == 0 for 2 seconds. After that, the results are correctly displayed in the grid. Here is the logic:
if (items.itemCount > 0) {
LazyVerticalGrid(...)
} else {
if (searchText.isNotEmpty()) {
Text("No items found")
}
}
Edit2:
fun getItems(searchText: String) = if (searchText.isEmpty()) {
repo.getItems()
} else {
repo.getSearchItems(searchText)
}.cachedIn(viewModelScope)
How can I stop collectAsLazyPagingItems() from firing when the itemCount is zero?
You can't. Following is a section from the source code:
When you call items.itemCount it gives you the size of itemSnapshotList which is initialized with an emptyList in the beginning. Think of the paging items as a state instead of an event which is fired. A state has always some value associated with it.
Although I believe that an empty list emission shouldn't cause any problem in general but if you really don't want to process that value, the only option is to ignore it by an if statement like this:
val items = viewModel.getItems().collectAsLazyPagingItems()
if(items.itemCount > 0) {
// UI goes here
}
// Some other UI
Or you can also return from the composable, if there's nothing to show in case of empty list.
val items = viewModel.getItems().collectAsLazyPagingItems()
if(items.itemCount == 0)
return
// UI goes here
Edit: You can use items.loadState.refresh to check if the data is being refreshed. The updated code will look like this:
when {
items.loadState.refresh is LoadState.Loading -> {
CircularProgressIndicator()
}
items.itemCount > 0 -> {
LazyVerticalGrid(...)
}
searchText.isNotEmpty() -> {
Text("No items found")
}
}

How to save paging state of LazyColumn during navigation in Jetpack Compose

I'm using androidx.paging:paging-compose (v1.0.0-alpha-14), together with Jetpack Compose (v1.0.3), I have a custom PagingSource which is responsible for pulling items from backend.
I also use compose navigation component.
The problem is I don't know how to save a state of Pager flow between navigating to different screen via NavHostController and going back (scroll state and cached items).
I was trying to save state via rememberSaveable but it cannot be done as it is not something which can be putted to Bundle.
Is there a quick/easy step to do it?
My sample code:
#Composable
fun SampleScreen(
composeNavController: NavHostController? = null,
myPagingSource: PagingSource<Int, MyItem>,
) {
val pager = remember { // rememberSaveable doesn't seems to work here
Pager(
config = PagingConfig(
pageSize = 25,
),
initialKey = 0,
pagingSourceFactory = myPagingSource
)
}
val lazyPagingItems = pager.flow.collectAsLazyPagingItems()
LazyColumn() {
itemsIndexed(items = lazyPagingItems) { index, item ->
MyRowItem(item) {
composeNavController?.navigate(...)
}
}
}
}
I found a solution!
#Composable
fun Sample(data: Flow<PagingData<Something>>):
val listState: LazyListState = rememberLazyListState()
val items: LazyPagingItems<Something> = data.collectAsLazyPagingItems()
when {
items.itemCount == 0 -> LoadingScreen()
else -> {
LazyColumn(state = listState, ...) {
...
}
}
}
...
I just found out what the issue is when using Paging.
The reason the list scroll position is not remembered with Paging when navigating boils down to what happens below the hood.
It looks like this:
Composable with LazyColumn is created.
We asynchronously request our list data from the pager. Current pager list item count = 0.
The UI draws a lazyColumn with 0 items.
The pager responds with data, e.g. 10 items, and the UI is recomposed to show them.
User scrolls e.g. all the way down and clicks the bottom item, which navigates them elsewhere.
User navigates back using e.g. the back button.
Uh oh. Due to navigation, our composable with LazyColumn is recomposed. We start again with asynchronously requesting pager data. Note: pager item count = 0 again!
rememberLazyListState is evaluated, and it tells the UI that the user scrolled down all the way, so it now should go back to the same offset, e.g. to the fifth item.
This is the point where the UI screams in wild confusion, as the pager has 0 items, so the lazyColumn has 0 items.
The UI cannot handle the scroll offset to the fifth item. The scroll position is set to just show from item 0, as there are only 0 items.
What happens next:
The pager responds that there are e.g. 10 items again, causing another recomposition.
After recomposition, we see our list again, with scroll position starting on item 0.
To confirm this is the case with your code, add a simple log statement just above the LazyColumn call:
Log.w("TEST", "List state recompose. " +
"first_visible=${listState.firstVisibleItemIndex}, " +
"offset=${listState.firstVisibleItemScrollOffset}, " +
"amount items=${items.itemCount}")
You should see, upon navigating back, a log line stating the exact same first_visible and offset, but with amount items=0.
The line directly after that will show that first_visible and offset are reset to 0.
My solution works, because it skips using the listState until the pager has loaded the data.
Once loaded, the correct values still reside in the listState, and the scroll position is correctly restored.
Source: https://issuetracker.google.com/issues/177245496
Save the list state in your viewmodel and reload it when you navigate back to the screen containing the list. You can use LazyListState in your viewmodel to save the state and pass that into your composable as a parameter. Something like this:
class MyViewModel: ViewModel() {
var listState = LazyListState()
}
#Composable
fun MessageListHandler() {
MessageList(
messages: viewmodel.messages,
listState = viewmode.listState
)
}
#Composable
fun MessageList(
messages: List<Message>,
listState: LazyListState) {
LazyColumn(state = listState) {
}
}
If you don't like the limitations that Navigation Compose puts on you, you can try using Jetmagic. It allows you to pass any object between screens and even manages your viewmodels in a way that makes them easier to access from any composable:
https://github.com/JohannBlake/Jetmagic
The issue is that when you navigate forward and back your composable will recompose and collectAsLazyPagingItems() will be called again, triggering a new network request.
If you want to avoid this issue, you should call pager.flow.cacheIn(viewModelScope) on your ViewModel with activity scope (the ViewModel instance is kept across fragments) before calling collectAsLazyPagingItems().
LazyPagingItems is not intended as a persistent data store; it is just a simple wrapper for the UI layer. Pager data should be cached in the ViewModel.
please try using '.cachedIn(viewModelScope) '
simple example:
#Composable
fun Simple() {
val simpleViewModel:SimpleViewModel = viewModel()
val list = simpleViewModel.simpleList.collectAsLazyPagingItems()
when (list.loadState.refresh) {
is LoadState.Error -> {
//..
}
is LoadState.Loading -> {
BoxProgress()
}
is LoadState.NotLoading -> {
when (list.itemCount) {
0 -> {
//..
}
else -> {
LazyColumn(){
items(list) { b ->
//..
}
}
}
}
}
}
//..
}
class SimpleViewModel : ViewModel() {
val simpleList = Pager(
PagingConfig(PAGE_SIZE),
pagingSourceFactory = { SimpleSource() }).flow.cachedIn(viewModelScope)
}

Paging library 3.0 : How to pass total count of items to the list header?

Help me please.
The app is just for receiving list of plants from https://trefle.io and showing it in RecyclerView.
I am using Paging library 3.0 here.
Task: I want to add a header where total amount of plants will be displayed.
The problem: I just cannot find a way to pass the value of total items to header.
Data model:
data class PlantsResponseObject(
#SerializedName("data")
val data: List<PlantModel>?,
#SerializedName("meta")
val meta: Meta?
) {
data class Meta(
#SerializedName("total")
val total: Int? // 415648
)
}
data class PlantModel(
#SerializedName("author")
val author: String?,
#SerializedName("genus_id")
val genusId: Int?,
#SerializedName("id")
val id: Int?)
DataSource class:
class PlantsDataSource(
private val plantsApi: PlantsAPI,
private var filters: String? = null,
private var isVegetable: Boolean? = false
) : RxPagingSource<Int, PlantView>() {
override fun loadSingle(params: LoadParams<Int>): Single<LoadResult<Int, PlantView>> {
val nextPageNumber = params.key ?: 1
return plantsApi.getPlants( //API call for plants
nextPageNumber, //different filters, does not matter
filters,
isVegetable)
.subscribeOn(Schedulers.io())
.map<LoadResult<Int, PlantView>> {
val total = it.meta?.total ?: 0 // Here I have an access to the total count
//of items, but where to pass it?
LoadResult.Page(
data = it.data!! //Here I can pass only plant items data
.map { PlantView.PlantItemView(it) },
prevKey = null,
nextKey = nextPageNumber.plus(1)
)
}
.onErrorReturn{
LoadResult.Error(it)
}
}
override fun invalidate() {
super.invalidate()
}
}
LoadResult.Page accepts nothing but list of plant themselves. And all classes above DataSource(Repo, ViewModel, Activity) has no access to response object.
Question: How to pass total count of items to the list header?
I will appreciate any help.
You can change the PagingData type to Pair<PlantView,Int> (or any other structure) to add whatever information you need.
Then you will be able to send total with pages doing something similar to:
LoadResult.Page(
data = it.data.map { Pair(PlantView.PlantItemView(it), total) },
prevKey = null,
nextKey = nextPageNumber.plus(1)
)
And in your ModelView do whatever, for example map it again to PlantItemView, but using the second field to update your header.
It's true that it's not very elegant because you are sending it in all items, but it's better than other suggested solutions.
Faced the same dilemma when trying to use Paging for the first time and it does not provide a way to obtain count despite it doing a count for the purpose of the paging ( i.e. the Paging library first checks with a COUNT(*) to see if there are more or less items than the stipulated PagingConfig value(s) before conducting the rest of the query, it could perfectly return the total number of results it found ).
The only way at the moment to achieve this is to run two queries in parallel: one for your items ( as you already have ) and another just to count how many results it finds using the same query params as the previous one, but for COUNT(*) only.
There is no need to return the later as a PagingDataSource<LivedData<Integer>> since it would add a lot of boilerplate unnecessarily. Simply return it as a normal LivedData<Integer> so that it will always be updating itself whenever the list results change, otherwise it can run into the issue of the list size changing and that value not updating after the first time it loads if you return a plain Integer.
After you have both of them set then add them to your RecyclerView adapter using a ConcatAdapter with the order of the previously mentioned adapters in the same order you'd want them to be displayed in the list.
ex: If you want the count to show at the beginning/top of the list then set up the ConcatAdapter with the count adapter first and the list items adapter after.
One way is to use MutableLiveData and then observe it. For example
val countPlants = MutableLiveData<Int>(0)
override fun loadSingle(..... {
countPlants.postValue(it.meta?.total ?: 0)
}
Then somewhere where your recyclerview is.
pagingDataSource.countPlants.observe(viewLifecycleOwner) { count ->
//update your view with the count value
}
The withHeader functions in Paging just return a ConcatAdapter given a LoadStateHeader, which has some code to listen and update based on adapter's LoadState.
You should be able to do something very similar by implementing your own ItemCountAdapter, except instead of listening to LoadState changes, it listens to adapter.itemCount. You'll need to build a flow / listener to decide when to send updates, but you can simply map loadState changes to itemCount.
See here for LoadStateAdapter code, which you can basically copy, and change loadState to itemCount: https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:paging/runtime/src/main/java/androidx/paging/LoadStateAdapter.kt?q=loadstateadapter
e.g.,
abstract class ItemCountAdapter<VH : RecyclerView.ViewHolder> : RecyclerView.Adapter<VH>() {
var itemCount: Int = 0
set(itemCount { ... }
open fun displayItemCountAsItem(itemCount: Int): Boolean {
return true
}
...
Then to actually create the ConcatAdapter, you want something similar to: https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:paging/runtime/src/main/java/androidx/paging/PagingDataAdapter.kt;l=236?q=withLoadStateHeader&sq=
fun PagingDataAdapter.withItemCountHeader(itemCountAdapter): ConcatAdapter {
addLoadStateListener {
itemCountAdapter.itemCount = itemCount
}
return ConcatAdapter(itemCountAdapter, this)
}
Another solution, although also not very elegant, would be to add the total amount to your data model PlantView.
PlantView(…val totalAmount: Int…)
Then in your viewmodel you could add a header with the information of one item. Here is a little modified code taken from the official paging documenation
pager.flow.map { pagingData: PagingData<PlantView> ->
// Map outer stream, so you can perform transformations on
// each paging generation.
pagingData
.map { plantView ->
// Convert items in stream to UiModel.PlantView.
UiModel.PlantView(plantView)
}
.insertSeparators<UiModel.PlantView, UiModel> { before, after ->
when {
//total amount is used from the next PlantView
before == null -> UiModel.SeparatorModel("HEADER", after?.totalAmount)
// Return null to avoid adding a separator between two items.
else -> null
}
}
}
A drawback is the fact that the total amount is in every PlantView and it's always the same and therefore redundant.
For now, I found this comment usefull: https://issuetracker.google.com/issues/175338415#comment5
There people discuss the ways to provide metadata state to Pager
A simple way I found to fix it is by using a lambda in the PagingSource constructor. Try the following:
class PlantsDataSource(
// ...
private val getTotalItems: (Int) -> Unit
) : RxPagingSource<Int, PlantView>() {
override fun loadSingle(params: LoadParams<Int>): Single<LoadResult<Int, PlantView>> {
...
.map<LoadResult<Int, PlantView>> {
val total = it.meta?.total ?: 0
getTotalItems(total)
...
}
...
}
}

How to avoid all sources of a MediatorLiveData calling the same function?

Introduction to the problem:
When using the MediatorLiveData that android has added with android jetpack, I find myself often calling the same function from every source. This can for example be because, whenever a source is updated, I have to check if it has an influence, or if another source is more important. An example in code (Kotlin, but shouldn't matter):
val isHovered = MutableLiveData<Boolean>()
val isSelected = MutableLiveData<Boolean>()
val color = MediatorLiveData<Int>().apply {
addSource(isHovered) { updateColor() }
addSource(isSelected) { updateColor() }
}
fun updateColor() {
if (isHovered.value == true)
color.value = Color.GREEN
else if (isSelected.value == true)
color.value = Color.RED
else
color.value = Color.GRAY
}
The item is green when hovering, red when selected and not hovering and gray otherwise. When isSelected changes to true, I still need to check whether it is hovering, before changing the color to red. Also when isHovering changes to false, I need to check whether it is selected before changing the color to grey. So the easiest is one function that takes all the variables into account and sets the color accordingly.
My problem:
When the MediatorLiveData changes from inactive to active, because the view is moved to the foreground, it can happen that the function updateColor is called multiple times, for each source that changed. This is unnecessary because each call already takes all variables into account. As this function can be quite complex and there can be many sources, is there a way to avoid calling it multiple times for the same states of the source LiveDatas?
I had the same issue and came up with the following solution. First, gather values from all sources to one value:
data class State(val isHovered: Boolean, val isSelected: Boolean)
private val state = MediatorLiveData<State>().apply {
fun update() {
value = State(isHovered.value ?: false, isSelected.value ?: false)
}
addSource(isHovered) { update() }
addSource(isSelected) { update() }
}
Then I created an extension function to emit only distinct values
fun <T> LiveData<T>.distinct(): LiveData<T> = MediatorLiveData<T>().apply {
var wasSet = false
addSource(this#distinct) {
// Update value if it has been changed or it's the first time the method called
// Because value is null by default it will not be set to null again, leaving the live data in [value not set] state
if (value != it || !wasSet) {
value = it
wasSet = true
}
}
}
So when observing state.distinct() you will get updated values only when it's really changed.
Sometimes also another extension can be useful:
fun <T> LiveData<T>.squashUpdates() = MediatorLiveData<T>().apply {
addSource(this#squashUpdates) {
postValue(it)
}
}
Normally, when LiveData value is changed, it notifies all observers immediately in the same stack frame. Let's say you have a MediatorLiveData and change values of multiple sources like:
isHovered.value = true
isSelected.value = true
state value will be changed two times in a row State(true, false) and State(true, true). They both will be dispatched, even when using distinct from above, because values are actually different. squashUpdates helps in this case by delaying dispatch to the end of the stack frame, dispatching last value only.
To avoid multiple calls, you should send only new updates from your LiveData instances. To achieve this, you can use SingleLiveEvent:
A lifecycle-aware observable that sends only new updates after
subscription, used for events like navigation and Snackbar messages.
This avoids a common problem with events: on configuration change
(like rotation) update can be emitted if the observer is active. This
LiveData only calls the observable if there's an explicit call to
setValue() or call().
Note that only one observer is going to be notified of changes.
You probably won't be able to prevent isHovered or isSelected from emitting items, but you can pervent color from emitting multiple items of same type. The idea is to change the color only when the current color is not equal to the new color.
fun updateColor() {
var newColor: Color?
if (isHovered.value == true)
newColor = Color.GREEN
else if (isSelected.value == true)
newColor = Color.RED
else
newColor = Color.GRAY
if (color.value != newColor) {
color.value = newColor!!
}
}

How to delete/remove PagedListAdapter item

Currently I am using Android Architecture Components for App development everything is working along with paging library Now I want to remove recyclerview Item using PagedListAdapter to populate this we required to add a data source and from data source list is updating using LiveData no I want to remove a item from list notifyItemRemoved() is working from PagedList I am getting this exception:
java.lang.UnsupportedOperationException
java.util.AbstractList.remove(AbstractList.java:638)
Since you cannot directly modify the data that you have in the PagedList as I believe its immutable, the key to implementing the removal of items in this situation is maintaining a backing dataset somewhere in your code that represents all the data that you have received so far. An ArrayList worked for me.
When you want to remove an item, remove it from your backing dataset and call invalidate on your data source. This will trigger the loadInitial() call, in which you can pass your backing dataset to the callback.onResult() function. Your PagedListAdapter will use the DiffUtil.ItemCallback you supplied to smartly determine that there has been an update to its data and will update itself accordingly.
This tutorial helped me understand the correct flow to use in order to properly delete an item while using the paging library:
https://medium.com/#FizzyInTheHall/implementing-swipe-to-delete-with-android-architecture-components-a95165b6c9bd
The repo associated with the tutorial can be found here:
https://gitlab.com/adrianhall/notes-app
Temporary hide item in list by call notifyItemUpdated() base on flags set in Pojo object
if(data?.hasVisible == false) {
itemBinding.root.visibility = View.GONE
itemBinding.root.layoutParams = RecyclerView.LayoutParams(0, 0)
}else{
itemBinding.root.visibility = View.VISIBLE
itemBinding.root.layoutParams = RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT,
RecyclerView.LayoutParams.WRAP_CONTENT)
}
I had same issue.
My PagedList displayed items that DataSource factory fetched from server. I came up with two solutions how to remove item from list.
Reload whole list.
Make API call to remove item from server and then call
pagedList.dataSource.invalidate().
Downside of this solution is that whole list is cleared and then all items received from server. Not exactly what I was looking for.
Store results in Room. Then PagedList will get items directly from Room and PagedListAdapter will manage removing/adding items itself.
In DAO object
("SELECT * FROM YourModel")
fun getAll(): DataSource.Factory<Int, YourModel>
#Delete
fun delete(item: YourModel)
To update database as user scrolls list I implemented BoundaryCallback. It is being called when there are no more items to show in DB, it can be called at the end of same page, so I ensured to not execute same request few times (In my case list's key is page number).
class YourModelBoundaryCallback(private val repository: Repository) : PagedList.BoundaryCallback<YourModel>() {
private val requestArray = SparseArray<Disposable>()
private var nextPage = 1
private var lastPage = 1
override fun onZeroItemsLoaded() {
if (requestArray.get(1) == null) {
requestArray.put(1, repository.getFirstPage()
.subscribe({
lastPage = it.total / PAGE_SIZE + if (it.total % PAGE_SIZE == 0) 0 else 1
nextPage++
}))
}
}
override fun onItemAtEndLoaded(itemAtEnd: YourModel) {
if (nextPage > lastPage) {
return
}
if (requestArray.get(nextPage) == null) {
requestArray.put(nextPage, repository.getNextPage(nextPage)
.subscribe({
lastPage = it.total / PAGE_SIZE + if (it.total % PAGE_SIZE == 0) 0 else 1
nextPage++
}))
}
}
override fun onItemAtFrontLoaded(itemAtFront: YourModel) {
// ignored, since we only ever append to what's in the DB
}
}
PagedList instance became this
private val pagedListConfig = PagedList.Config.Builder()
.setEnablePlaceholders(false)
.setPrefetchDistance(3)
.setPageSize(PAGE_SIZE)
.build()
val pagedList = LivePagedListBuilder(yourModelDao.getAll(), pagedListConfig)
.setBoundaryCallback(YourModelBoundaryCallback(repository))
.build()
And finally to remove item from adapter I just call yourModelDao.remove(yourModel)
Adapter.notifyItemRemoved(position);
appDatabase.userDao().deleteUser(user);
It's work for me!

Categories

Resources