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();
Related
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.
I'm running into an issue with Realm, one to many relationship and updating existing entries to add more data to the relationship.
Model 1
public class Model1 extends RealmObject
{
#PrimaryKey
private String identifier;
#SerializedName("type")
private String type;
#SerializedName("additionalInfo")
private String additionalInfo;
#SerializedName("options")
private RealmList<Model2> moreModels;
}
Model 2
public class Model2 extends RealmObject
{
#SerializedName("hint")
private String hint;
#SerializedName("label")
private String label;
#SerializedName("favorite")
private boolean favorite;
#PrimaryKey
private String identifier;
}
So from an API I'm getting a list of Model1 objects, and each of those objects contains their own list of Model2 objects. Simple enough. This works well, I can add it to Realm and see the relationship.
Now my problem emerges when I make a second API call for a different user. The way I was hoping to have this work was to have the identifier property on Model2 be made up of userId + label. Therefore, each user will have their own set of Model2 objects. However, I was hoping to have only one set of Model1 objects, where its reference to Model2 objects gets updated as more are added to the Model2 table.
Instead what I got working is I keep my one set of Model1 (good), but the relationship to Model2 always gets overwritten with the latest set. So I have a table of Model2 objects that has 80 entries, but only the last 40 are connected.
So it looks like my update or insert is updating the Model1 entries in the table (GOOD) but instead of concatenating the existing relationships with the new ones, its just updating the relationships to only use the new ones. The Model2 entries are being added to their table correctly in that there are not duplicates based on the primary key. But the connection is broken.
Update Code
List<Model1> test = response.body();
try(Realm realmInstance = Realm.getDefaultInstance()) {
realmInstance.executeTransaction((realm) ->
{
realm.insertOrUpdate(test);
final RealmResults<Model> category = realm.where(Model1).findAll();
}
);
}
Yes, that is how the insertOrUpdate and copyToRealmOrUpdate methods works currently. Ideally, there would be a mode you could provide like REPLACE_LISTS or MERGE_LISTS, but right now that hasn't been implemented.
Your only choice is manually updating these relationships.
I am trying to find a way to insert a child object in its parent object without having to do a query for each one.
Let me be more specific:
class Parent extends RealmObject {
String id;
RealmList<Child> childs;
}
class Child extends RealmObject {
String parentId;
}
And suppose I make a request in a webservice to get child object of different parents... so, now I have:
List<Child> childsOfDifferentParents;
And I want to add to link each child to its correct parent.
The only way I've found so far was, query for each parent and add it.
But, since it is too many objects, it wouldn't be efficient. What should I do?
This question is a follow-up question from: Organize Android Realm data in lists
Due to the data returned by the API we use, it's slightly impossible to do an actual query on the realm database. Instead I'm wrapping my ordered data in a RealmList and adding a #PrimaryKey public String id; to it.
So our realm data looks like:
public class ListPhoto extends RealmObject {
#PrimaryKey public String id;
public RealmList<Photo> list; // Photo contains String/int/boolean
}
which makes easy to write to and read from the Realm DB by simply using the API endpoint as the id.
So a typical query on it looks like:
realm.where(ListPhoto.class).equalTo("id", id).findFirstAsync();
This creates a slightly overhead of listening/subscribing to data because now I need to check listUser.isLoaded() use ListUser to addChangeListener/removeChangeListener and ListUser.list as an actual data on my adapter.
So my question is:
Is there a way I can query this realm to receive a RealmResults<Photo>. That way I could easily use this data in RealmRecyclerViewAdapter and use listeners directly on it.
Edit: to further clarify, I would like something like the following (I know this doesn't compile, it's just a pseudo-code on what I would like to achieve).
realm
.where(ListPhoto.class)
.equalTo("id", id)
.findFirstAsync() // get a results of that photo list
.where(Photo.class)
.getField("list")
.findAllAsync(); // get the field "list" into a `RealmResults<Photo>`
edit final code: considering it's not possible ATM to do it directly on queries, my final solution was to simply have an adapter that checks data and subscribe if needed. Code below:
public abstract class RealmAdapter
<T extends RealmModel,
VH extends RecyclerView.ViewHolder>
extends RealmRecyclerViewAdapter<T, VH>
implements RealmChangeListener<RealmModel> {
public RealmAdapter(Context context, OrderedRealmCollection data, RealmObject realmObject) {
super(context, data, true);
if (data == null) {
realmObject.addChangeListener(this);
}
}
#Override public void onChange(RealmModel element) {
RealmList list = null;
try {
// accessing the `getter` from the generated class
// because it can be list of Photo, User, Album, Comment, etc
// but the field name will always be `list` so the generated will always be realmGet$list
list = (RealmList) element.getClass().getMethod("realmGet$list").invoke(element);
} catch (Exception e) {
e.printStackTrace();
}
if (list != null) {
((RealmObject) element).removeChangeListener(this);
updateData(list);
}
}
}
First you query the ListPhoto, because it's async you have to register a listener for the results. Then in that listener you can query the result to get a RealmResult.
Something like this
final ListPhoto listPhoto = realm.where(ListPhoto.class).equalTo("id", id).findFirstAsync();
listPhoto.addChangeListener(new RealmChangeListener<RealmModel>() {
#Override
public void onChange(RealmModel element) {
RealmResults<Photo> photos = listPhoto.getList().where().findAll();
// do stuff with your photo results here.
// unregister the listener.
listPhoto.removeChangeListeners();
}
});
Note that you can actually query a RealmList. That's why we can call listPhoto.getList().where(). The where() just means "return all".
I cannot test it because I don't have your code. You may need to cast the element with ((ListPhoto) element).
I know you said you're not considering the option of using the synchronous API, but I still think it's worth noting that your problem would be solved like so:
RealmResults<Photo> results = realm.where(ListPhoto.class).equalTo("id", id).findFirst()
.getList().where().findAll();
EDIT: To be completely informative though, I cite the docs:
findFirstAsync
public E findFirstAsync()
Similar to findFirst() but runs asynchronously on a worker thread This method is only available from a Looper thread.
Returns: immediately an empty RealmObject.
Trying to access any field on the returned object before it is loaded
will throw an IllegalStateException.
Use RealmObject.isLoaded() to check if the object is fully loaded
or register a listener RealmObject.addChangeListener(io.realm.RealmChangeListener<E>) to be
notified when the query completes.
If no RealmObject was found after
the query completed, the returned RealmObject will have
RealmObject.isLoaded() set to true and RealmObject.isValid() set to
false.
So technically yes, you need to do the following:
private OrderedRealmCollection<Photo> photos = null;
//...
final ListPhoto listPhoto = realm.where(ListPhoto.class).equalTo("id", id).findFirstAsync();
listPhoto.addChangeListener(new RealmChangeListener<ListPhoto>() {
#Override
public void onChange(ListPhoto element) {
if(element.isValid()) {
realmRecyclerViewAdapter.updateData(element.list);
}
listPhoto.removeChangeListeners();
}
}
I've been trying to add Realm in my Android app. Their docs are pretty well explained & easy to follow. But it fails to explain this one particular area. I'm unable to figure out the practical use for the #Ignore annotation. I know that fields under this annotation are not persisted.
Can someone please share a few use cases. Also I wanted to know the scope of such fields. I mean, if I set an #Ignore field to some value, would that value be available to the other classes in my app for that particular launch session. If yes, then how do we access it? If no (which I guess is the case), then why do we need such a field anyway?
I've searched here and on web but couldn't find the relevant information. If out of my ignorance, I've missed upon some resource, please guide me to it.
Thanks.
Accordingly to the official documentation (see https://realm.io/docs/java/latest/) #Ignore is useful in two cases:
When you use GSON integration and your JSON contains more data than you want to store, but you still would like to parse it, and use right after.
You can't create custom getters and setter in classes extending RealmObject, since they are going to be overridden. But in case you want to have some custom logic anyway, ignored fields can be used as a hack to do that, because Realm doesn't override their getter & setters. Example:
package io.realm.entities;
import io.realm.RealmObject;
import io.realm.annotations.Ignore;
public class StringOnly extends RealmObject {
private String name;
#Ignore
private String kingName;
// custom setter
public void setKingName(String kingName) { setName("King " + kingName); }
// custom getter
public String getKingName() { return getName(); }
// setter and getter for 'name'
}
Ignored fields are accessible only from the object they were set in (same as with regular objects in Java).
UPDATE: As the #The-null-Pointer- pointed out in the comments the second point is out of date. Realm now allows having custom getters and setters in Realm models.
Here's a couple of real-world use cases:
1 - Get user's fullname:
public class User extends RealmObject {
private String first;
private String last;
#Ignore
private String fullName;
public String getFullName() {
return getFirst() + " " + getLast();
}
Get JSON representation of object:
public class User extends RealmObject {
private String first;
private String last;
#Ignore
private JSONObject Json;
public JSONObject getJson() {
try {
JSONObject dict = new JSONObject();
dict.put("first", getFirst());
dict.put("last", getLast());
return dict;
} catch (JSONException e) {
// log the exception
}
return null;
}
I've found it useful to define field names for when I am querying. For example
User.java
public class User extends RealmObject {
#Index
public String name;
#Ignore
public static final String NAME = "name";
}
And then later on I can do something like:
realm.where(User.class).equalTo(User.NAME, "John").findFirst();
This way if the schema changes from say name to id I don't have to hunt down every occurrence of "name".
Please see the the official documentation about #Ignore annotation:
The annotation #Ignore implies that a field should not be persisted to disk. Ignored fields are useful if your input contains more fields than your model, and you don’t wish to have many special cases for handling these unused data fields.