I am writing a project. As a DB I use Realm . This is the first experience with this database, so I ask you not to lynch the question. So, for example, as a model, there is a class (everything is simple, because I want to convey the essence of the question as clearly as possible):

public class QuoteText extends RealmObject { private long id; private String quoteText; ... } 

To isolate the layer of work with this database, I decided to apply a generalized version of the Repository pattern. Wrote interface:

 public interface QuoteRepository { List<QuoteText> getListOfQuoteText(); } 

And the class that implements this interface:

 public class QuoteDataRepository implements QuoteRepository { private final Realm realm; @Override public List<QuoteText> getListOfQuoteText() { return realm.where(QuoteText.class).findAll(); } } 

Accordingly, in the fragment to get the QuoteText list from the QuoteText :

 QuoteDataRepository quoteDataRepository = new QuoteDataRepository(); List<QuoteText> quoteTexts = quoteDataRepository.getListOfQuoteText(); 

Everything would be fine, but I would like to do all these requests not in the UI stream. How to shove it all in a different thread? (Especially interesting: is it possible to combine the methods of asynchronous requests offered by Realm (assignment of listeners, or a request using onSuccess() , onError() , etc.) and isolation of the work with database).

Thanks for the help!

Edit: it is important the moment of abstraction of the code for working with the database and the implementation of asynchronous requests.

  • one
    You cannot fully abstract from Realm in your fragment, because the result of the sample will be RealmResults - you will work with it. Transforming it into a List meaningless and merciless - the very essence of working with the database is lost, especially considering that in Realm the selection of RealmResults is related to the data in the database and changes in the data in the sample change the data in the database itself. - pavlofff

3 answers 3

Most of all it will turn out if you connect Realm + RxJava + Retrolambda.

For example, this can happen:

 public Observable<List<VkPost>> getFeedVkPostsSortedAsync(String field, Sort order) { return mRealm.where(VkPost.class) .equalTo(VkPost.FIELD_IS_IN_FEED, true) .equalTo(VkPost.FIELD_IS_IN_FAVORITES, true) .findAllSortedAsync(field, order) .asObservable() .filter(RealmResults::isLoaded) .filter(RealmResults::isValid) //опционально и на всякий случай отвязываем объекты от реалма и //возвращаем обычные объекты в обычном листе .flatMap(realmResults -> Observable.just(mRealm.copyFromRealm(realmResults))); } 

This Observable, launched from the main thread, will execute a query to the database asynchronously, deselect objects that do not meet the two conditions of the object field values ​​( VkPost.FIELD_IS_IN_FEED , VkPost.FIELD_IS_IN_FAVORITES ) and check that the objects returned are completely ready for use. And will issue a new sample with each change in it.

  • Here is a beauty! True, with Rx, I'm still on "you." But you convinced me to get to know her better) - V. Zatchepina
  • @ V.Zatchepina, you will not regret it) Rx is cool. The truth is that everything is okay to subscribe better not in the fragment, since it can be destroyed, but somewhere else. For example in singleton. Take, for example, mosby and saw MVI. In the presenter subscribe and send data to the View (fragment) that displays them. - Yuriy SPb
  • Thank you very much! Now one more variant, I will think it over and if it works, I will accomplish my goal. With a different outcome, I will learn RxJava, since the code is just fine :) - V. Zatchepina
  • @ V.Zatchepina This solution, no matter how beautiful the code, also does not abstract you from RealmResults , if you are trying to achieve just that - the result of the method will be of the type RealmResults . This abstraction is generally meaningless and its only ability is to degrade the interaction with the database. - pavlofff
  • 2
    @ V.Zatchepina The goal itself is to abstract from the database, because suddenly I will use another database somewhat strange. On repacking in "neutral" objects may require a significant amount of time and resources, with some of the functionality of the direct sample is lost. To reduce the amount of code, you can write a helper class that will have methods for queries in Realm and keep connecting to it. In the methods themselves, transfer only the request parameters, and receive a ready-made selection. similar to SQLite - SQLiteOpenHelper - pavlofff

Realm has the ability to create asynchronous requests . To do this, instead of findAll() call findAllAsync() .

I will correct your interface, because both of these methods do not return a List , but RealmResults is an object of the Future standard:

 public interface QuoteRepository { RealmResults<QuoteText> getListOfQuoteTextAsync(); } 

In order to receive notification of the end of the download, you need to add a subscription to the RealmResults instance:

 RealmResults<QuoteText> result = quoteDataRepository.getListOfQuoteTextAsync(); result.addChangeListener(new RealmChangeListener() { @Override public void onChange(RealmResults<User> results) { // метод будет вызван, когда запрос будет выполнен или при обновлении данных } }); 

In addition, you can make sure that the download is complete by calling the isLoaded() method:

 if (result.isLoaded()) { // данные загружены } 

Getting the result

To work with query results (including asynchronous), Realm provides specialized adapters that need to be added to dependencies in build.gradle :

 dependencies { compile 'io.realm:android-adapters:1.4.0' } 

After that you need to create a successor from RealmRecyclerViewAdapter , which will work with your ViewHolder . I will demonstrate the use of the example from the documentation.

 public class MyFragment extends Fragment { private Realm realm; private RecyclerView recyclerView; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { realm = Realm.getDefaultInstance(); View root = inflater.inflate(R.layout.fragment_view, container, false); recyclerView = (RecyclerView) root.findViewById(R.id.recycler_view); // установка Вашего адаптера для RecyclerView recyclerView.setAdapter(new MyRecyclerViewAdapter(getActivity(), // установка результата асинхронного запроса quoteDataRepository.getListOfQuoteTextAsync())); // ... return root; } @Override public void onDestroyView() { super.onDestroyView(); realm.close(); } } 

In general, to get (and display) the result inside Fragment / Activity you need to subscribe to RealmResults . This should be done in the calling code in order to be able to unsubscribe from notifications (since no one except the calling code knows when the request for it is already irrelevant). This option will look like this:

 public class MyFragment extends Fragment { private Realm realm; private RealmResults results; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { realm = Realm.getDefaultInstance(); View root = inflater.inflate(R.layout.fragment_view, container, false); results = quoteDataRepository.getListOfQuoteTextAsync(); // добавление подписки на получение результата results.addChangeListener(new RealmChangeListener() { @Override public void onChange(RealmResults<User> results) { // метод будет вызван, когда запрос будет выполнен или при обновлении данных // здесь можно обновлять экран или делать другой полезный код } }); return root; } @Override public void onDestroyView() { super.onDestroyView(); // при уничтожении фрагмента нужно отписаться от уведомлений results.removeChangeListeners(); realm.close(); } } 
  • Yes, yes, I know it. It’s just that one thing doesn’t come to me: if I write these requests in the repository class in async, then calling this method in the fragment, the method will return zero, without waiting for the real result to return. Initially, I interfered with the implementation of the bd with a fragment in order to touch the realm. But in reality, I need to make the repository class method return not an empty list, but wait for the data, so that the fragment has no idea about which database it works with (so to say, not a single line of code with realm in the fragment). Phew ... I hope I didn't mess you up much. - V. Zatchepina
  • The method returns not null, but RealmResults. You need to subscribe to this object so that you will be informed when the data will be uploaded. At this moment it will be possible to use them - XIII-th
  • Oh ... forgive my explanations, but I will repeat: I know that it will return non-null. I launch the app, a fragment is created, it creates an instance of the repository and calls its method in which the request to the database goes. As a result, I get a list of data (Why am I writing a List <Object> - so that there is no code associated with Realm). If you put everything in the main thread, everything works fine. But if you write an async request, then the zero list is naturally returned, since the data has not yet returned. (continued see below ...) - V. Zatchepina
  • In order to subscribe to RealmResults, the code will be written in the fragment (and not in the repository class), and I don’t need this. It does not occur to the head how to subscribe avoiding the code in the fragment. I already think that the adapter can transmit the adapter in the arguments of the repository method and update it in the listener ... but this is also a variant. I hope you understand me ... Or I do not understand you) - V. Zatchepina
  • @ V.Zatchepina, added a section about working with adapters in Realm . Take a look - XIII-th

This article (s) gave answers to all my questions! Thanks to the author!

https://medium.com/@Viraj.Tank/realm-integration-in-android-best-practices-449919d25f2f#.9735g4ojc