Change LiveData source by updating a variable - java

I want the LiveData source for a RecyclerView to change depending on which list you selected. And that if you've selected a source in this search.
At the moment I can't switch back and forth between the sources. So I can display items from my Room database, but I can't change the source if I've selected another list.
Example: If you selected List 2, the LiveData source will be changed and all items contained in that List 2 will be displayed. Now you should also be able to search for words in this list 2. How can you do this during the runtime of an app?
A part of my current Repository:
public LiveData<List<VocabularyEntity>> getVocabularies(int listNumber, String searchText) {
if (listNumber == 0) {
return listDao.getVocabularies(searchText);
} else {
return listDao.getVocabularyList(listNumber, searchText);
}
}
And a part of my current ViewModel:
public LiveData<List<ListEntity>> getLists() {
return repository.getLists(listNumber, searchText);
}

I do not see any setValue or getValue function that is being called on your LiveData actually.
In order to change the LiveData to interact with the live changes, you need to call the setValue in your LiveData object. Something like the following should fix your problem here I think.
// I am assuming you have this variable declared in your viewmodel
private LiveData<List<ListEntity>> vocabList;
public LiveData<List<ListEntity>> getLists() {
List<ListEntity> vocabListFromDB = repository.getLists(listNumber, searchText);
vocabList.setValue(vocabListFromDB);
return vocabList;
}
And you do not have to return the LiveData object from the repository function anymore.
public List<VocabularyEntity> getVocabularies(int listNumber, String searchText) {
if(listNumber == 0) {
return listDao.getVocabularies(searchText);
} else {
return listDao.getVocabularyList(listNumber, searchText);
}
}
I hope that helps!
I want to share my personal opinion on implementing this actually. I would rather have a ContentObserver instead of a LiveData setup. Implementation with a ContentObserver and a CursorLoader looks like an easier and robust solution in my humble opinion.

Related

What is the clean way to check a SELECT query result's length in MVVM architecture?

I am creating an App that will use a SearchView to let user make queries to filter data. I am using RoomDB, and trying to follow Model-View-ViewModel architecture as recommended in Android Developers' Guidelines.
I have one entity and one DAO (for now my DB has only one table). I have a method in the DAO that looks like this:
#Query("SELECT * FROM table WHERE column1 = :search OR column2 = :search")
LiveData<List<TableRow>> filteredSearch(String search);
So, from an AsyncTask I can use the RoomDatabase's instance, right, and obtain the results of an user's query, like this, right?
// Let's assume search already contains user's input
String search;
// DatabaseClient is a singleton that holds MyRoomDatabase instance
// I am using it as Repository for now... bad call?
LiveData<List<TableRow>> user_query = DatabaseClient
.getInstance(getApplicationContext())
// my DatabaseClient has this method that
// I made to call DAO's query method
.getFilteredList(search);
So, I want to load one fragment or another in my Main Activity depending on this user query's length (if 0 results, fragmentA else fragmentB). This is business logic to some extent? I wonder... should I read the query's length from the Viemodel, or from the View (AKA Activity)? As you can see, I am still struggling with RoomDB and ViewModels at all.
My plan was making a method in the ViewModel that returns the LiveData<List<TableRow>> with the query results by using a code snippet similar to the one above, and then, from the MainActivity:
search_view.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
#Override
public boolean onQueryTextSubmit(String query) {
// Should I delegate AsyncTask to the Repository AKA
// DatabaseClient? Maybe... but please bear with me
class UserSearchTask extends AsyncTask<Void, Void, List<TableRows>> {
#Override
protected List<TableRows> doInBackground(Void... voids) {
TableRowsViewModel my_viewmodel = new TableRowsViewModel(getApplication());
LiveData<List<TableRows>> search_results;
search_results = my_viewmodel.getUserSearch(query);
// TODO I will care about type mismatches later
return search_results;
}
#Override
protected void onPostExecute(List<TableRows> found_elements) {
super.onPostExecute(found_elements);
// TODO So, I want to check user's search results from here
if ( found_elements.length > 0) {
showFragmentB();
} else {
showFragmentA();
}
}
}
}
I have been without coding for more than half year, and I am a little rusty. I forgot concepts that I knew before about ViewModels, LiveData and stuff. Am I breaking MVVM's architecture with my approach? What's the role of LiveData in this kind of logic attempt of mine? Can I read the SQL query result's length directly from some LiveData's method, or else, I should retrieve the list from it to do so?
I guess the actual question is: is my approach wrong? What would be the cleanest way to implement my fragment's logic depending on user search's length?
EDIT: I am not asking only about good practices (which are still welcome); I have barely dedicated 7 hours to this app yet and I couldn't still build a first alpha version to start testing it. My first priority in short-term is putting this thing together (and myself together I might add); in other words: first, I want to make it work even if it is not clean. Right now I am a simple-minded monkey which just thinks about this like if this was a normal PC app in which I don't have to struggle with App lifecycles, multithreading and all related stuff,in which I just retrieve the SQL query's result right away. I beg your pardon for my ignorance.
So, in order to add more context about what I am trying to do: If searxh results are zero, fragmentA would be a form for adding a new row to the table; fragmentB would show just the data of the first row, not listing yet (I will reach there eventually, but not yet).
Here is the way I ended up implementing the logic I had in mind when I made the question. But that doesn't mean this is the clean way to do it.
In order to get the size of the User query, I ended up using an Observer (as Teo said in his comment). I am not sure if using Observer and LiveData for a Database that is merely local in the phone's app (and therefore shall only be modified by the App's user himself) for obtaining query results each time the user hits "Search" button, I am not sure if using Oberser and LiveData for this is overkill or not... and the aberration of using DatabaseClient (the RoomDatabase's singleton) as a Repository? Not anymore... I have created a dedicated Repository Class to handle the DatabaseClient and the DAOs.
That said, the relevant part of my Repository class:
public class Repository {
private final TableRowDao tablerow_dao;
public Repository(Application application) {
AppDatabase app_db = DatabaseClient.getInstance(application).getAppDatabase();
tablerow_dao = app_db.tableRowDao();
}
public LiveData<List<TableRow>> getFilteredList(String search) {
return tablerow_dao.filteredSearch(search);
}
// [...]
...here, the ViewModel:
public class TableRowsViewModel extends AndroidViewModel {
private Repository repository;
public TableRowsViewModel(#NonNull Application application) {
super(application);
repository = new Repository(application);
}
public LiveData<List<TableRow>> getUserSearch(String search) {
return repository.getFilteredList(search);
}
No AsyncTasks were used for this purpose.
In MainActivity, within OnCreate method:
search_view.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
#Override
public boolean onQueryTextSubmit(String query) {
my_viewmodel = new TableRowsViewModel(getApplication());
search_results = my_viewmodel.getUserSearch(query);
observeSearchResults(search_results);
return true;
}
// [...]
});
ObserveSearchResults is a private method that I declared in MainActivity as well:
// Having a observer is good and stuff, but am I overdoing it?
private void observeSearchResults(LiveData<List<TableRow>> search_results) {
search_results.observe(this, new Observer<List<TableRow>>() {
#Override
public void onChanged(List<TableRow> rows) {
if ( rows.size() > 0 ) {
// TODO I don't list the results yet, I show the first one right away
profileFragment = ProfileFragment.newInstance(rows.get(0));
transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragmentContainer, profileFragment);
transaction.addToBackStack(null);
transaction.commit();
} else {
transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragmentContainer, insertFragment);
transaction.addToBackStack(null);
transaction.commit();
}
}
});
}
This worked for me, but that doesn't mean that I am doing this in a clean way at all.

I'm I affecting the values? or the references? Removing multiple Observers without a LifeCycleOwner

I want to be able to connect different LiveData<X> to the same Observer.
Up to now, my little module has been working extremely fine as hell, but to avoid linking the ViewModel to a LifecycleOwner, I included a way for the module to use the .observeForever() function if owner is null,
The observers are wrapped inside a bigger one that stores an int value that declares if the onChange() on each of the LiveData<>'s is the first one or not... this was done because in some cases I needed to ignore the initial onChange() callback.
Because there may be many observers (Keeping track of this int value)... depending on the amount of LiveData<X> ("sources" as the docs call them), it was easy for me to clear all the observers with the liveData.removeObservers(owner); function, automatically clearing all the observers at that specific owner.
But because the owner is null, now I need to keep a reference to all the observers in a single one, and remove THEM by name liveData.removeObserver(observer);
My first concern is that by declaring new on each iteration, I'm losing the reference to that observer forever.
If that's the case I can remove the Observer inside the wrapper, which is, as intended, the same among all observers, BUT the first thing that comes intuitively to mind, is that the obvious thing to do is not to destroy the most inner observer, but the outer one, because that destroys the inner one as well.
The problem is that the outer ones are different, while the inner one is the one that is common, so:
Which one should I remove, and How should they be declared?
private RunTimeObserverWrapper<? super T> runTimeObserverWrapper;
/*This is the recent change I made in the hopes of being able to remove it/them */
private void connectObservers(
List<LiveData<T>> liveDatas,
boolean ignoreInitialization
) {
this.listLiveData = liveDatas;
Observer<? super T> itemObserver = itemObserverInitializer();
/*This is the common observer*/
for (LiveData<T> liveData: listLiveData
) {
if (!liveData.hasObservers()) {
runTimeObserverWrapper = new RunTimeObserverWrapper<>(ignoreInitialization, itemObserver);
/*This is the recent change I made placing it as a field variable*/
// RunTimeObserverWrapper<? super T> runTimeObserverWrapper = new RunTimeObserverWrapper<>(ignoreInitialization, itemObserver);
/*This was working as intended, but I want to disassociate the module from the LifeCycleOwner*/
if (owner != null) {
liveData.observe(
owner,
runTimeObserverWrapper
);
} else {
liveData.observeForever(
runTimeObserverWrapper
);
}
/*New Snippet*/
// liveData.observe(
// owner,
// runTimeObserverWrapper
// );
/*Old snippet*/
}
}
}
public void destroyObserversAndList() {
for (LiveData<T> liveData: listLiveData
) {
// liveData.removeObservers(owner);
/*This was correctly removing ALL Observers*/
if (owner != null) {
liveData.removeObservers(owner);
} else {
liveData.removeObserver(runTimeObserverWrapper);
}
/*This is the new snippet to account for a lack of LifeCycleOwner*/
}
listLiveData.clear();
}
So, as you can see, my concern is that by calling liveData.removeObserver(runTimeObserverWrapper);, I''ll only be removing the last observer defined by new inside the iteration.
What should I do?
The solution was a lot harder than I first realized.
Primarily, because the answer was found by solving a secondary unrelated problem: the Database would at times return an empty value for the table in which all the rows in it were erased, and my list differ module, didnt knew what type of list it should erase.
The solution to this problem helped solve 2 problems at once, maybe a 3rd one, and one of them was the problem mentioned above.
The solution was to wrap both: the LiveData with its own Observer, now both should be given an Id, so that its answer could be stored with its Id in a LinkedHashMap.
If the answer was empty, it would just store the empty list with its corresponding Id, erasing the previous value.
Now that the LiveData was bundled with its own Observer, I could make a method that could release it from its own Observer, and, by iterating throught each LiveDataWrapper, I could call this method and remove them.
public void destroyObserversAndList() {
if (listLiveData != null) {
for (LiveDataSourceWrapper<T> liveData: listLiveData
) {
liveData.removeObserver();
}
listLiveData.clear();
}
}
And the LiveDataSourceWrapper class:
public class LiveDataSourceWrapper<X> {
private int liveDataId;
private SourceObserverWrapper<X> identityLiveDataObserver;
private LiveData<X> liveData;
public LiveDataSourceWrapper(
LiveData<X> liveData,
int liveDataId
) {
this.liveData = liveData;
this.liveDataId = liveDataId;
}
public void observeForever(#NonNull SourceObserver<X> observer) {
identityLiveDataObserver = new SourceObserverWrapper<X>(liveDataId, observer);
liveData.observeForever(identityLiveDataObserver);
}
public void removeObserver() {
liveData.removeObserver(identityLiveDataObserver);
}
public boolean hasObservers() {
return liveData.hasObservers();
}
}

Can you use room and not use a LiveData object?

My usage currently is to store 5 specific button's info into a database (room) to persist it across reboots. My current usage doesn't rely on changes of the data because the only one changing the data is the user upon long press of the button (then i update the database). Hence I do not need a LiveData variable, and this is making it difficult for me to initialize my ViewModel.
Essentially, since the LiveData objects only update on change, my data never gets initialized.
Thus the app always will cause a null-pointer on startup.
I'll share a gist of my setup so far bellow. I'm hoping there is some way to make this work where I don't have to observe any LiveData object, and I can just grab data when I instantiate the Model.
Entity:
#Entity(tableName = "myEntity")
public class MyEntity {
#PrimaryKey
public int buttonID;
// other fields...
}
DAO:
#Dao
interface MyDAO {
#Query("Select * from myDB")
LiveData<List<MyEntity>> getEntityList();
// I think this needs to change to just List<MyEntity>?
// also insert and update here...
}
Repository:
class MyRepository {
private MyDAO myDAO;
private LiveData<List<MyEntity>> allEntities;
MyRepository(Application application) {
MyDatabase db = MyDatabase.getInstance(application);
myDAO = db.myDAO();
allEntities = myDAO.getAllEntities();
}
LiveData<List<MyEntity>> getAllEntities() { return allEntities; }
// Update entity...
}
ViewModel:
public class ViewModel extends AndroidViewModel {
private MyRepository repository;
private List<MyEntity> tempList;
private HashMap<MyEntity> allEntities;
public ViewModel (Application application) {
super(application);
repository = new MyRepository(application);
Observer<List<MyEntity>> observer = data -> tempList = data;
ObserveOnce(repository.getAllEntities(), observer); // ObserveOnce implementation found in this answer: https://stackoverflow.com/a/59845763/10013384
allEntities = new HashMap<>();
for (int i = 0; i < tempList.size(); i++) { // Nullpointer here, as tempList doesn't have any items yet.
allEntities.put(tempList.get(i).buttonID, templist.get(i));
}
}
// getter and update methods...
}
Activity:
// ...
protected void onCreate(Bundle savedInstanceState) {
// ...
viewModel = new ViewModelProvider(this).get(ViewModel.class);
// Initialize UI views with data from ViewModel
}
Then in the respective listeners:
#Override
public boolean onLongClick(View v) {
int index = (Integer) v.getTag();
data.get(index).foo = fooNewUIData;
ButtonArray[index].setText(fooNewUIData);
ViewModel.update(data.get(index)); // if updated, update the ViewModel and the database
}
since the LiveData objects only update on change, my data never gets initialized
That is not your problem. Your problem is that you think that ObserveOnce() is a blocking call, and that the results will be ready immediately when it returns. In reality, LiveData from Room does work on a background thread. You need to react to when the data is available in your Observer, not assume that it will be available in the next statement.
OfCourse you can, you can simply return the normal object class. LiveData needs to be used only if you want observe the changes to those rows.
You can also use to kotlin flows to still listen to the changes and not use LiveData
Without LiveData:
List<MyEntity> getAllEntities();
With Kotlin Flows:
fun getAllEntities(): Flow<List<MyEntity>>
Hope this helps !!
Yes, of course you can. Just change the return type of the specific function in your DAO from LiveData<MyDataClass> to MyDataClass.
See this Codelab for further tutorial about Room.
Okay, thanks all to provided answers and feedback, I appreciate it!
Ultimately I agree with #CommonsWare that the easiest way to handle this is probably not to have the ViewModel consume the LiveData. I'm opting to setup an Observer as normal in my Activity, and convert it into the format that I want (ArrayList) when I save that data to my Activity class.
The issue I was originally concerned about with data not being sorted to my liking could otherwise be solved with a simple sort:
Collections.sort(this.data, (o1, o2) -> Integer.compare(o1.buttonID, o2.buttonID));
As for the async issue, I'm just falling back to allowing the observer's callback update the data as it gets it (and update the UI accordingly). If this situation involves a lot more data, then perhaps I'll need some sort of a splash screen while the app loads data. But luckily I don't have to do any of that quite yet.
Activity:
// onCreate() {
ViewModel = new ViewModelProvider(this).get(ViewModel.class);
Observer<List<MyEntity>> observer = this::setData;
observeOnce(ViewModel.getAllEntities(), observer);
if (data.size() > 0) {
// Initialize UI views
}
// }
//...
public void setData(List<MyEntity> entities) {
this.data = entities
Collections.sort(this.data, (o1, o2) -> Integer.compare(o1.buttonID, o2.buttonID));
// also update UI if need-be
}
The only thing left is to solve the issue of my Database not persisting across reboots, but that is out of the scope of this question.

Getting LiveData values to use with other LiveData values

(New to android programming)
I have a RecyclerView of subcategories and their corresponding keywords.
like this:
https://imgur.com/a/bEFS6cm
The subcategories are fetched by observing
subcategoryViewModel.getAllSubcategoriesForCategory(id).observe...
(I am using Room), here I have the id available (it is known which category is chosen at the time of creation of the subcategory fragment).
However, I am having troubles calculating the corresponding keywords. The keywords are the names of the articles contained in each subcategory.
In my ArticleDao I have a function
LiveData<List<String>> getAllArticleNamesById(int subId);
So logically, I just have to get the current subcategory list and get their corresponding keywords in a for loop.
But how do I do that if both the subcategory list and the keywords list are LiveData and I cannot access their values, only their observers can?
I tried putting an observer within an observer but I don't think that's the best idea.
subcategoryViewModel.getAllSubcategoriesForCategory(id).observe(getViewLifecycleOwner(),
new Observer<List<Subcategory>>() {
#Override
public void onChanged(List<Subcategory> subcategories) {
recyclerAdapter.setSubcategories(subcategories);
for (Subcategory sub : subcategories) {
articleViewModel.getAllArticleNamesById(sub.getId()).observe(getViewLifecycleOwner(),
new Observer<List<String>>() {
#Override
public void onChanged(List<String> strings) {
recyclerAdapter.addToKeywordsList(keywordsIntoString(strings));
}
});
}
});
I found some information on LiveData Transformations (map, switchmap) but that doesn't really apply to my problem since its supposed to apply a function on LiveData when otherLiveData changes. (at least from what I understand). I just need to access the current subcategory list and work with the values so I can observe the article names.
Ideally, I would need something like this:
for (Subcategory sub : subcategoryList) {
articleViewModel.getAllArticleNamesById(sub.getId()).observe...
}
So my question is, how do I access the subcategories which I'm already observing? Am I missing something?

ISIS: Moving from deprecated #Action(invokeOn=...) to #Action(associateWith=...)

I am working on a project using ISIS 1.16.2. I have a superclass, called ConfigurationItem, which has some common properties (name, createdTimestamp etc.).
For example it has a delete action method, annotated with #Action(invokeOn = InvokeOn.OBJECT_AND_COLLECTION, ...), which I need to be callable from entitys detail view as well as from collection views with selection boxes.
Example:
public class ConfigurationItem {
#Action(
invokeOn = InvokeOn.OBJECT_AND_COLLECTION,
semantics = SemanticsOf.NON_IDEMPOTENT_ARE_YOU_SURE,
domainEvent = DeletedDomainEvent.class)
public Object delete() {
repositoryService.remove(this);
return null;
}
// ...
}
public class ConfigurationItems {
#Action(semantics = SemanticsOf.SAFE)
public List<T> listAll() {
return repositoryService.allInstances(<item-subclass>.class);
}
// ...
}
This works pretty well but the "invokeOn" annotation is now deprecated. The JavaDoc says that one should switch to #Action(associateWith="...") but I don't know how to transfer the semantics of 'InvokeOn' since I have no collection field for reference.
Instead I only have the collection of objects returned by the database retrieve action.
My question is: How do I transfer the deprecated #Action(invokeOn=...) semantics to the new #Action(associateWith="...") concept for collection return values with no backed property field?
Thanks in advance!
Good question, this obviously isn't explained well enough in the Apache Isis documentation.
The #Action(invokeOn=InvokeOn.OBJECT_AND_COLLECTION) has always been a bit of a kludge, because it involves invoking an action against a standalone collection (which is to say, the list of object returned from a previous query). We don't like this because there is no "single" object to invoke the action on.
When we implemented that feature, the support for view models was nowhere near as comprehensive as it now is. So, our recommendation now is, rather than returning a bare standalone collection, instead wrap it in a view model which holds the collection.
The view model then gives us a single target to invoke some behaviour on; the idea being that it is the responsibility of the view model to iterate over all selected items and invoke an action on them.
With your code, we can introduce SomeConfigItems as the view model:
#XmlRootElement("configItems")
public class SomeConfigItems {
#lombok.Getter #lombok.Setter
private List<ConfigurationItem> items = new ArrayList<>();
#Action(
associateWith = "items", // associates with the items collection
semantics = SemanticsOf.NON_IDEMPOTENT_ARE_YOU_SURE,
domainEvent = DeletedDomainEvent.class)
public SomeConfigItems delete(List<ConfigurationItem> items) {
for(ConfigurationItem item: items) {
repositoryService.remove(item);
}
return this;
}
// optionally, select all items for deletion by default
public List<ConfigurationItem> default0Delete() { return getItems(); }
// I don't *think* that a choices method is required, but if present then
// is the potential list of items for the argument
//public List<ConfigurationItem> choices0Delete() { return getItems(); }
}
and then change the ConfigurationItems action to return this view model:
public class ConfigurationItems {
#Action(semantics = SemanticsOf.SAFE)
public SelectedItems listAll() {
List<T> items = repositoryService.allInstances(<item-subclass>.class);
return new SelectedItems(items);
}
}
Now that you have a view model to represent the output, you'll probably find other things you can do with it.
Hope that makes sense!

Categories