I have following database relationship:
In words: One Order has 0-n Books, one Order is assigned to one Customer.
In my case, I have bookId. I want to launch some function when I get all associated items (Book, Order and Customer) and when I am assured all of them exists - I need to launch it only one time. I tried to solve it following way:
ViewModel:
private LiveData<Book> book;
private LiveData<Order> order;
private LiveData<Customer> customer;
public MyViewModel(Application app) {
...
book = bookRepository.getBookLiveData(id);
order = Transformations.switchMap(book, b -> orderRepository.getOrder(b.getIdOrder()));
customer = Transformations.switchMap(order, o -> customerRepository.getCustomer(o.getIdCustomer()));
}
However, this solution is uneffective and I believe this can be done some more elegant way with Room/LiveData.
I tried also another approach - creating following object BookOrderCustomer:
public class BookOrderCustomer {
#Embedded
public Book book;
#Embedded
public Order order;
#Embedded
public Customer customer;
}
But this did not work as expected,Dao's query always returned null.
Any idea how to solve this case? Thank you.
There is the MediatorLiveData, it could observe all your streams and merge data. I think this is a best approach.
Related
I have to insert a value to table one in a hour for a specific id. I know it can be achieved by SQLite trigger but i read somewhere else that Room database currently doesn't support SQLite trigger function.
How to achieve above task? I have included my data model here.
#Entity(tableName = "device_table")
public class Device {
#PrimaryKey (autoGenerate = true)
private int id;
private String userId;
private long time;
public Device(String userId) {
this.userId = userId;
}
// and rest of the getter setter methods
}
I think you don't need SQLite trigger there (if update of your row doesn't depend on insert/delete another row|rows). You can divide your task to 2 subtasks:
How to create function, that updates row in Sqlite with some id.
How in Android invoke this function once in an hour.
For first subtask decision depends on how you want to update your data.
The simplest decision here - to use in your DAO interface method with #Insert annotation:
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertDevice(device: Device)
You should put "updated" instance of your data class Device to this method.
"OnConflictStrategy.REPLACE" means that if row with this "id" exists, then row will be overridden, else - it will be added.
For second subtask you can use one of the mechanisms - Handler, Executor, Timer, AlarmManager, WorkManager (last works even if your app is inactive). There are many answers/examples on using them here on SO.
I am refactoring old application to mvvm pattern, using room, repository, viewmodel, ets.
I have an old code, which contains Content provider helper class with many functions like this:
public static int deleteOldLogs(int NumDays) {
//get NumDays before today, then constract a content provider delete command and run
...
}
or
public static Cursor getTodayLogs() {
//get a day from today, then constract a content provider query and run
...
}
or
public static boolean isActionValid(Context context, int id_order, int id_actionh) {
//get all products from database table, then check if all products match some criteria, then return boolean result
...
}
My question is in what layer to place this logic? Is it a repository or viewmodel should contain? All the examples that I see in the net is very simple and not suit my goals.
View model helps us to provide data between repository and UI . For direct interaction with room database , we use repository . Once we get the data from repo we can perform all sort of computation (i.e sorting , filtering etc ) in ViewModel .
In order to display data from the database, we use an observer who will observe the data changes, LiveData in the ViewModel.
We use ViewModelProvider which is going to create a ViewModel for us. We need to connect our ViewModel with the ViewModelProvider, and then in the onChanged method, we always get our updated data which we can display on the screen.
For eg . We want to get some record from our database .
For this we need to create a repository that will interact directly with database or carrying the logic to fetch data from database .
public class ABCRepository {
#Inject
DrugsDao mABCDao;
#Inject
public ABCRepository(){
}
public LiveData<List<NameModel>> getNameByLetter(String letter) {
return mABCDao.getName(letter);
}
}
Now in View Model
public class SearchViewModel extends ViewModel {
#Inject
ABCRepository mABCRepository;
LiveData<List<GlobalSearchModel>> getNameList(String queryText) {
MutableLiveData<List<GlobalSearchModel>> mGlobalSearchResults = new
MutableLiveData<>();
List<NameModel> synonymsNameList=mABCRepository.getNameByLetter(queryText);
new Thread(() -> {
List<GlobalSearchModel> globalSearchModelList =
mABCRepository.getNameByLetter(queryText)
// this is where you can perform any action on list . either sorting or.
filtering and then return the new list to your UI.
mGlobalSearchResults.postValue(globalSearchModelList);
}).start();
return globalSearchModelList;
}
}
In your fragment or activity you can observe this data ,
getViewModel().getAllCountries().observe(this, this::addSearchResultsInRecycler);
Hope this is helpful . Though not explained good but you can have reference from
https://medium.com/#skydoves/android-mvvm-architecture-components-using-the-movie-database-api-8fbab128d7
Please bear with me, i'm new to architecture components and android in general.
My question is similar to this question but unfortunately the accepted answer doesn't seem to work.
I have an example one to many relation like in this answer. My example database has two tables USERS and PETS as shown in the following images:
Let's say I want to get a list of users containing a list of their pets grouped by user id only with pets younger than 5.
The result should look like this (pseudo code):
{uId: 2, [Pet3, Pet4]; uId: 4, [Pet6, Pet7];}
Another requirement is that the Dao needs to return the list as a LiveData object because I'm using MVVM architecture and want it to be Lifecycle aware and observable.
With these requirements, the UserDao would look like this:
#Dao
interface UserDao {
#Insert
void insert(User user);
#Transaction
#Query("SELECT USERS.uId, PETS.pId , PETS.userId, PETS.age " +
"FROM USERS INNER JOIN PETS ON PETS.userId = USERS.uId " +
"WHERE PETS.age < 5 " +
"GROUP BY USERS.uId")
LiveData<List<UserWithPets>> getUserPets();
}
User Entity:
#Entity
public class User {
#PrimaryKey
public int id; // User id
}
Pet Entity:
#Entity
public class Pet {
#PrimaryKey
public int id; // Pet id
public int userId; // User id
public int age;
}
The problem is now: how should i design the UserWithPets that room understands it and maps the cursor the way i want?
Here is what i tried so far:
Approach 1:
The most convenient way in my opinion would be using a Relation, like in the POJO below.
UserWithPets POJO:
public class UserWithPets {
#Embedded
public User user;
#Relation(parentColumn = "id", entityColumn = "userId", entity = Pet.class)
public List<Pet> pets;
}
Unfortunately, the functionality to assign a condition to a relation is not yet implemented by google. So we always get a full list of pets for every user that owns a pet younger than 5. Hopefully this will be possible soon, since the feature request is already assigned here and here.
Statement from google from this feature request: "we are planning to implement some query rewriting logic to fix these, not for 2.1 but hopefully in 2.2 where we'll focus more on relations."
Approach 2:
Another option would be Embedding both, User and Pet like:
public class UserWithPets {
#Embedded
public User user;
#Embedded
public Pet pet;
}
This doesn't work either, because now we only get 1 pet per user.
Approach 3:
this answer suggests to just create a merged class that extends from user like:
public class UserWithPets extends User {
#Embedded(prefix = "PETS_")
List<Pet> pets = new ArrayList<>();
}
I tried in many ways, with contructor and without, but i can't get it to work. it always gives errors like "Entities and Pojos must have a usable public constructor. You can have an empty constructor or a constructor whose parameters match the fields (by name and type). - java.util.List"
or
The query returns some columns ... which are not used by UserWithPets. So any advice is welcome here.
Approach 4:
Just make two queries and stitch the results together. How would i do that using LiveData? Where should the joining operation be done? I can't do it in the Activity, that's not the point of an MVVM pattern. And not in the repository or viewmodel, since LiveData is immutable. Or is there another way?
What would be a working solution to get a result with the above requirements?
In an Android app using Architecture Components I have the following view model:
public class MainViewModel extends AndroidViewModel {
private final MutableLiveData<List<String>> mUnchecked = new MutableLiveData<>();
private LiveData<List<String>> mChecked;
public void setUnchecked(List<String> list) {
mUnchecked.setValue(list);
}
public LiveData<List<String>> getChecked() { // OBSERVED BY A FRAGMENT
return mChecked;
}
public MainViewModel(Application app) {
super(app);
mChecked = Transformations.switchMap(mUnchecked,
list-> myDao().checkWords(list));
}
The purpose of the above switchMap is to check, which of the words passed as a list of strings, do exist in a Room table:
#Dao
public interface MyDao {
#Query("SELECT word FROM dictionary WHERE word IN (:words)")
LiveData<List<String>> checkWords(List<String> words);
The above code works well for me!
However I am stuck with wanting something slightly different -
Instead of the list of strings, I would prefer to pass a map of strings (words) -> integers (scores):
public void setUnchecked(Map<String,Integer> map) {
mUnchecked.setValue(map);
}
The integers would be word scores in my game. And once the checkWords() has returned the results, I would like to set the scores to null for the words not found in the Room table and leave the other scores as they are.
The programming code would be easy (iterate through mChecked.getValue() and set to null for the words not found in the list returned by the DAO method) - but how to "marry" it with my LiveData members?
TL;DR
I would like to change my view model to hold maps instead of the lists:
public class MainViewModel extends AndroidViewModel {
private final MutableLiveData<Map<String,Integer>> mUnchecked = new MutableLiveData<>();
private final MutableLiveData<Map<String,Integer>> mChecked = new MutableLiveData<>();
public void setUnchecked(Map<String,Integer> map) {
mUnchecked.setValue(map);
}
public LiveData<Map<String,Integer>> getChecked() { // OBSERVED BY A FRAGMENT
return mChecked;
}
public MainViewModel(Application app) {
super(app);
// HOW TO OBSERVE mUnchecked
// AND RUN myDao().checkWords(new ArrayList<>(mUnchecked.getValue().keys()))
// WRAPPED IN Executors.newSingleThreadScheduledExecutor().execute( ... )
// AND THEN CALL mChecked.postValue() ?
}
How to achieve that please? Should I extend MutableLiveData or maybe use MediatorLiveData or maybe use Transformations.switchMap()?
UPDATE:
I will try the following tomorrow (today is too late in the evening) -
The Dao method I will change to return a list instead of LiveData:
#Query("SELECT word FROM dictionary WHERE word IN (:words)")
List<String> checkWords(List<String> words);
And then I will try to extend the MutableLiveData:
private final MutableLiveData<Map<String,Integer>> mChecked = new MutableLiveData<>();
private final MutableLiveData<Map<String,Integer>> mUnchecked = new MutableLiveData<Map<String,Integer>>() {
#Override
public void setValue(Map<String,Integer> uncheckedMap) {
super.setValue(uncheckedMap);
Executors.newSingleThreadScheduledExecutor().execute(() -> {
List<String> uncheckedList = new ArrayList<>(uncheckedMap.keySet());
List<String> checkedList = WordsDatabase.getInstance(mApp).wordsDao().checkWords(uncheckedList);
Map<String,Integer> checkedMap = new HashMap<>();
for (String word: uncheckedList) {
Integer score = (checkedList.contains(word) ? uncheckedMap.get(word) : null);
checkedMap.put(word, score);
}
mChecked.postValue(checkedMap);
});
}
};
Well, what you have there in the update probably works, though I wouldn't create a new Executor for every setValue() call — create just one and hold onto it in your MutableLiveData subclass. Also, depending on your minSdkVersion, you might use some of the Java 8 stuff on HashMap (e.g., replaceAll()) to simplify the code a bit.
You could use MediatorLiveData, though in the end I think it would result in more code, not less. So, while from a purity standpoint MediatorLiveData is a better answer, that may not be a good reason for you to use it.
Frankly, this sort of thing isn't what LiveData is really set up for, IMHO. If this were my code that I were working on right now, I'd be using RxJava for the bulk of it, converting to LiveData in the end. And, I'd have as much of this as possible in a repository, rather than in a viewmodel. While your unchecked-to-checked stuff would be a tricky RxJava chain to work out, I'd still prefer it to the MutableLiveData subclass.
What EpicPandaForce suggests is an ideal sort of LiveData-only approach, though I don't think he is implementing your algorithm quite correctly, and I am skeptical that it can be adapted easily to your desired algorithm.
In the end, though, the decision kinda comes down to: who is going to see this code?
If this code is for your eyes only, or will live in a dusty GitHub repo that few are likely to look at, then if you feel that you can maintain the MutableLiveData subclass, we can't really complain.
If this code is going to be reviewed by co-workers, ask your co-workers what they think.
If this code is going to be reviewed by prospective employers... consider RxJava. Yes, it has a learning curve, but for the purposes of getting interest from employers, they will be more impressed by you knowing how to use RxJava than by you knowing how to hack LiveData to get what you want.
Tricky question!
If we check the source code for Transformations.switchMap, we see that:
1.) it wraps the provided live data with a MediatorLiveData
2.) if the wrapped live data emits an event, then it invokes a function that receives the new value of wrapped live data, and returns a "new" live data of a different type
3.) if the "new" live data of a different type differs from the previous one, then the observer of the previous one is removed, and it's added to the new one instead (so that you only observe the newest LiveData and don't accidentally end up observing an old one)
With that in mind, I think we can chain your switchMap calls and create a new LiveData whenever myDao().checkWords(words) changes.
LiveData<List<String>> foundInDb = Transformations.switchMap(mWords, words -> myDao().checkWords(words));
LiveData<Map<String, Integer>> found = Transformations.switchMap(foundInDb, (words) -> {
MutableLiveData<Map<String, Integer>> scoreMap = new MutableLiveData<>();
// calculate the score map from `words` list
scoreMap.setValue(map);
return scoreMap;
});
this.mFound = found;
Please verify if what I'm telling you is correct, though.
Also if there are a bunch of words, consider using some async mechanism and scoreMap.postValue(map).
In this example, the docs talked about getting the parent objects while specifying queries for the child objects.
Is there a way for getting the child objects while specifying a query for the parent object?
In the given example, can I search for dogs who are of brown color with the user named John?
EDIT: Since Realm 3.5.0, you can actually use the "backlinks" mentioned in the comment section. Rejoice!
In fact, since Realm 3.0.0, bidirectional links are a performance bottleneck, so using backlinks is the preferred way.
The way it works is:
public class User extends RealmObject {
private RealmList<Dog> dogs;
}
public class Dog extends RealmObject {
#LinkingObjects("dogs")
private final RealmResults<User> owners = null;
}
Now you can do:
realm.where(Dog.class).equalTo("color", "Brown").equalTo("owners.name", "John").findAll();
OLD ANSWER:
You can only search for dogs with a given user if you have an object link to the User.
public class Dog extends RealmObject {
//...
private User user;
}
Then you could do
realm.where(Dog.class).equalTo("color", "Brown").equalTo("user.name", "John").findAll();