Passing value to LiveData in Android - android

I'm new in development and I'm aware that stack isn't for 'full code requests'. But I'm stuck and can't find the solution.
I'm using Room database which has two columns - firstName and lastName. I'm loading the database with passed firstName parameter:
#Query("SELECT * FROM passenger WHERE firstName LIKE :firstName")
List<Passenger>getAllByName (String firstName);
It works as supposed.
But.. When I want to update Passenger, I need to populate data again, again, and again. There comes LiveData and observer.
But.. setValue in LiveData is private and I cannot send any parameters for Query line. There comes MutableLiveData, but how can I implement that?

#Eduardas Seems like you need to return LiveData instead:
LiveData<List<Passenger>> getAllByName (String name);
And you can write a transformation in the ViewModel or you can directly observe it from your Activity/Fragment.
In activity/fragment onCreate() or onResume():
YourDao.getAllByName(name).observe(this, new LiveData<List<Passenger>>(){
#Override
public void onChanged( #Nullable List<Passenger>) {
// update your adapter if the list isn't null
}
});
Something similar to above. You can add customization as per your use case.

Related

How to update a row in room? (please read first)

So I observe my room db in recyclerview via viewmodel. To update a row I call update method on repository object in activity. Am I doing it right? Because this is what I have seen in tutorials. My question is if we are using repository object for update, create and viewmodel to read data, whats the use case for setdata and postdata methods of livedata? Also how to update an entire table(overwrite)?
Observing
noteViewModel.getAllNotes().observe(this, Observer<List<Note>> { notes ->
notes?.let {
notesList = notes as ArrayList<Note>
notesAdapter = NotesAdapter(notes, this#MainActivity)
recyclerView.adapter = notesAdapter
notesAdapter!!.notifyDataSetChanged()
}
})
Updating
NoteDatabase.getInstance(this#MainActivity).noteDao().updateNote(
notechecked.also { it.done = value }
Your code is fine. This is exactly how your'e supposed to observe and update rows in Room.
The use case for setData and postData methods of LiveData are.. well.. to update the LiveData object.
For example, Room uses postData() to update the LiveData object it initially returned (when you called getAllNotes()), whenever you update your DB in a way that affects your LiveData object.
Because Room calls postData() for you, you don't need to.
But, if you were to hold some value directly in your viewmodel (rather than in Room), inside a LiveData object, and you'd want to change it when the user clicks something in your view, you'd have to call setData/postData yourself.

Android ViewModel with Fragments causing the same data to be populated in different fragments

I recently switched to Android MVVM on one of my Projects, the problem that I am facing is that with my Fragments used with ViewPager and TabLayout the data for each tab must be different based on an id of each tab, however since I am using AndroidViewModel to connect to my data source, the same data is shown in all my tab fargments. I understand that problem is that same ViewModel is shared between all the dynamic fragments[Fragmnet Class being the same].
Is there any way around this? or if I am doing something wrong.
//Code that returns data
private MutableLiveData<List<InventoryProduct>> inventoryProductList;
//we will call this method to get the data
public LiveData<List<InventoryProduct>> getCategoriesList(String cat_id,String store_id) {
//if the list is null
if (inventoryProductList == null) {
inventoryProductList = new MutableLiveData<>();
//we will load it asynchronously from server in this method
loadInventoryProducts(cat_id,store_id);
}
//finally we will return the list
return inventoryProductList;
}
There is nothing wrong with having the same ViewModel for multiple fragments, in fact, it helps in a lot of ways. In your case, I would suggest that keep some identifier in the fragment, which you can pass to the ViewModel's function and accordingly decide what data to provide. This way different fragments would have different data and your data would still be persistent as long as the LifeCycleOwner is alive.
Following from the edited question, you will need to remove the null check, as the same instance of ViewModel is being used, once initialized, inventoryProductList is never null again and hence the subsequent functions are getting the data of the first fragment. As a solution(If you don't want to go the DB way), you can maintain a Map of the LiveData like this
Map<CatId/StoreId,LiveData<List<InventoryProduct>>> dataMap=new HashMap();
Now Instead of null check, you check the map for your CatId/StoryID (based on what key you have already used) and if the Map does not have the value already, go for the API call, otherwise return the value from the map.
Something like this
Say you had used StoreID as the Key
if(!dataMap.containsKey(store_id)){
MutableLiveData<List<InventoryProduct>> inventoryProductList = new MutableLiveData<>();
//we will load it asynchronously from server in this method
loadInventoryProducts(cat_id,store_id);
dataMap.put(store_id,inventoryProductList);
//You need to post the response from the api call in this inventoryProductList
}
return dataMap.get(store_id);
Make sure that once you get the API response for the corresponding cat_id/store_id, you actually post the data to the corresponding LiveData.

Modify LiveData item fetched from Room

I have simple RecyclerView, which I fill with LiveData.
Activity:
viewModel.getBooks().observe(this, books -> booksAdapter.setBooks(books));
ViewModel:
public LiveData<List<Book>> getBooks() {
books = booksRepository.getBooks();
return books;
}
BooksRepository just calls this DAO's method:
#Query("SELECT * FROM books")
LiveData<List<Book>> getBooks();
Everything works as expected. However, in RecyclerView, I have LIKE button for every item, and I want to create onClick listener, which changes book's flag FAVOURITE accordingly. However, Room does not support that with LiveData, and it does not support MutableLiveData as well.
One (not good) solution would be to send Application to adapter, where I would create repository and update Book entity "manually". But I know it is not a good idea to send Application to adapter.
Any idea how to make this? I just need to set one simple boolean column in Book entity. Thanks.
Updating a value within a livedata set will not update the underlying source. I would recommend updating your DAO to where when the user clicks the LIKE button, it updates the DB..which then in turn post an update to your livedata and the changes would be reflected on the view.
eg:
#Query("UPDATE books SET FAVOURITE = :value WHERE books.bookId = :bookId")
public abstract void setBookFavourited(boolean value, int bookId);
Also to note, you shouldnt need to pass Application to the adapter. Setup an interface within the adapter that tells the activity that a user pressed the favorite button.
In adapter:
public interface BookFavoriteListener {
void onBookFavorited(Book book);
}
Implement the interface in your activity, then pass 'this' into the constructor of the adapter to set the listener.

RealmResult as RealmObject field

I'm trying to figure out the best way to set up a RealmObject with a RealmResult as one of its fields.
For example, let's say I have two RealmObjects, Goal and Achievement. The Goal object contains fields that define a query of Achievement's the user wants to track (e.g. date range the achievement was created, type of achievement, etc) and has custom methods to extract statistics from those Achievements.
What is the best way for Goal to contain this RealmResult of Achievements? Here are some ways I've thought of doing this:
Have a persisted RealmList field in Goal and update it anytime a field is changed that would change the resulting query. But how would this RealmList get updated if a new Achievement gets added to the realm?
Use #Ignore annotation on a RealmResult<Achievement> field within Goal. Anywhere in Goal where mResult is used, first check if null and requery if needed. This seems like I will be doing a lot of unneccessary querying if I'm using something like a RecyclerView that refetches the object in getItem().
Have a wrapper class that contains a Goal object and the RealmResult<Achievement> as fields. Add a listener to Goal so that anytime a relevant field changes the RealmResult can be requeried.
I'm leaning towards the last one as the cleanest way to keep a valid RealmResult. Am I missing an easier way to accomplish this?
Okay so I'm trying to implement a wrapper class (which I think is similar to the DAO abstraction #EpicPandaForce was mentioning, but I'm not super familiar with that)
public class GoalWrapper {
private RealmResults<Achievements> mResults;
private Goal mGoal;
private Realm mRealm;
public GoalWrapper(Realm realm, Goal goal) {
mRealm = realm;
mGoal = goal;
// TODO: does this need to be removed somewhere? What happens when GoalWrapper gets GC'd?
goal.addChangeListener(new RealmChangeListener<RealmModel>() {
#Override
public void onChange(RealmModel element) {
// rerun the query
findResultForGoal();
}
});
findResultForGoal();
}
/**
* Run a query for the given goal and calculate the result
*/
private void findResultForGoal() {
mResults = mRealm.where(Achievement.class)
.greaterThanOrEqualTo("date", mGoal.getStartDate())
.lessThanOrEqualTo("date", mGoal.getEndDate())
.equalTo("type", mGoal.getAchievementType())
.findAll();
calculateStats();
}
private void calculateStats() {
// Get relevant stats from mResult...
}
}
I haven't tested this code yet but I plan to have a RecyclerView.Adapter with an ArrayList of GoalWrapper objects.
My one concern is that I never remove the listener on mGoal. Do I even need to remove it? What happens in the case that the ArrayList gets GC'ed? I would think that the Goal field and resulting listeners attached to it all get GC'ed as well.

Check if data exist when using onChildEventListener

Is it possible to check if child data exists over a Firebase DataReference when using observeChildEvent? I'm using onChildEventListener to fill a RecyclerView on my application but during the data change, I show a ProgressBar. The problem appears when there's no data in the current query, so I never receive an event and I can't hide my progressBar.
Is there any way to achieve this without launch a previous observeSingleValueEvent with a limit(1) value to check in the dataSnapshot if there is available data?
I'm using RxFirebase2 to work with Firebase, so I did a method to check if a DatabaseReference have childrens, using it together with RxJava to avoid use OnChildEvents when my reference have no childrens:
public Single<Boolean> checkIfRefHaveAvailableChild(DatabaseReference databaseReference){
final Query query = databaseReference.limitToFirst(MIN_RETRIEVE_DATA);
return RxFirebaseDatabase.observeSingleValueEvent(query)
.subscribeOn(Schedulers.io())
.take(1)
.map(dataSnapshot -> dataSnapshot.hasChildren())
.single(false);
}

Categories

Resources