Reputation: 2707
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?
Upvotes: 21
Views: 18678
Reputation: 926
I personally prefer using java.util.Optional
. Your code will look like:
@Query("SELECT * FROM contact WHERE phone_number = :number")
Flowable<Optional<Contact>> findByPhoneNumber(int number);
And then
findByPhoneNumber(number).map(optionalContact -> {
if (optionalContact.isPresent()) {
return optionalContact.get();
} else {
// no contact present, use default value or something
}
})
Upvotes: 1
Reputation: 13103
@Query("SELECT * FROM contact WHERE phone_number = :number")
Flowable<List<Contact>> findByPhoneNumber(int number);
then
Optional<Contact> queryPhone(int number) {
findByPhoneNumber(number).map { list ->
if (list.isEmpt()) return Optional.empty() else return Optional.of(list[0])
}
}
as described here
I find it's strange that this behaviour is no where to be found on official docs though
Upvotes: 0
Reputation: 1516
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.
Upvotes: 2
Reputation: 788
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)
Upvotes: 19
Reputation: 1403
As said here, you can use Maybe
or Single
for this case:
@Query("SELECT * FROM Users WHERE id = :userId")
Maybe<User> getUserById(String userId);
Here’s what happens:
@Query("SELECT * FROM Users WHERE id = :userId")
Single<User> getUserById(String userId);
Here are some scenarios:
It was added in version 1.0.0-alpha5.
Upvotes: 38
Reputation: 10052
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);
}
Upvotes: 7