Notifying adapter from model - android

I'm trying to make changes to my android application by adopting the MVP pattern, but I'm having trouble with where to notify the adapter the recyclerview is using.
What I'm currently doing is giving a reference to the adapter in my model and notifying it as changes are made when click events happen, like so:
public class MyModel {
private MyAdapter adapter;
...
public void setAdapter(MyAdapter adapter) { this.adapter = adapter; }
public void action() {
// make changes to model and notify adapter as changes are
// made to individual items
...
adapter.notifyItemChanged(position)
}
}
I'm wonder what the conventional way of handling this kind of behaviour is using the MVP pattern.

The Observer pattern might be what you are looking for. When changes are made to the model you can notify the Observers (The presenters) so they can then update the view.
http://en.wikipedia.org/wiki/Observer_pattern

Related

android realm listener for a specific model

I have two realm models, CallLogModel and MessageModel. I want to update a ListView whenever there is a change in my CallLogModel collection. I'm using the following code to do so,
private void setupRealmListener() {
realm = Realm.getDefaultInstance();
callLogs = realm.where(CallLogModel.class).findAllAsync();
callLogs.addChangeListener(results -> {
populateList();
});
}
The problem with this code is that, it calls the populateList() function when it detects a change in any of my models(both CallLogModel and MessageModel). How can I specifically set a listener for CallLogModel while ignoring all changes from other models?
Thanks.

Updating a RecyclerView by a new LiveData<List> return from Room dynamically

I have a conventional Room->DAO->Livedata->Repositiry->ViewModel->RecyclerView app. Different buttons of UI must pass different lists of data to RecyclerView.
By button click I want:
Make new #Query in DAO and get new LiveData<`List> object in return.
Put this new data into the RecyclerViewAdapter and call notifyDataSetChanged () to make new List visuals.
The Dao #Query:
#Query("SELECT * FROM entry_table WHERE path LIKE :path ORDER BY priority DESC")
LiveData<List<Entry>> getNotesOfFolder(String path); //Returns LiveData with List of Entries
The recyclerView is updated via onChanged of Observer like this:
public class RecyclerViewActivity extends AppCompatActivity {…
Observer<List<Entry>> entryObserver = new Observer<List<Entry>>() {
#Override
public void onChanged(List<Entry> entries) {
recyclerAdapter.setEntries(entries);
}
};
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.EntryHolder> {…
public void setEntries(List<Entry> entries) {
this.entries = entries; //setting LiveData content to adapter's List (i.e. entries)
notifyDataSetChanged();
The problem is that my Observer does not call the onChange method when LiveData receives new value from DAO. I believe it is because this LiveData’s content is not CHANGED but REPLACED by another LiveData.
I tried to re-subscribe the Observer to LiveData again and it somewhat worked, but when I try to call some conventional Room queries like #Delete, I got multiple (up to 10!) onChange calls and some of them behave weirdly and pass the wrong List to RVadapter.
So there two questions:
How can I just call onChanged() of my Observer?
Is there some other stylish way of passing new LiveData object to RecyclerView dynamically?
1)
In viewModel , create a getter method for live data:
//...
private LiveData<List<Entry>> liveData;
//...
public LiveData<List<Entry>> getLiveData() {
return liveData;
}
in Activity:
#Override
protected void onCreate(Bundle savedInstanceState) {
//...
viewModel.getLiveData().observe(this, new Observer<List<Entry>>() {
#Override
public void onChanged(List<Entry> entryList) {
//set new value here
}
});
}
2) DiffUtil is very helpful to update your list in recycler view and it gives you some nice animations.
I tried to re-subscribe the Observer to LiveData again and it somewhat worked, but when I try to call some conventional Room queries like #Delete, I got multiple (up to 10!) onChange calls and some of them behave weirdly and pass the wrong List to RVadapter.
This would make sense if you didn't first unsubscribe your observer from the old LiveData object... the one you replace when your query changes.
If your query updates, you will need to get a new LiveData from the DAO. If you overwrite your old LiveData with the new one, you will not only need to (re-)subscribe your Observer to the new one, you will also need to unsubscribe it from the old one. Otherwise it will live on and keep updating the observer.

Listening for button click and linking fragment with view model

Im trying to learn view models and implement them in my app. I have been following a tutorial on getting me started but, I have a couple questions.
How do i listen for a button click? Since all the business logic is suppose to be stored in the view model would I put an OnClick listener there? Or would i put it with my onChange method in the activity that launches the fragment?
How to tell the fragment to use the view model?
Update was looking at this guys tutorial Location of click event in MVVM architecture . Isn't the whole point of mvvm to eliminate the need of interfaces?
Update 2: Found where you can use data binding to shove OnClick listener into button here: Handle onClick event with Databinding and MVVM and Using DataBinding library for binding events
Live data observe code from activity launching fragment
//private BattleRhythmViewModel battleModel;
battleModel = ViewModelProviders.of(this).get(BattleRhythmViewModel.class);
battleModel.getEvents().observe(this, new Observer<ArrayList<Event>>() {
#Override
public void onChanged(#Nullable ArrayList<Event> events) {
// Add newly created events to array/recycler view
// Another one for pushing new platform/content to database
}
});
}
View model for fragment
public class BattleRhythmViewModel extends ViewModel {
private MutableLiveData<ArrayList<Event>> battleRhythmEvents;
private MutableLiveData<ArrayList<TableData>> battleRhythmExtra;
public LiveData<ArrayList<Event>> getEvents()
{
return battleRhythmEvents;
}
public LiveData<ArrayList<TableData>> getExtras()
{
return battleRhythmExtra;
}
}

How to properly update Android's RecyclerView using LiveData?

Bottom Line Question
If I'm using MutableLiveData<List<Article>>, is there a way to properly notify observers when the title/content of an Article has changed, a new Article has been added, and an Article has been removed?
It seems the notifications are only possible when an entirely new collection is set on the LiveData, which would seem to result in a really inefficient UI refresh.
Hypothetical Example
Suppose the following...
My LiveData class looks something like this:
public class ArticleViewModel extends ViewModel {
private final MutableLiveData<List<Article>> mArticles = new MutableLiveData<>();
}
I want to display the Articles in a list by using the RecyclerView. So any time my Fragment observes a change in the ArticleViewModel's LiveData it calls the following method on my custom RecyclerView.Adapter class:
public class ArticleRecyclerViewAdapater extends RecyclerView.Adapter<Article> {
private final ArrayList<Article> mValues = new ArrayList<>();
public void resetValues(Collection<Article> articles) {
mValues.clear();
mValues.addAll(articles);
notifyDataSetChanged();
}
}
Finally, my application will allow the user to add a new Article, delete an existing Article, and change an existing Article's name (which needs to be updated in the RecyclerView list). How can I do that properly?
Add/Remove Article
It seems the LiveData construct doesn't notify observers if you add/remove an item from the underlying Collection. It seems you'd have to call LiveData#setValue, perhaps the ArticleViewModel would need a method that looks something like this:
public void deleteArticle(int index) {
final List<Article> articles = mArticles.getValue();
articles.remove(index);
mArticles.setValue(articles);
}
Isn't that really inefficient because it would trigger a complete refresh in the RecyclerView.Adapter as opposed to just adding/removing a single row?
Change Name
It seems the LiveData construct doesn't notify observers if you change the contents of an item in the underlying collection. So if I wanted to change the title of an existing Article and have that reflected in the RecyclerView then my ArticleViewModel would have to modify the object and call LiveData#setValue with the entire collection.
Again, isn't this really inefficient because it would trigger a complete refresh in the RecyclerView.Adapter?
Case1:
When you add or delete
So when you add or delete the element in the list you don't change the refernce of list item so every time you modify the liveData item you have to update live data value by calling setValue method(if you are updating the item on main thread)or Post value(when you are updating the value on background thread)
The problem is that it is not efficient
Solution
Use diffutil
Case 2:When you are updating the item property by editing the item.
The Problem
LiveData will only alert its observers of a change if the top level value has changed. In the case of a complex object, though, that means only when the object reference has changed, but not when some property of the object has changed.
Solution
To observe the change in property you need PropertyAwareMutableLiveData
class PropertyAwareMutableLiveData<T: BaseObservable>: MutableLiveData<T>() {
private val callback = object: Observable.OnPropertyChangedCallback() {
override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
value = value
}
}
override fun setValue(value: T?) {
super.setValue(value)
value?.addOnPropertyChangedCallback(callback)
}
}
Two things to note here:
1.First is that our value is a generic type which implements the BaseObservable interface. This gives us access to the OnPropertyChangedCallback.
2.Next is that, whenever some property of the BaseObservable changes, we simply reset the top level value property to be its current value, thus alerting the observers of the LiveData that something has changed.
LiveData will only notify when its wrapped object reference is changed. When you assign a new List to a LiveData then it will notify because its wrapped object reference is changed but if add/remove items from a LiveData's List it will not notify because it still has the same List reference as wrapped object. So you can overcome this problem by making an extension of MutableLiveData as explained here in another stackoverflow question.
I know it's too late to answer.
But I hope it will help other developers searching for a resolution on a similar issue.
Take a look at LiveAdapter.
You just need to add the latest dependency in Gradle.
dependencies {
implementation 'com.github.RaviKoradiya:LiveAdapter:1.3.4'
// kapt 'com.android.databinding:compiler:GRADLE_PLUGIN_VERSION' // this line only for Kotlin projects
}
and bind adapter with your RecyclerView
// Kotlin sample
LiveAdapter(
data = liveListOfItems,
lifecycleOwner = this#MainActivity,
variable = BR.item )
.map<Header, ItemHeaderBinding>(R.layout.item_header) {
onBind{
}
onClick{
}
areContentsTheSame { old: Header, new: Header ->
return#areContentsTheSame old.text == new.text
}
areItemSame { old: Header, new: Header ->
return#areContentsTheSame old.text == new.text
}
}
.map<Point, ItemPointBinding>(R.layout.item_point) {
onBind{
}
onClick{
}
areContentsTheSame { old: Point, new: Point ->
return#areContentsTheSame old.id == new.id
}
areItemSame { old: Header, new: Header ->
return#areContentsTheSame old.text == new.text
}
}
.into(recyclerview)
That's it. Not need to write extra code for adapter implementation, observe LiveData and notify the adapter.

update listview dynamically with adapter

This tutorial uses a SimpleAdapter which works fine, but I need to update the arrays in the adapter when new data is entered.
Could you please guide me on how to update a ListView using something other than a SimpleAdapter?
Use a ArrayAdapter backed by an ArrayList. To change the data, just update the data in the list and call adapter.notifyDataSetChanged().
If you create your own adapter, there is one notable abstract function:
public void registerDataSetObserver(DataSetObserver observer) {
...
}
You can use the given observers to notify the system to update:
private ArrayList<DataSetObserver> observers = new ArrayList<DataSetObserver>();
public void registerDataSetObserver(DataSetObserver observer) {
observers.add(observer);
}
public void notifyDataSetChanged(){
for (DataSetObserver observer: observers) {
observer.onChanged();
}
}
Though aren't you glad there are things like the SimpleAdapter and ArrayAdapter and you don't have to do all that?
SimpleListAdapter's are primarily used for static data! If you want to handle dynamic data, you're better off working with an ArrayAdapter, ListAdapter or with a CursorAdapter if your data is coming in from the database.
Here's a useful tutorial in understanding binding data in a ListAdapter
As referenced in this SO question
Most people recommend using notifyDataSetChanged(), but I found this link pretty useful. In fact using clear and add you can accomplish the same goal using less memory footprint, and more responsibe app.
For example:
notesListAdapter.clear();
notes = new ArrayList<Note>();
notesListAdapter.add(todayNote);
if (birthdayNote != null) notesListAdapter.add(birthdayNote);
/* no need to refresh, let the adaptor do its job */
I created a method just for that. I use it any time I need to manually update a ListView. Hopefully this gives you an idea of how to implement your own
public static void UpdateListView(List<SomeObject> SomeObjects, ListView ListVw)
{
if(ListVw != null)
{
final YourAdapter adapter = (YourAdapter) ListVw.getAdapter();
//You'll have to create this method in your adapter class. It's a simple setter.
adapter.SetList(SomeObjects);
adapter.notifyDataSetChanged();
}
}
I'm using an adapter that inherites from BaseAdapter. Should work for any other type of adapter.

Categories

Resources