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.
Related
I'm trying to figure out how to sort my recyclerview's data by click of a button. So far I've got it sorted initially as I start the application and add new items on the list. But I'd like to be able to sort it by different parameters as I'm using the app, by clicking a button "Sort by distance".
I'm using the MVVM architecture and am working with LiveData.
Here's my MainActivity (Not completely, but irrelevant parts are omitted)
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RecyclerView recyclerView = findViewById(R.id.recyclerview);
final RunListAdapter adapter = new RunListAdapter(new RunListAdapter.RunDiff());
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
mRunViewModel = new ViewModelProvider(this).get(RunViewModel.class);
mRunViewModel.getAllRuns().observe(this, runs -> {
adapter.submitList(runs);
});
sortByDistanceButton = findViewById(R.id.sortByDistance);
sortByDistanceButton.setOnClickListener(view -> {
//DO SOMETHING
});
Here's my RunViewModel
public class RunViewModel extends AndroidViewModel {
private RunRepository mRepository;
private final LiveData<List<Run>> mAllRuns;
public RunViewModel(Application application) {
super(application);
mRepository = new RunRepository(application);
mAllRuns = mRepository.getAllRuns();
}
LiveData<List<Run>> getAllRuns() {
return mRepository.getAllRuns();
}
public void insert(Run run) {
mRepository.insert(run);
}
public void delete(Run run) {
mRepository.delete(run);
}
}
Here's my RunRepository
public class RunRepository {
private RunDao mRunDao;
private LiveData<List<Run>> mAllRuns;
RunRepository(Application application) {
RunRoomDatabase db = RunRoomDatabase.getDatabase(application);
mRunDao = db.runDao();
mAllRuns = mRunDao.getRunsByDate();
}
LiveData<List<Run>> getAllRuns() {
return mRunDao.getRunsByDate();
}
void insert(Run run) {
RunRoomDatabase.databaseWriteExecutor.execute(() ->
mRunDao.insert(run));
}
public void delete(Run run) {
RunRoomDatabase.databaseWriteExecutor.execute(() -> {
mRunDao.deleteRun(run);
});
}
}
And finally here's my RunDao
public interface RunDao {
#Insert(onConflict = OnConflictStrategy.IGNORE)
void insert(Run run);
#Query("DELETE FROM run_table")
void deleteAll();
#Query("SELECT * FROM run_table ORDER BY date")
LiveData<List<Run>> getRunsByDate();
#Query("SELECT * FROM run_table ORDER BY distance ASC")
LiveData<List<Run>> getRunsByDistance();
#Query("SELECT * FROM run_table ORDER BY duration ASC")
LiveData<List<Run>> getRunsByDuration();
#Delete
void deleteRun(Run run);
}
I've tried to pass on a "choice" argument and setting the choice to a different one, but to no avail. I've tried to read about other's having similar problems, but I haven't been able to figure how to apply any of them to my situation. I'm completely stumped.
Here are some SO questions I've read and tried to wrap my head around.
This one actually is very similar to mine and the original question asked figured out a solution, but never shared the details of the implementation =)
How to sort LiveData from Room Database? Doing a button to switch recyclerview's item's order
https://www.reddit.com/r/androiddev/comments/n9nraa/i_want_to_sort_a_livedatalistplayers_from_room_in/
how to properly sort a list using repository pattern and live data in an MVVM android application?
Moving from top to bottom,
You are observing mRunViewModel.getAllRuns()
Your mRunViewModel.getAllRuns() is mRepository.getAllRuns();
Your mRepository.getAllRuns(); is mRunDao.getRunsByDate();
So you are so far just unconditionally routing LiveData from Room to your view.
Simplest way to achieve switching could be this:
Change your RunDao to have single parameterized method like this example
#Query("SELECT * FROM Persons ORDER BY
CASE WHEN :isAsc = 1 THEN first_name END ASC,
CASE WHEN :isAsc = 0 THEN first_name END DESC")
List<Person> getPersonsAlphabetically(boolean isAsc);
(your implementation will be different, including return type and parameter type, but the above should give the idea)
Route the above dao method into repo and use updated repo inside vm. Every time view decides it's time to show some different sorting order, it will talk to view model, viewmodel will pull repo's method and repo will pull dao with proper parameter, dao will give you back livedata with properly sorted content.
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 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.
The root problem: I want to set the id of [Entity A] in foreign key of [Entity B] but id of [Entity A] is not available until inserted in the database (because it is autogenerated by the DBMS).
Using architecture components (Room, ViewModel and LiveData), how can I perform a transaction that saves multiple related entities in the database? The following code currently resides in the ViewModel and works fine. The problem is I want to put this AsyncTask in the repository layer like other simple one-operation queries, but is it OK? Because in that case the repository would be responsible for managing relationships and knowing about entity details.
As I said above, the main problem is that I need id of the inserted entity so I can save it in another entity. If this requirement didn't exist, I would be able to persist each entity one by one in separate AsyncTasks in the repository.
MainViewModel.java:
public void buy(Item item, Store store) {
new AsyncTask<Void, Void, Void>() {
#Override
protected Void doInBackground(Void... voids) {
long storeId = mRepository.insertStore(store);
Purchase purchase = new Purchase(storeId); // here uses id of the store
long purchaseId = mRepository.insertPurchase(purchase);
item.setPurchaseId(purchaseId); // here uses id of the purchase
mRepository.updateItem(item);
return null;
}
}.execute();
}
I think what you're doing is fine if you keep this in the Repository layer. I don't think keeping this in the ViewModel is a good idea as it's suppose to be the Repository's responsibility to handle your data, in this case, the Item and Store objects. I believe that your Repository should be responsible for the management of this data and its relationships. To answer your question about receiving the ID of the updated entity, what you can do is have your AsyncTask implement the onPostExecute method and have your doInBackground method return an actual value (like the storeId) instead of null. You can then have onPostExecute retrieve that value and delegate control to a callback listener of some sort.
You can execute multiple database operations in a transaction using Android Room.
This way, you are ensured that your database integrity is not altered in case one of those operation fails (operations are rolled-back).
Here is how you can define a Transaction with Room in the Dao class:
#Dao
public abstract class MyDao {
#Insert
public abstract long insertStore(Store store);
#Insert(onConflict = OnConflictStrategy.ROLLBACK)
public abstract long recordPurchase(Purchase purchase);
#Update
public abstract void updateItem(Item updatedItem);
#Transaction
public void buyItemFromStore(Item boughtItem, Store store) {
// Anything inside this method runs in a single transaction.
long storedId = insertStore(store);
Purchase purchase = new Purchase(storeId);
long purchaseId = recordPurchase(purchase);
item.setPurchaseId(purchaseId);
updateItem(item);
}
}
You can refer to the documentation for an explanation on how #Transaction works.
Then in your repository class, call the buyItemFromStore from your AsyncTask:
public class MyRepository {
private MyDao dao;
public void buy(Item item, Store store) {
new AsyncTask<Void, Void, Void>() {
#Override
protected Void doOnBackground(Void... voids) {
// Everything is saved in a transaction.
dao.buyItemFromStore(item, store);
return null;
}
}
}
}
Note that this is perfectly fine for the Repository layer to be aware of relationships between entities, as long as the stored objects are related in some way (with Store Purchase and Item it seems to be the case).
If you are unable to alter your Dao class, consider RoomDatabase.runInTransaction.
I need to check some data, whether or not to send a tracking info. This data is saved inside the Realm database. Here is the model:
public class RealmTrackedState extends RealmObject {
#PrimaryKey
private int id = 1;
private RealmList<RealmChat> realmChatsStarted;
private boolean isSupportChatOpened;
private boolean isSupportChatAnswered;
/* getters and setters */
}
The idea is - every chat that is not inside the realmChatsStarted should be tracked and then added to this list. Similar thing for isSupportChatOpened boolean - however because of the business logic this is a special case.
So - I've wrapped this inside one Realm object. And I've wrapped this into few shouldTrack() methods, like this:
#Override
public void insertOrUpdateAsync(#NonNull final RealmModel object, #Nullable OnInsertListener listener) {
Realm instance = getRealmInstance();
instance.executeTransactionAsync(realm -> realm.insertOrUpdate(object), () ->
notifyOnSuccessNclose(listener, instance),
error -> notifyOnErrorNclose(listener, error, instance));
}
#Override
public RealmTrackedState getRealmTrackedState() {
try (Realm instance = getRealmInstance()) {
RealmResults<RealmTrackedState> trackedStates = instance.where(RealmTrackedState.class).findAll();
if (!trackedStates.isEmpty()) {
return instance.copyFromRealm(trackedStates.first());
}
RealmTrackedState trackedState = new RealmTrackedState();
trackedState.setRealmChatsStarted(new RealmList<>());
insertOrUpdateAsync(trackedState, null);
return trackedState;
}
}
#Override
public boolean shouldTrackChatStarted(#NonNull RealmChat chat) {
if (getCurrentUser().isRecruiter()) {
return false;
}
RealmList<RealmChat> channels = getRealmTrackedState().getRealmChatsStarted();
for (RealmChat trackedChats : channels) {
if (trackedChats.getId() == chat.getId()) {
return false;
}
}
getRealmInstance().executeTransaction(realm -> {
RealmTrackedState realmTrackedState = getRealmTrackedState();
realmTrackedState.addChatStartedChat(chat);
realm.insertOrUpdate(realmTrackedState);
});
return true;
}
And for any other field inside RealmTrackedState model happens the same.
So, within the presenter class, where I'm firing a track I have this:
private void trackState(){
if(dataManager.shouldTrackChatStarted(chatCache)){
//track data
}
if(dataManager.shouldTrackSupportChatOpened(chatCache)){
//track data
}
if(dataManager.shouldTrackWhatever(chatCache)){
//track data
}
...
}
And I wonder:
a. How much of a performance impact this would have.
I'm new to Realm, but for me opening and closing a DB looks ... heavy.
I like in this implementation that each should(...) method is standalone. Even though I'm launching three of them in a row - in other cases I'd probably use only one.
However would it be wiser to get this main object once and then operate on it? Sounds like it.
b. I see that I can either operate on synchronous and asynchronous transactions. I'm afraid that stacking a series of synchronous transactions may clog the CPU, and using the series of asynchronous may cause unexpected behaviour.
c. #PrimaryKey - I used this because of the wild copy paste session. Assuming that this class should have only instance - is it a correct way to do this?
ad a.
Realm caches instances so opening and closing instances are not that expensive as it sounds. First time an app is opening a Realm file, a number of consistency checks are performed (primarily does model classes match classes on disk) but next time you open an instance, you don't do this check.
ad b.
If your transactions depend on each other, you might have to be careful. On the other hand, why have multiple transactions? An async transaction will notify you when it has completed which can help me to get the behaviour you except.
ad c.
Primary keys are useful when you update objects (using insertOrUpdate()) as the value is use to decide if you are creating/inserting or updating an object.