I'm showing my recycler view like this:
parrafoLeyViewModel.allParrafosLey.observe(this, Observer {
recyclerview_lectura_ley.layoutManager = LinearLayoutManager(this)
adapterListar = AdapterListarLey(this, it, this)
recyclerview_lectura_ley.adapter = adapterListar
})
So when I insert data into the RoomDatabase my recyclerview restarts and goes back to the start
GlobalScope.launch {
db.parrafoLeyDao().updateComentarioLey(
ComentarioLey(
idParrfo,
spTipoComentario.selectedItem.toString(),
etInserteComentario.text.toString()
)
)
}
What I want is that when I insert the data it remains in the same position. I am using ViewModel
You are not correctly passing the data to Recycler View
Please make sure your Data Source is wrapped inside a Live Data
And then pass this live data to your Recycler View Adapter.
This way Recycler View will only re-draw the changed item and not the entire list. Because Recycler View works well with Live Data.
Any method of Recycler View which will cause the list to re-draw should be excluded.
for e.g. notifyDataSetChanged() method.
Instead call submitList()
And lastly please go through below documentation:
Recycler View Sample
RecyclerView.State
Related
I develop an Android-App which has a RecyclerView, each item displayed in the RecyclerView represents a video saved locally. Now, when a button is clicked in one of the items in the RecyclerView, the file should be uploaded to a server. Now my question: How can I implement a solution to do this using MVVM?
I also created a little image which describes my problem:
Note: A short, conceptual answeser is sufficient
your adapter takes in a callback method to invoke once the button is tapped, the fragment then does this upload by using the VM, once that is done, success or failure you alert your UI to make whatever changes are needed. there's no interaction between your viewmodel and your adapter directly
class MyAdapter constructor(
private val callback: (item: YourType) -> Unit,
) : RecyclerView.Adapter.... {
then, you would call this in onBind or wherever you're setting up your button for the adapter, by calling:
myButton.setOnClickListener {
callback.invoke(yourItem)
}
usually this is in the format of:
override fun onBindViewHolder(holder: SomeViewHolder, position: Int) {
val yourItem: YourType = items[position]
....
yourButton.setOnClickListener {
callback.invoke(yourItem)
}
}
when you create your adapter, you now do:
myAdapter = MyAdapter () { callback ->
//here, callback will be of type `YourType`
//here, you can do whatever service call you want with your viewmodel, because this is in your fragment
}
In summary, the logic for what has to happen when an item is tapped is now shifted on to the class making use of the adapter, usually the fragment or the activity. This makes it so that you can easily reuse your adapter, because it doesn't contain any actual business logic inside of it - your adapter basically informs your fragment that:
"something was tapped, here's the corresponding item".
Your adapter notifies your fragment that an item was tapped, your fragment can then decide what to do, in this case it can shift this along to the Viewmodel so that it can then perform any network operation you need and inform the UI of the result of that operation, usually this is done by observing on to live data
I am using ViewModel and repository pattern to fetch the data in the list. The items are arranged as a list of rows and products. A Row class has products in it. The products can be scrolled horizontally. I am using a recycler view with linear layout manager (horizontal orientation) nested inside another recycler view (vertical orientation). Fetching the items via a ViewModel and rendering in the recycler views is pretty straightforward. The challenge is when I try to update the items (their counts) when an item is added to the cart. When the button (plus sign) is clicked, a callback is sent to the view model via a listener. The horizontal adapter sends the request back to the container (vertical) adapter, and the vertical adapter sends it back to the view model.
// The horizontal adapter
class SimpleProductAdapter(
private val shopId: String,
private val listener: (product: CartProduct) -> Unit
) :
ListAdapter<CartProduct, RecyclerView.ViewHolder>(...DiffCallbackGoesHere) {
// ... some more things here
fun bind(item: CartProduct?) {
view.add_to_cart_button.setOnClickListener {
listener(item)
}
}
The vertical adapter has similar structure
class RowAdapter(
private val shopId: String,
private val listener: (product: CartProduct) -> Unit
) :
PagedListAdapter<Row, RecyclerView.ViewHolder>(...RowDiffCallbackGoesHere) {
// ... some more things here
fun bind(item: Row?) {
SimpleProductAdapter(shopId) { product ->
listener(product)
}
}
And the main site inside the fragment where the viewmodel calls exist:
val rowAdapter = RowAdapter(args.shopId) { product->
if (actionType == ADD_TO_CART_ACTION) viewModel.buy(product)
.observe(viewLifecycleOwner, Observer {
view.swipe.isRefreshing = it is Resource.Loading
// I want to update the quantity here on success result
if(Resource is Success) {}
})
When the result is Success, I want to update the quantity; two things are challenging here
I am using PagedListAdapter from the paging library which gives me a (supposedly) immutable list of products.
Even if I update the PagedList and issue a notifyDataSetChanged, it would be too much to do that just to change a single count over a large set of items.
I am hoping to find a way where I can easily target the specific product to update or another alternative that I keep seeing on the web is to build a custom layout manager so that I can have a single adapter that draws everything on a single pass without having to nest the recycler views. That way updating the item would become easier (couldn't find a code example on this).
Any suggestions please.
In Paging3, there are plans to eventually add an API to support granular updates without invalidation: https://issuetracker.google.com/160232968
For now, you must invalidate in order to update the backing dataset. In general, DiffUtil will do a pretty good job of hiding this from the user though.
I followed this guide to understand how to use LiveData and data binding to update the UI when changes occur in the database and also save data back to the database when I make changes in the app.
The part I'm missing is how to properly extend the things explained in the guide to a Layoutwith a RecyclerView. In this case, the ViewModel would contain a LiveData<List<Model>> models member and I would have to bind a RecyclerView list item to a Model, by adding something like this in my list item <layout/>:
<data>
<variable name="model"
type="com.example.models.Model" />
</data>
but still observe the models colletion of my ViewModel in my Activity:
viewModel.models.observe(this, Observer { models ->
modelsAdapter.data = models?.data
modelsAdapter.notifyDataSetChanged()
})
The Observer's method above will not be triggered if a property of a Model instance in the models collection changes so If I want to run some code (like saving data to my DB) when a change is made by the user on a list item which is bound to my Model, I could just add a method to my Model class and bind the onChange event of a View in my list item <layout/> to that method. Let's say that I'd have an EditText on my list item <layout/>, then I could add the lines below to run the the listItemTextChanged() method on the bound Model when the text on my EditText changes:
<EditText
...
android:text="#={model.ListItemText}"
android:onTextChanged="#{ () -> model.listItemTextChanged()}" />
However, this means that beside adding a method to my Model class I'd also have to add a reference to my Repository if I want to save the text of my EditText to the database and it feels like this is not the correct way to do it and that my ViewModel, which contains the LiveData<List<Model>> models should be responsible for communicating with my Repository to perform database operations.
I know that I could add the ViewModel as a <layout/> variable to my list item <layout/> and add a listItemTextChanged(Model model) method on my ViewModel that could then update the model in the database but that doesn't sound right either.
Could anyone point me in the right direction?
i dont use databinding however your quite right that only the view model should talk to the repository, but if your using live data then you need to observe it in your fragment /activity and that should update your recycler view
myViewModel = ViewModelProviders.of(getActivity()).get(MyViewModel.class);
myViewModel.getCards().observe(getViewLifecycleOwner(), new
Observer<List<Card>>() {
#Override
public void onChanged(#Nullable List<Card> cards) {
if (cards != null && cardAdapter != null){
cardAdapter.refreshList(cards);
}
}
}
});
so you could add a text watcher on the card and have that call a method in your view model to save it although that would probably update too much, maybe have a save button
I have an activity which contains fragment and this fragment have Recyclerview. For RecyclerView Adapter, I am using ViewModel with live data. When I add a record to room database recyclerView is updated but when i update or delete a record from recylerView, live data is not updated due to which recyclerVeiw adapter is not updated. To observer live data I used this code in the fragment
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
ViewModelProviders.of(this).get(OrderViewModel::class.jav
orderViewModel.getBrokerClientOrder(clientUid).observe(viewLifecycleOwner, androidx.lifecycle.Observer {
orderAdapter!!.refreshOrderAdapter(it)
})}
In Adapter to get an object of ViewModel
orderViewModel = ViewModelProviders.of(context as AppCompatActivity).get(OrderViewModel::class.java)
While in activity i used the above mechenism every thing work fine.
In your code you are observing model changes and passing it to recyclerView, but you need to observe recyclerView changes and pass it to your model.
I'm using a RecyclerView to display some data from a Firestore database. I'm using as an adapter, the FirestoreRecyclerAdapter for obvious reasons. I'm successfully displaying all 35 items in my RecyclerView. The problem is, I cannot scroll to a specific position. This is what I have tried:
recyclerView = locationsFragmentView.findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(context));
FirestoreRecyclerOptions<MyModelClass> options = new FirestoreRecyclerOptions.Builder<MyModelClass>().setQuery(query, MyModelClass.class).build();
adapter = new MyFirestoreRecyclerAdapter(options);
recyclerView.setAdapter(adapter);
recyclerView.setHasFixedSize(false);
recyclerView.getLayoutManager().scrollToPosition(10);
Everytime I open my app, I'm always positioned at the first position and not on the 10'th as I specified in the scrollToPosition() method.
I have also used:
recyclerView.scrollToPosition(10);
and
recyclerView.smoothScrollToPosition(10);
But without luck. How do I scroll to specific position? Thanks!
The recyclerView doesn't scroll because it's still empty when you call recyclerView.scrollToPosition(10);, you should move that code after the recyclerView gets populated from the Firebase response, probably inside a callback.
Instead of using recyclerView.scrollToPosition(10); try to use below code to set position in recyclerview
recyclerView.smoothScrollToPosition(10);
In my case, I didn't want move to a specific position, instead I just wanted to keep the original position after returning from a different Activity or returning from background.
I just added this empty listener and it's working.
listAdapter.snapshots.addChangeEventListener(object : ChangeEventListener {
override fun onChildChanged(
type: ChangeEventType,
snapshot: DocumentSnapshot,
newIndex: Int,
oldIndex: Int
) {
}
override fun onDataChanged() {
}
override fun onError(e: FirebaseFirestoreException) {
}
})
Here listAdapter is FirestoreRecyclerAdapter.
Note: There must have some negative consequences on doing this.
I solved my problem with a Simpler solution.
I just called adapter.startListening(); in onViewCreated() instead of onStart() and called adapter.stopListening(); in onDestroyView() instead of onStop()
That prevented the entire list from regenerating while coming back from the next activity and thus retained the scroll position where it was previously.
Source: https://github.com/firebase/FirebaseUI-Android/issues/998#issuecomment-342413662