I have a SearchView that executes a network request to search for some tracks and then populates a RecylerView with the results. I have found this code which works fine.
I have already integrated the RecyclerView EmptyView through the Adapter but now I am trying to integrate the LoadingView(Progress) and ErrorView inside this code. I tried to put the LoadingView(ProgressBar) on Visibility True inside the concatMap but got the error that the “Only the original thread that created a view hierarchy can touch its views.” which can be solved running that on the MainThread but I am sure there is a better way to do this.
Can someone have a better idea where and how the logic about show/hide the ErrorView and LoadingView can be integrated into this code?
I am using also RxBinding. Maybe also using RxRecyclerView would be a good idea?
RxSearchView.queryTextChanges(searchView).
filter(charSequence ->
!TextUtils.isEmpty(charSequence))
.throttleLast(100, TimeUnit.DAYS.MILLISECONDS)
.debounce(200, TimeUnit.MILLISECONDS)
.onBackpressureLatest()
.concatMap(searchTerm ->
{
return searchTracks(searchTerm).
.subscribeOn(Schedulers.io())
.onErrorResumeNext(throwable1 -> {
//handle error somehow, change UI
return Observable.empty();
}
);
}
)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(tracks -> {
populateTracks(tracks);
}
});
}, throwable -> {
//show errorView
});
This here was my complete solution without stripping code for the presentation.
RxSearchView.queryTextChanges(searchView)
.skip(1)
.doOnNext(charSequence -> Log.v(TAG, "searching: " + charSequence))
.throttleLast(100, TimeUnit.MILLISECONDS)
.debounce(200, TimeUnit.MILLISECONDS)
.onBackpressureLatest()
.observeOn(AndroidSchedulers.mainThread())
.filter(charSequence -> {
final boolean empty = TextUtils.isEmpty(charSequence);
if (empty) {
Log.v(TAG, "empty view");
mAdapter.clear();
}
return !empty;
})
.concatMap(charSequence -> {
Log.v(TAG, "requesting " + charSequence);
return onErrorResumeNext(
mGitApiService.searchRepositoriesSingle(charSequence.toString())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()),
throwable -> {
try {
throw throwable;
} catch (HttpException httpException) {
showEmptyErrorView(httpException.message());
} catch (Throwable other) {
showEmptyErrorView(other.getMessage());
other.printStackTrace();
}
return Observable.empty();
});
})
.doOnNext(charSequence -> Log.v(TAG, "got data"))
.subscribe(response -> {
showRepositories(response.getItems());
}, throwable -> {
throwable.printStackTrace();
showEmptyErrorView(throwable.getMessage());
});
so basically whenever you touch your view you have to call .observeOn(AndroidSchedulers.mainThread())
Related
I am using RXAndroidBle library to connect and read data from the BLE device. I have set the establishConnection function to true i.e. auto-connect to true. when the BleAlreadyConnectedException is occurring I want to capture that exception and restart the flow of reading data because every time disposing and connecting to BLE device is creating issues. so better to keep the connection alive and re-read the data.
In onErrorResumeNext i to re-call the functions writeStatus, readModelInfo,getReadings and so on. Now sure how would I achieve it.
device.establishConnection(true)
.flatMap(rxBleConnection -> {
rxBleConnection.discoverServices();
mRxBleConnection = rxBleConnection;
return Observable.just(rxBleConnection);
})
.flatMap(rxBleConnection -> rxBleConnection.setupNotification(TSDictionary.BATTERY_LEVEL,NotificationSetupMode.QUICK_SETUP).flatMap(it->it))
.flatMap(bytes -> writeStatus())
.flatMap(bytes->readModelInfo(bytes))
.flatMap(bytes -> getReadings(bytes))
.doOnNext(data->initializeErrorHistory(data))
.flatMap(data->getSequenceSize())
.flatMap(length ->getOperationInfo(length))
.doOnNext(data->initializeOperationInfo(data))
.onErrorResumeNext(new Function<Throwable, ObservableSource<? extends ArrayList<Map<Integer, TSDictionaryMetaData>>>>() {
#Override
public ObservableSource<? extends ArrayList<Map<Integer, TSDictionaryMetaData>>> apply(#io.reactivex.annotations.NonNull Throwable throwable) throws Exception {
if(throwable instanceof BleAlreadyConnectedException){
// i want to RECALL/restart the function call
// writeStatus ,readModelInfo,getReadings, initializeErrorHistory
// getSequenceSize , getOperationInfo, initializeOperationInfo
}
return null;
}
})
.subscribe(data -> {
}, e -> {
e.printStackTrace();
});
Put the onErrorResumeNext closer to the connection code.
device.establishConnection(true)
.doOnNext(rxBleConnection -> {
rxBleConnection.discoverServices();
mRxBleConnection = rxBleConnection;
})
.onErrorResumeNext(throwable -> {
if (throwable instanceof BleAlreadyConnectedException) {
return Observable.just(mRxBleConnection);
}
return Observable.error(throwable);
})
.flatMap(rxBleConnection ->
rxBleConnection.setupNotification(TSDictionary.BATTERY_LEVEL,
NotificationSetupMode.QUICK_SETUP)
.flatMap(it->it)
)
.flatMap(bytes -> writeStatus())
.flatMap(bytes->readModelInfo(bytes))
.flatMap(bytes -> getReadings(bytes))
.doOnNext(data->initializeErrorHistory(data))
.flatMap(data->getSequenceSize())
.flatMap(length ->getOperationInfo(length))
.doOnNext(data->initializeOperationInfo(data))
.subscribe(data -> {
}, e -> {
e.printStackTrace();
});
I have an array of Objects and I want to filter that array based on the text user types on an EditText android view.
What I thought it that I should try and convert the array of POJOs to an Observable of Strings and this is what I did :
Observable<String> professionsObservable = Observable.fromArray(((GetStartedActivity) getActivity()).professions)
.map(profession -> {
if (profession.getName().length() > 0) {
professionsNameList.add(capitalizeFirstLetter(profession.getName()));
}
return professionsNameList;
})
.flatMapIterable(items -> items);
Now I want to combine the text from the EditText with the `professionsObservable I posted above.
This is the code I'm using :
RxTextView.textChangeEvents(etProfession)
.doOnEach(notif -> {
if (etProfession.getText().toString().trim().length() > 0) {
etCompany.setVisibility(GONE);
etIndustry.setVisibility(GONE);
} else {
etCompany.setVisibility(VISIBLE);
etIndustry.setVisibility(VISIBLE);
}
})
.debounce(EDITTEXT_DELAY, TimeUnit.MILLISECONDS)
.skip(1)
.map(textChangeEvent -> textChangeEvent.text().toString())
.switchMap(search -> {
return professionsObservable
.filter(profession -> {
return profession.toLowerCase().startsWith(search);
});
}
)
.toList()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
filteredProfessions -> {
Timber.i("NOT ENTERING");
rvProfession.setVisibility(VISIBLE);
professionAdapter.addItems(filteredProfessions);
},
throwable -> Log.i("THROW", "PROFESSIONS ", throwable));
I'm using map operator to turn the text change event to a String and then for each String I get from the stream I'm using switchMap (cause I don't care for results from previous searches). Then I compose all Strings to a List with toList. The problem is that it never reaches the subscribe call while I have a lot of strings in the initial Array I used and I do type text that fits the condition of the filter operator.
Is it something that I might missing here ?
EDIT : I updated my code to :
RxTextView.textChangeEvents(etProfession)
.doOnEach(notif -> {
if (etProfession.getText().toString().trim().length() > 0) {
etCompany.setVisibility(GONE);
etIndustry.setVisibility(GONE);
} else {
etCompany.setVisibility(VISIBLE);
etIndustry.setVisibility(VISIBLE);
}
})
.subscribeOn(AndroidSchedulers.mainThread())
.debounce(EDITTEXT_DELAY, TimeUnit.MILLISECONDS)
.skip(1)
.map(textChangeEvent -> textChangeEvent.text().toString())
.flatMap(search -> {
return Observable.fromArray(((GetStartedActivity) getActivity()).professions)
.map(profession -> {
List<String> professionsList = new ArrayList<>();
if (profession.getName().length() > 0) {
professionsList.add(capitalizeFirstLetter(profession.getName()));
}
return professionsList;
})
.flatMapIterable(items -> items)
.filter(profession -> {
if (profession.toLowerCase().startsWith(search.toLowerCase())) {
}
return profession.toLowerCase().startsWith(search.toLowerCase());
});
})
.toList()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
filteredProfessions -> {
rvProfession.setVisibility(VISIBLE);
// professionAdapter.addItems(filteredProfessions);
},
throwable -> Log.i("THROW", "PROFESSIONS ", throwable));
If I remove toList() operator my code works (enters the subscribe call) but if I leave it there it wont. Anyone knows why ?
In my experience, RxBinding requires .subscribeOn(AndroidSchedulers.mainThread()) right after .textChangeEvents() or any other event. So probably you are causing it to fail by adding .subscribeOn(Schedulers.io)
See method .checkMainThread() in https://github.com/JakeWharton/RxBinding/blob/master/rxbinding/src/main/java/com/jakewharton/rxbinding2/internal/Preconditions.java
UPDATE
Also, probably because .onComplete() never comes from upstream, .toList() is never executed.
I'm trying to do a simple search UI, where the text change triggers a search in the service and that gets mapped to a ViewState. It would seem easy, but the following code doesn't work:
queryText.filter { it.length > 3 }
.switchMap { service.search(it) }
.onErrorReturn { SearchResponse(null, it.message) }
.map { SearchViewState(items = it.items, error = it.error) }
.startWith { SearchViewState(loading = true) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { viewState.onNext(it) }
I've no idea what I did wrong, but through debugging I can see that the stream throws a NetworkOnMainThreadException and then terminates so new events are no longer processed.
What is the correct way to do this?
I assume queryText is the source of textchanges which happen on the main thread. Therefore subscribeOn has no effect on it. You should apply subscribeOn to the actual network call:
queryText.filter { it.length > 3 }
.switchMap {
service.search(it)
.subscribeOn(Schedulers.io())
.onErrorReturn { SearchResponse(null, it.message) }
.map { SearchViewState(items = it.items, error = it.error) }
.startWith ( SearchViewState(loading = true) )
}
.observeOn(AndroidSchedulers.mainThread())
.subscribe { viewState.onNext(it) }
In addition, I think you have to do the error recovery and state changes associated with the particular network call, otherwise a failure will stop the entire sequence.
Here's what I am trying to achieve:
I have an API response say myResponse, which contains List of item. I want to iterate through each item through flatMapIterable which works fine. But before doing that I would like to check if API didn't return error or List size is > 0. I am not able to achieve this.
Here's the code what I have tried so far:
// This works & I am able to iterate through each item
myApi.getData()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.flatMapIterable(response -> response.getData().getItems())
.subscribeWith(new DisposableObserver<Mobile>() {
#Override
public void onNext(#NonNull Item item) {
Log.e("TAG", "item is => " + item.getItemName());
}
#Override
public void onError(#NonNull Throwable e) {
}
#Override
public void onComplete() {
Log.e("TAG", "processing completed");
}
});
But I am not sure how should I use map or flatmap before flatMapIterableto check for any error. I am doing something like following without any success:
myApi.getData()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.map(response -> {
if(response.getStatus() == 200) {
List<Item> items = response.getData().getItems();
if(items == null || items.size() < 1)
return Observable.error(new Throwable("No data!"));
return Observable.just(response);
}
return Observable.error(new Throwable(response.getData().getMsg()));
})
.doOnError(Throwable::getMessage)
.flatMapIterable(response -> response.getData().getItems());
Here on flatMapIterable, I get error cannot resolve getData(). because I believe It's not able to identify response as MyResponse.class. I am stuck here, please let me know how can I achieve what I want with RxJava2?
In map(response -> {...}) you have created Observable of MyResponse, that is why it does not compile. Just replace map(response -> {...}) with flatMap(response -> {...}) or add flatMap(r -> r) before flatMapIterable.
While I'm using debounce() ,then fetch data from backend and the data
I want to convert to another data and lastly use toList().
when I'm using toList() nothing happens no any log not in subscribe and error ,without toList() it works and subscribe() method enters as much as I have list of books, I tested the second part of code it without debounce() just getItems() and using toList() it works.
Below is my code the first part with debounce() and itList() which is not working and the second with toList() which works
public Flowable<List<Book>> getItems(String query) {}
textChangeSubscriber
.debounce(300, TimeUnit.MILLISECONDS)
.observeOn(Schedulers.computation())
.switchMap(s -> getItems(s).toObservable())
.flatMapIterable(items -> items)
.map(Book::convert)
.toList()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(books -> {
Log.i("test", "" + books.toString());
}, error -> {
Log.i("test", "" + error);
});
getItems(query).flatMapIterable(items -> items)
.map(Book::convert)
.toList()
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(books -> {
Log.i("test", "" + "" + books.toString());
}, error -> {
Log.i("test", "" + error);
});
toList requires the sequence to terminate which doesn't happen on the outer stream that responds to text events. You should move the processing of the books into the switchMap:
textChangeSubscriber
.map(CharSequence::toString) // <-- text components emit mutable CharSequence
.debounce(300, TimeUnit.MILLISECONDS)
.observeOn(Schedulers.computation())
.switchMap(s ->
getItems(s)
.flatMapIterable(items -> items)
.map(Book::convert)
.toList()
.toFlowable() // or toObservable(), depending on textChangeSubscriber
)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(books -> {
Log.i("test", "" + books.toString());
}, error -> {
Log.i("test", "" + error);
});