简体   繁体   中英

How to handel no results with Android Room and RxJava 2?

I have database with table contact and I want to check if there is contact with some phone number.

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

I have RxJava 2 Composite disposable with statement from above to check if there is contact with phone number.

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");
                }
            }));

This is working fine if query can find contact with required phone number, but if there is result, it nothing happens.

Here are two test cases that I wrote and they work fine:

@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();
    }

}

How I can solve this?

As said here , you can use Maybe or Single for this case:

Maybe

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

Here's what happens:

  • When there is no user in the database and the query returns no rows, Maybe will complete.
  • When there is a user in the database, Maybe will trigger onSuccess and it will complete.
  • If the user is updated after Maybe was completed, nothing happens.

Single

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

Here are some scenarios:

  • When there is no user in the database and the query returns no rows, Single will trigger onError(EmptyResultSetException.class)
  • When there is a user in the database, Single will trigger onSuccess.
  • If the user is updated after Single.onComplete was called, nothing happens, since the stream was completed.

It was added in version 1.0.0-alpha5 .

If you want to use your entity only once, Single or Maybe is sufficient. But if you want to observe if your query is updated you can use Flowable and wrap your object in List , so when there is no results you will get empty list, and when after that, database is updated you will get another event with your result in list.

Code

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

I believe it's usefull in some scenarios. The drawback is that you have to access object like resultList.get(0)

When you use Flowable (and LiveData too) as a return value in your Dao class then your query never stops emitting data as Room is monitoring tables for data changes. Quoting official documentation:

Furthermore, if the response is an observable data type, such as Flowable or LiveData, Room watches all tables referenced in the query for invalidation.

Not sure what's the best way of handling such situation but what worked for me was a good old .timeout() operator. Please have a look at the following test and follow comments:

@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);
}

I guess you also could use wrapper with Single. Like:

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

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

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

And use it like:

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

Where 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");
                }
            });
}

Don't forget to use IO thread.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM