Suppose I have a Book object(id, name, author).
Suppose I have a big list of books, is it possible to retrieve only the names of these books without retrieving the entire objects?
Suppose I have a big list of books, is it possible to retrieve only the names of these books without retrieving the entire objects?
Sure, see:
private RealmChangeListener<RealmResults<Book>> listener = (results) -> {
adapter.updateData(results);
};
RealmResults<Book> books;
void blah() {
books = realm.where(Book.class).findAllAsync();
books.addChangeListener(listener);
}
And
public class MyAdapter extends ___Adapter {
...
public class ViewHolder /* ... */ {
public void bind(Book book) {
bookName.setText(book.getName());
}
}
}
The reason this would work is that while RealmResults evaluates "how to access the given objects", you actually load only the name of the book, by calling book.getName().
Basically, managed RealmObjects are lazily accessed, and Realm itself only loads given object properties when that given property's accessor is called: in this case, book.getName().
As for getting a List<String>, not really. That would read the property from every single object.
Related
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
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 am trying to query my Realm DB such that the output will give an unmanaged object and for that, I changed my RealmList type of object to List.
Now the thing is in addchangeListener I am getting my output object(stories) value as managed. But the type of stories is List. So why my stories object is becoming managed where it should act as an unmanaged object.
List<story> stories = realm.where(story.class).findAllAsync();
stories.addChangeListener(new RealmChangeListener<RealmResults<story>>() {
#Override
public void onChange(RealmResults<story> storydata) {
if (storydata.size() != 0) {
madapter = new StoriesAdapter(stories, getBaseContext(), MR);
mrecyclerview.setNestedScrollingEnabled(false);
mrecyclerview.setLayoutManager(new LinearLayoutManager(getBaseContext()));
mrecyclerview.setAdapter(madapter);
}
}
});
StoriesAdapter
class StoriesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
List<story> storyList;
StoriesAdapter(List<story> storyList) {
this.storyList = storyList;
}
}
I am saying my List is managed because when i am trying to write below code I am getting Cannot modify managed objects outside of a write transaction.
madapter.storyList.get(3).setTitle("Wonderland"); // where storyList is List which i am pointing to `stories`.
List<story> stories = realm.where(story.class).findAllAsync();
Because specifying the type List<story> just means you'll see the returned list as a List<story>, but technically it's still a RealmResults<story>.
stories.addChangeListener(new RealmChangeListener<RealmResults<story>>() {
This line underneath shouldn't even compile.
Stories should be stored in a field.
private RealmResults<story> stories;
public void ...() {
stories = ...
stories.addChangeListener(...
Anyways, so you are working with RealmResults, which means that in
class StoriesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
List<story> storyList;
This storyList you provided is a RealmResults<story>, so calling storyList.get(...) will return managed RealmObjects.
Managed RealmObjects are "temporarily immutable", meaning they can only be modified in a transaction. It is also generally not recommended to run write transactions on the UI thread.
The simplest way would be to use realm-android-adapters.
class StoriesAdapter extends RealmRecyclerViewAdapter<story, RecyclerView.ViewHolder> {
StoriesAdapter(OrderedRealmCollection<story> stories) {
super(stories, true, true);
}
}
And when you want to modify an object, you do
story item = getData().get(3);
final String id = item.getId();
realm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
story changedItem = realm.where(story.class).equalTo("id", id).findFirst();
changedItem.setTitle("Wonderland");
}
});
And then Realm will handle automatically updating the RealmResults, the story object, and the RecyclerView.
EDIT: If you intend to use unmanaged objects, then you could use realm.copyFromRealm(results), except that does the read on the UI thread.
You could create a background looper thread and obtain the results from there, but managing that could be tricky. Luckily for you, there's a library I made called Monarchy which lets you do exactly that.
See the relevant sample code for how you'd use it.
The stories is implicitly Managed, the reason is that RealmResults extends the list interface abstractly. Thats why the casting is possible, underneath the same mechanisms for a RealmResults still takes precedence. Also, you should only pass RealmResults instance to an Adapter directly, if you register a RealmChangeListener on it, which will call adapter.notifyDataSetChanged(). Otherwise, writes will update the RealmResults content, and your adapter will be desynchronized.
Realm is not like SQLite or Core Data. If you’re using Realm, take advantage of live objects. Don’t implement any refreshing logic or requerying. Always allow the current class to own its own instance of a realm query.
This fact is true,Realm objects and any child objects are NOT thread-safe. They’re confined to a single thread to ensure that atomic rights are maintained. There is an internal list where every single thread has its own unique Realm instance. If you want to pass objects between a thread–for example, if you create a dog object on the main thread, pass it to the background thread, and then try and access a property–it will trigger an exception straight away.
Also you are using asynchronous query, which puts it on a worker thread.
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();
}
}
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();