Room. RxJava
У цьому уроці розглянемо можливість спільного використання RxJava і Room. Як отримувати дані у Flowable, Single і Maybe.
У build.gradle модуля додавайте dependencies
implementation "android.arch.persistence.room:rxjava2:1.0.0"
Flowable
У Dao вказуємо для методу вихідний тип Flowable
@Query("SELECT * FROM employee")
Flowable<List<Employee>> getAll();
У коді підписуємося й отримуємо дані
db.employeeDao().getAll()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<List<Employee>>() {
@Override
public void accept(List<Employee> employees) throws Exception {
// ...
}
});
subscribeOn у випадку з Flowable не потрібен. Запит до бази буде виконано не в UI потоці. А ось, щоб результат прийшов у UI потік, використовуємо observeOn
Тепер при будь-якій зміні даних у базі, ми будемо отримувати свіжі дані в методі accept. І нам не треба буде щоразу їх знову запитувати самим.
Якщо під час запиту декількох записів, замість Flowable<List<Employee>> використовувати Flowable<Employee>:
@Query("SELECT * FROM employee")
Flowable<Employee> getAll();
то ми отримаємо тільки перший запис з усього результату
Якщо ж ми складаємо запит для отримання тільки одного запису, то Flowable<Employee> цілком підійде. Давайте розглянемо цей приклад докладніше.
Метод у Dao
@Query("SELECT * FROM employee WHERE id = :id")
Flowable<Employee> getById(long id);
У коді підписуємося на Flowable
db.employeeDao().getById(1)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<Employee>() {
@Override
public void accept(Employee employee) throws Exception {
// ...
}
});
Отже, ми запитуємо з бази запис за id. І тут можливі варіанти.
Якщо запис є в базі, то він прийде в accept відразу ж після підписки. І під час кожного наступного оновлення цього запису в базі даних, він також приходитиме в accept.
Якщо запису немає, то відразу після підписки нічого не прийде. А ось якщо він пізніше з'явиться, то він прийде в accept.
У вищеописаного прикладу є мінус. Якщо запису немає в базі, то Flowable взагалі нічого нам не надішле. Тобто це буде виглядати так, ніби він все ще виконує запит.
Це можна виправити таким чином:
@Query("SELECT * FROM employee WHERE id = :id")
Flowable<List<Employee>> getById(long id);
Хоч ми й очікуємо всього один запис, але використовуємо не Flowable<Employee>, а Flowable<List<Employee>>. І якщо запису немає, то ми хоча б отримаємо порожній аркуш замість повної тиші.
Single
Розглянемо той самий приклад із запитом одного запису, але з використанням Single. Нагадаю, що в Single може прийти тільки один onNext, або OnError. Після цього Single вважається завершеним.
Метод в Dao
@Query("SELECT * FROM employee WHERE id = :id")
Single<Employee> getById(long id);
У коді підписуємося
db.employeeDao().getById(1)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new DisposableSingleObserver<Employee>() {
@Override
public void onSuccess(Employee employee) {
// ...
}
@Override
public void onError(Throwable e) {
// ...
}
});
На відміну від Flowable, з Single необхідно використовувати onSubscribe, щоб задати потік для виконання запиту. Інакше в onError прийде помилка: java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
Знову розглядаємо варіанти наявності потрібного запису в базі.
Якщо такий запис у базі є, то він прийде в onSuccess. Після цього Single вважатиметься завершеним і під час наступних оновлень цього запису нічого приходити вже не буде.
Якщо такого запису в базі немає, то ми в onError отримаємо помилку: android.arch.persistence.room.EmptyResultSetException: Query returned empty result set: SELECT * FROM employee WHERE id = ?.
Після цього Single вважатиметься завершеним, і навіть якщо такий запис з'явиться в базі, то нам нічого приходити вже не буде.
Maybe
Розглянемо той самий приклад із запитом одного запису, але з використанням Maybe. Нагадаю, що в Maybe може прийти або один onNext, або onComplete, або OnError. Після цього Maybe вважається завершеним.
Метод в Dao
@Query("SELECT * FROM employee WHERE id = :id")
Maybe<Employee> getById(long id);
У коді підписуємося
db.employeeDao().getById(1)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new DisposableMaybeObserver<Employee>() {
@Override
public void onSuccess(Employee employee) {
// ...
}
@Override
public void onError(Throwable e) {
// ...
}
@Override
public void onComplete() {
// ...
}
});
З Maybe також необхідно використовувати onSubscribe, щоб задати потік для виконання запиту.
Розглядаємо варіанти наявності необхідного запису в базі.
Якщо такий запис у базі є, то він прийде в onSuccess. Після цього Maybe вважатиметься завершеним і під час наступних оновлень цього запису нічого приходити вже не буде.
Якщо такого запису в базі немає, то ми отримаємо onComplete. Після цього Maybe вважатиметься завершеним, і навіть якщо такий запис з'явиться в базі, то нам нічого приходити вже не буде.
У якому випадку що краще використовувати?
Flowable підходить, якщо ви запитуєте дані і далі плануєте автоматично отримувати їх оновлення.
Single і Maybe підходять для одноразового отримання даних. Різниця між ними в тому, що Single логічніше використовувати, якщо запис має бути в базі. Якщо його немає, вам прийде помилка. А Maybe допускає, що запису може і не бути.
Приклад
Крок 1: Залежності
Додайте необхідні залежності в build.gradle:
dependencies {
implementation 'androidx.room:room-runtime:2.5.0'
implementation 'io.reactivex.rxjava3:rxjava:3.1.6'
implementation 'androidx.room:room-ktx:2.5.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.1'
annotationProcessor 'androidx.room:room-compiler:2.5.0' // або kapt, якщо використовуєте Kotlin
}
Крок 2: Створення сутності (Entity)
@Entity(tableName = "users")
public class User {
@PrimaryKey(autoGenerate = true)
public int id;
@ColumnInfo(name = "name")
public String name;
@ColumnInfo(name = "email")
public String email;
// Конструктори, геттери та сеттери
public User(String name, String email) {
this.name = name;
this.email = email;
}
}
Крок 3: Створення DAO
@Dao
public interface UserDao {
// Використання Flowable для автоматичної підписки на зміни
@Insert
Completable insert(User user);
@Query("SELECT * FROM users")
Flowable<List<User>> getAllUsers();
// Використання Single для отримання одного елемента
@Query("SELECT * FROM users WHERE id = :userId LIMIT 1")
Single<User> getUserById(int userId);
}
Крок 4: Репозиторій
public class UserRepository {
private final UserDao userDao;
public UserRepository(Application application) {
UserDatabase db = UserDatabase.getDatabase(application);
this.userDao = db.userDao();
}
// Отримання всіх користувачів як Flowable
public Flowable<List<User>> getAllUsers() {
return userDao.getAllUsers()
.subscribeOn(Schedulers.io()) // Підписка на IO потоці
.observeOn(AndroidSchedulers.mainThread()); // Оновлення UI на головному потоці
}
// Отримання користувача за id як Single
public Single<User> getUserById(int id) {
return userDao.getUserById(id)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
// Вставка користувача
public Completable insertUser(User user) {
return userDao.insert(user)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
}
Крок 5: ViewModel
public class UserViewModel extends AndroidViewModel {
private final UserRepository repository;
private final LiveData<List<User>> allUsers;
public UserViewModel(Application application) {
super(application);
repository = new UserRepository(application);
allUsers = new LiveData<List<User>>() {
@Override
protected void onActive() {
super.onActive();
repository.getAllUsers().subscribe(new Observer<List<User>>() {
@Override
public void onChanged(List<User> users) {
setValue(users); // оновлення LiveData з отриманими даними
}
});
}
};
}
public LiveData<List<User>> getAllUsers() {
return allUsers;
}
public void addUser(User user) {
repository.insertUser(user).subscribe();
}
}
Крок 6: Інтеграція з UI (Activity)
public class UserActivity extends AppCompatActivity {
private UserViewModel userViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user);
// ініціалізація ViewModel
userViewModel = new ViewModelProvider(this).get(UserViewModel.class);
// Підписка на LiveData
userViewModel.getAllUsers().observe(this, users -> {
// Оновлення UI при зміні даних
// Наприклад, оновлення RecyclerView
});
// Додавання користувача через ViewModel
User newUser = new User("John Doe", "john.doe@example.com");
userViewModel.addUser(newUser);
}
}
Пояснення:
- Dao:
- Використовуємо
Flowableдля отримання списку користувачів, щоб автоматично оновлювати UI при змінах. - Використовуємо
Singleдля запиту одного елемента. - Використовуємо
Completableдля операцій, що не повертають результат, але можуть завершитись з помилкою (вставка).
- Використовуємо
- Репозиторій:
- Зберігає доступ до
Daoі надає методи для взаємодії з даними. Використовуємо RxJava для асинхронних операцій.
- Зберігає доступ до
- ViewModel:
- Отримує дані через репозиторій та підписується на зміни даних через RxJava (через LiveData).
- UI:
- Використовуємо
LiveDataдля оновлення UI при зміні даних.
- Використовуємо
Переваги:
- RxJava дозволяє легко працювати з асинхронними операціями.
- Flowable автоматично оновлює UI при зміні даних у базі.
- Single для отримання одного результату.
Документація
https://developer.android.com/training/data-storage/room/async-queries#java