web-dev-qa-db-fra.com

Comment gérer aucun résultat avec Android Room et RxJava 2?

J'ai une base de données avec table contact et je veux vérifier s'il y a contact avec un numéro de téléphone.

@Query("SELECT * FROM contact WHERE phone_number = :number")
Flowable<Contact> findByPhoneNumber(int number);

J'ai RxJava 2 Composite jetable avec une déclaration ci-dessus pour vérifier s'il y a contact avec le numéro de téléphone.

disposable.add(Db.with(context).getContactsDao().findByPhoneNumber(phoneNumber)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribeWith(new DisposableSubscriber<Contact>() {
                @Override
                public void onNext(final Contact contact) {
                    Log.d("TAG", "phone number fined");
                    Conversation conversation;
                    if(contact != null){
                        conversation = Db.with(context).getConversationsDao().findBySender(contact.getContactId());
                        if(conversation != null){
                            conversation.setUpdatedAt(Utils.getDateAndTimeNow());
                            saveConversation(contact, conversation, context, text, phoneNumber, false);
                        } else {
                            conversation = getConversation(contact, contact.getPhoneNumber());
                            saveConversation(contact, conversation, context, text, phoneNumber, true);
                        }
                    } else {
                        conversation = Db.with(context).getConversationsDao().findByPhone(phoneNumber);
                        if(conversation != null){
                            conversation.setUpdatedAt(Utils.getDateAndTimeNow());
                            saveConversation(contact, conversation, context, text, phoneNumber, false);
                        } else {
                            conversation = getConversation(contact, phoneNumber);
                            saveConversation(contact, conversation, context, text, phoneNumber, true);
                        }
                    }
                }

                @Override
                public void onError(Throwable t) {
                    Log.d("TAG", "find phone number throwable");
                    Toast.makeText(context, t.getLocalizedMessage(), Toast.LENGTH_LONG).show();
                }

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

Cela fonctionne bien si la requête peut trouver le contact avec le numéro de téléphone requis, mais s'il y a un résultat, il ne se passe rien.

Voici deux cas de test que j'ai écrits et ils fonctionnent bien:

@RunWith(AndroidJUnit4.class)
public class ContactsTest {

    private AppDatabase db;

    @Rule
    public InstantTaskExecutorRule instantTaskExecutorRule =
            new InstantTaskExecutorRule();

    @Before
    public void initDb() throws Exception {
        db = Room.inMemoryDatabaseBuilder(
                InstrumentationRegistry.getContext(),
                AppDatabase.class)
                // allowing main thread queries, just for testing
                .allowMainThreadQueries()
                .build();
    }

    @After
    public void close(){
        db.close();
    }

    @Test
    public void insertAndFindTest(){
        final Contact contact = new Contact();
        contact.setName("Test");
        contact.setPhoneNumber(555);
        db.contactsDao()
                .insert(contact);

        db.contactsDao().findByPhoneNumber(contact.getPhoneNumber())
                .test()
                .assertValue(new Predicate<Contact>() {
                    @Override
                    public boolean test(@NonNull Contact savedContact) throws Exception {
                        if(savedContact.getPhoneNumber() == contact.getPhoneNumber()){
                            return true;
                        }
                        return false;
                    }
                });
    }

    @Test
    public void findNoValues(){
        db.contactsDao().findByPhoneNumber(333)
                .test()
                .assertNoValues();
    }

}

Comment puis-je résoudre ce problème?

14
Zookey

Comme dit ici , vous pouvez utiliser Maybe ou Single pour ce cas:

Peut être

@Query("SELECT * FROM Users WHERE id = :userId")
Maybe<User> getUserById(String userId);

Voici ce qui se passe:

  • Lorsqu'il n'y a aucun utilisateur dans la base de données et que la requête ne renvoie aucune ligne, Peut-être se terminera.
  • Lorsqu'il y a un utilisateur dans la base de données, Maybe déclenchera onSuccess et il se terminera.
  • Si l'utilisateur est mis à jour après la fin de Peut-être, rien ne se produit.

Célibataire

@Query("SELECT * FROM Users WHERE id = :userId")
Single<User> getUserById(String userId);

Voici quelques scénarios:

  • Lorsqu'il n'y a aucun utilisateur dans la base de données et que la requête ne renvoie aucune ligne, Single déclenche onError (EmptyResultSetException.class)
  • Lorsqu'il y a un utilisateur dans la base de données, Single déclenche onSuccess.
  • Si l'utilisateur est mis à jour après l'appel de Single.onComplete, rien ne se passe, car le flux est terminé.

Il a été ajouté dans la version 1.0.0-alpha5 .

24
J-rooft

Si vous ne souhaitez utiliser votre entité qu'une seule fois, Single ou Maybe suffit. Mais si vous voulez observer si votre requête est mise à jour, vous pouvez utiliser Flowable et envelopper votre objet dans List, donc quand il n'y a aucun résultat, vous obtiendrez une liste vide, et après cela, la base de données est mis à jour, vous obtiendrez un autre événement avec votre résultat dans la liste.

Code

@Query("SELECT * FROM contact WHERE phone_number = :number LIMIT 1")
Flowable<List<Contact>> findByPhoneNumber(int number)

Je pense que c'est utile dans certains scénarios. L'inconvénient est que vous devez accéder à un objet comme resultList.get(0)

12

Lorsque vous utilisez Flowable (et LiveData aussi) comme valeur de retour dans votre classe Dao, votre requête n'arrête jamais d'émettre des données car Room surveille les tables pour les modifications de données. Citant la documentation officielle:

De plus, si la réponse est un type de données observable, tel que Flowable ou LiveData, Room surveille toutes les tables référencées dans la requête pour invalidation.

Je ne sais pas quelle est la meilleure façon de gérer une telle situation, mais ce qui a fonctionné pour moi était un bon ancien opérateur .timeout(). Veuillez consulter le test suivant et suivre les commentaires:

@Test
public void shouldCompleteIfForced() throws InterruptedException {
    // given
    TestScheduler testScheduler = new TestScheduler();

    // when asking db for non existent project
    TestSubscriber<Project> test = projectDao.getProject("non existent project")
            .timeout(4, TimeUnit.SECONDS, testScheduler)
            .test();

    // then hang forever waiting for first emission which will never happen
    // as there is no such project
    test.assertNoValues();
    test.assertNotComplete();
    test.assertNoErrors();

    // when time passes and we trigger timeout() operator
    testScheduler.advanceTimeBy(10, TimeUnit.SECONDS);

    // then finally break stream with TimeoutException error ...
    test.assertError(TimeoutException.class);
}
4
pelotasplus

Je suppose que vous pouvez également utiliser le wrapper avec Single. Comme:

public class QueryResult<D> {
            public D data;
            public QueryResult() {}

            public QueryResult(D data) {
                this.data = data;
            }

            public boolean isEmpty(){
                return data != null;
            }
 }

Et utilisez-le comme:

public Single<QueryResult<Transaction>> getTransaction(long id) {
            return createSingle(() -> database.getTransactionDao().getTransaction(id))
                    .map(QueryResult::new);
}

createAsyncSingle:

protected <T> Single<T> createSingle(final Callable<T> func) {
            return Single.create(emitter -> {
                try {
                    T result = func.call();
                    emitter.onSuccess(result);

                } catch (Exception ex) {
                    Log.e("TAG", "Error of operation with db");
                }
            });
}

N'oubliez pas d'utiliser le fil IO.

1
Djek-Grif