Страницы

Поиск по вопросам

Показаны сообщения с ярлыком realm. Показать все сообщения
Показаны сообщения с ярлыком realm. Показать все сообщения

воскресенье, 8 марта 2020 г.

Realm and Threading

#java #android #многопоточность #realm #realm_android


Пишу проект. В качестве БД использую Realm. Это первый опыт с этой бд, поэтому за
вопрос прошу не линчевать.
Итак, к примеру, в качестве модели бд есть класс (все упрощенно, так как хочу передать
суть вопроса как можно яснее):

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


Для изолирования слоя работы с этой бд я решила применить обобщенный вариант паттерна
Repository. Написала интерфейс:

public interface QuoteRepository {
    List getListOfQuoteText();
}


И класс, реализующий этот интерфейс:

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


Соответственно во фрагменте для получения списка QuoteText из бд:

QuoteDataRepository quoteDataRepository = new QuoteDataRepository();
List quoteTexts = quoteDataRepository.getListOfQuoteText();


Все бы ничего, но хотелось бы все эти запросы делать не в UI потоке. Как засунуть
это все в другой поток? (Особенно интересно: можно ли совместить способы асинхронных
запросов предлагаемые Realm (назнчаение слушателей, или запрос с использованием onSuccess(),
onError() и прочее) и изоляцию слоя работы с бд). 

Спасибо за помощь!

Правка: важен момент именно абстракции кода работы с бд и реализации асинхронных
запросов.
    


Ответы

Ответ 1



Красивее всего получится если вы будете соединять Realm + RxJava + Retrolambda. Например вот такое может получиться: public Observable> 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))); } Данный Observable, запущенный из основного потока выполнит запрос в БД асинхронно, отсеет объекты не удовлетворяющие двум условиям значений полей объектов (VkPost.FIELD_IS_IN_FEED, VkPost.FIELD_IS_IN_FAVORITES) и проверит что объекты возвращаемые полностью готовы для использования. И будет эмитить новую выборку при каждом изменении в ней.

Ответ 2



У Realm есть возможность создавать асинхронные запросы. Для этого нужно вместо findAll() вызывать findAllAsync(). Поправлю Ваш интерфейс, потому что оба этих метода возвращают не List, а RealmResults - это объект стандарта Future: public interface QuoteRepository { RealmResults getListOfQuoteTextAsync(); } Для того, чтобы получить уведомление об окончании загрузки нужно добавить подписку на экземпляр RealmResults: RealmResults result = quoteDataRepository.getListOfQuoteTextAsync(); result.addChangeListener(new RealmChangeListener() { @Override public void onChange(RealmResults results) { // метод будет вызван, когда запрос будет выполнен или при обновлении данных } }); Помимо этого, можно убедиться в завершении загрузки вызвав метод isLoaded(): if (result.isLoaded()) { // данные загружены } Получение результата Для работы с результатами запросов (в том числе асинхронными) Realm предоставляет специализированные адаптеры, которые нужно добавить в зависимости в build.gradle: dependencies { compile 'io.realm:android-adapters:1.4.0' } После этого нужно создать наследника от RealmRecyclerViewAdapter, который будет работать с Вашим ViewHolder. Продемонстрирую использование на примере из документации 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(); } } В общем случае для получения (и отображения) результата внутри Fragment/Activity нужно осуществить подписку на RealmResults. Сделать это нужно именно в вызывающем коде для возможности отписаться от уведомлений (так как никто кроме вызывающего кода не знает, когда запрос для него уже неактуален). Этот вариант будет выглядеть так: 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 results) { // метод будет вызван, когда запрос будет выполнен или при обновлении данных // здесь можно обновлять экран или делать другой полезный код } }); return root; } @Override public void onDestroyView() { super.onDestroyView(); // при уничтожении фрагмента нужно отписаться от уведомлений results.removeChangeListeners(); realm.close(); } }

Ответ 3



Вот эта статья(-и) дала(-и) ответы на все мои вопросы! Спасибо автору! https://medium.com/@Viraj.Tank/realm-integration-in-android-best-practices-449919d25f2f#.9735g4ojc

понедельник, 16 декабря 2019 г.

Асинхронное чтение/запись в Realm используя RXJava 2

#java #android #rxjava #realm #rxandroid


Моя первая реализация асинхронной работы с помощью RXJava 2. 

Цель:

Получить json данные с сервера библиотекой Retrofit2. Если успешно, то записать в
Realm и сразу после записи получить обратно данные и отправить адаптеру RecyclerView.

Так вот, я все это реализовал таким образом:

private void fetchChatsFromNetwork(int count, AccessDataModel accessDataModel) {

    String accessToken = accessDataModel.getAccessToken();

    MyApplication.getRestApi().getChats(count, accessToken, Constants.api_version)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribeWith(new DisposableSubscriber() {
                @Override
                public void onNext(ChatsModel chatsModel) {
                    if (chatsRepository.hasData()) {

                        chatsRepository.updateChatsData(chatsModel)
                                .subscribe(new DisposableObserver() {
                                    @Override
                                    public void onNext(ChatsModel localChatsModel) {
                                        Log.d(TAG, "DO, onSuccess updated!");
                                        iGetChatsCallback.onGetChatsSuccess(localChatsModel);
                                    }

                                    @Override
                                    public void onError(Throwable e) {
                                        Log.d(TAG, "DO, onError when update!");
                                        iGetChatsCallback.onGetChatsError(e.getMessage());
                                    }

                                    @Override
                                    public void onComplete() {
                                        dispose();
                                        Log.d(TAG, "DO, onComplete!");
                                    }
                                });

                    } else {
                        chatsRepository.insertChatsData(chatsModel)
                                .subscribe(new DisposableObserver() {
                                    @Override
                                    public void onNext(ChatsModel localChatsModel) {
                                        iGetChatsCallback.onGetChatsSuccess(localChatsModel);
                                        Log.d(TAG, "DO, onSuccess inserted!");
                                    }

                                    @Override
                                    public void onError(Throwable e) {
                                        iGetChatsCallback.onGetChatsError(e.getMessage());
                                        Log.d(TAG, "DO, onError when inserting!");
                                    }

                                    @Override
                                    public void onComplete() {
                                        dispose();
                                        Log.d(TAG, "DO, onComplete!");
                                    }
                                });
                    }
                }

                @Override
                public void onError(Throwable t) {
                    Log.d(TAG, "onError" + t.getMessage());
                }

                @Override
                public void onComplete() {
                    Log.d(TAG, "onComplete");
                }
            });
}


Я записываю данные в Realm в методе onNext() подписчика MyApplication.getRestApi().getChats().

Вот код записи:

public Observable updateChatsData(final ChatsModel chatsModel) {

    return Observable.create(new ObservableOnSubscribe() {
        @Override
        public void subscribe(ObservableEmitter e) throws Exception {
            if (chatsModel != null) {
                realm.executeTransactionAsync(
                        realm -> realm.copyToRealmOrUpdate(chatsModel),
                        () -> {
                            Log.d(LOG_TAG, "Data success updated!");
                            ChatsModel localChatsModel = getAllChatsData();
                            e.onNext(localChatsModel);
                            e.onComplete();
                        },
                        error -> {
                            Log.d(LOG_TAG, "Update data failed!");
                            e.onError(error);
                        });
            }

        }
    });

}


Метод updateChatsData() записывает асинхронно и объявлен в другом классе. 

Как видите мой метод fetchChatsFromNetwork() написан громоздко или мне так кажется.

Вопрос:

Правильно ли я делаю или нет, если нет, то как было бы правильнее?
    


Ответы

Ответ 1



Можно полностью отвязать запись в БД от уведомления адаптера о новых данных. Подпишитесь на Observable, выдающий выборку из БД и уведомляющий о ней адаптер. При сетевом запросе полученные данные пишите в БД. При таком способе Observable из первого пункта уведомит адаптер сразу после записи/обновлении данных в БД. Саму запись в БД макже можно проще сделать через flatMap как-то так: MyApplication.getRestApi().getChats(count, accessToken, Constants.api_version) .flatMap(data -> (chatsRepository.hasData() ? chatsRepository.updateChatsData(data) : chatsRepository.insertChatsData(data)).flatMap(data -> Observable.just(true))) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribeWith( aBoolean -> System.out.println("data in DB updated"), error -> System.out.println("error: " + e.getMessage()) ); Тут, возможно, придётся поиграться с заменой транзакций записей в БД с синхронных на асинхронные (скорее наоборот) из-за того, как работают асинхронные в потоках без Looper.

пятница, 14 июня 2019 г.

Realm and Threading

Пишу проект. В качестве БД использую Realm. Это первый опыт с этой бд, поэтому за вопрос прошу не линчевать. Итак, к примеру, в качестве модели бд есть класс (все упрощенно, так как хочу передать суть вопроса как можно яснее):
public class QuoteText extends RealmObject { private long id; private String quoteText; ... }
Для изолирования слоя работы с этой бд я решила применить обобщенный вариант паттерна Repository. Написала интерфейс:
public interface QuoteRepository { List getListOfQuoteText(); }
И класс, реализующий этот интерфейс:
public class QuoteDataRepository implements QuoteRepository { private final Realm realm; @Override public List getListOfQuoteText() { return realm.where(QuoteText.class).findAll(); } }
Соответственно во фрагменте для получения списка QuoteText из бд:
QuoteDataRepository quoteDataRepository = new QuoteDataRepository(); List quoteTexts = quoteDataRepository.getListOfQuoteText();
Все бы ничего, но хотелось бы все эти запросы делать не в UI потоке. Как засунуть это все в другой поток? (Особенно интересно: можно ли совместить способы асинхронных запросов предлагаемые Realm (назнчаение слушателей, или запрос с использованием onSuccess(), onError() и прочее) и изоляцию слоя работы с бд).
Спасибо за помощь!
Правка: важен момент именно абстракции кода работы с бд и реализации асинхронных запросов.


Ответ

Красивее всего получится если вы будете соединять Realm + RxJava + Retrolambda.
Например вот такое может получиться:
public Observable> 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))); }
Данный Observable, запущенный из основного потока выполнит запрос в БД асинхронно, отсеет объекты не удовлетворяющие двум условиям значений полей объектов (VkPost.FIELD_IS_IN_FEED, VkPost.FIELD_IS_IN_FAVORITES) и проверит что объекты возвращаемые полностью готовы для использования. И будет эмитить новую выборку при каждом изменении в ней.