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?
Comme dit ici , vous pouvez utiliser Maybe
ou Single
pour ce cas:
@Query("SELECT * FROM Users WHERE id = :userId")
Maybe<User> getUserById(String userId);
Voici ce qui se passe:
@Query("SELECT * FROM Users WHERE id = :userId")
Single<User> getUserById(String userId);
Voici quelques scénarios:
Il a été ajouté dans la version 1.0.0-alpha5 .
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)
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);
}
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);
}
Où 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.