In MainActivity I make two calls one by one getAddress(), this is code getAddress method:
public class RemoteRepository {
private ApiRequest apiRequest;
private LiveData<List<AddressResponse>> allAddresses = new MutableLiveData<>();
public RemoteRepository() {
apiRequest = RetrofitRequest.getInstance().create(ApiRequest.class);
}
public LiveData<List<AddressResponse>> getAddresses() {
return allAddresses;
}
public void getAddress(double query1, double query2) {
apiRequest.getAddress(query1, query2)
.enqueue(new Callback<AddressResponse>() {
#Override
public void onResponse(Call<AddressResponse> call, Response<AddressResponse> response) {
if(response.body() != null) {
allAddresses.getValue().add(response.body());
}
}
#Override
public void onFailure(Call<AddressResponse> call, Throwable t) {
}
});
}
}
Unfortunately, I get the following error: java.lang.NullPointerException: Attempt to invoke interface method 'boolean java.util.List.add(java.lang.Object)' on a null object reference.
Is there any way to add data from each call to LiveData<List<AddressResponse>> allAddresses ?
I believe this what you need to change this
allAddresses.getValue().add(response.body());
to this:
allAddresses.postValue(response.body())
If want to post or add a task to the main thread of the application whenever there is a change in the value then use:
allAddresses.postValue(response.body())
While the setValue() method is used to set the changed value i.e. the change value will be dispatched to the active observer of the application:
allAddresses.setValue(response.body())
First it's better to create instance of your LiveData in getter method not in top of your class like this:
Public LiveData<List<ResponseAddress> getAddress(){
if(allAddress == Null)
allAddress = new MutableLiveData ();
return allAddress;
}
Then ensure your response body is not null :
if(response.body() != null ){
// Replacement Bellow
}
And in your if statement body just Replace :
allAddresses.getValue().add(response.body());
With this :
allAddresses.setValue(response.body());
Related
I'm facing this issue on my project. I receive from api call a response like:
{
"aResponse": {
"listOfSomething": [
//here some data
]
}
}
And relative data classes are
data class ResponseClass(
val aResponse : AResponse
)
data class AResponse(
val listOfSomething : List<String>
)
Not it happen that when "listOfSomething" is empty, i receive this response:
{
"aResponse": {
"listOfSomething": ""
}
}
that throws (of course) the exception
com.squareup.moshi.JsonDataException: Expected BEGIN_OBJECT but was STRING
How can i solve it?
You are getting this error as when there is data you get array and when no data get string which is wrong in retrofit.
If there are no data insise listOfSomething then ask backend to send empty array instead of string.
{
"aResponse": {
"listOfSomething": []
}
}
instead of
{
"aResponse": {
"listOfSomething": ""
}
}
If your json result is gonna change depends of the result, first of all your backend is doing a bad job, then you have to "hack" a little bit your app to adapt the code...
Your POJO class should be :
data class MyResponse(
val aResponse: AResponse
)
data class AResponse(
val listOfSomething: Any
)
You can declare it as Any which is not a good practise, but it's a workaround to make it work according to your backend. Is like in Java adding Object
Then you can do something in your onResponse
#Override
fun onResponse(response: Response<MyResponse>) {
if (response.isSuccess()) {
if (response.listOfSomething is String) {
//do something with the String
} else {
//do something with the List
}
}
}
First, your backend implementation is wrong.
You should not send an empty string to represent an empty array.
If you can't fix it on backend side because the API are not under your control, you can try with something like this:
public final class IgnoreStringForArrays implements JsonAdapter.Factory {
#Retention(RetentionPolicy.RUNTIME)
#JsonQualifier
public #interface IgnoreJsonArrayError {
}
#Override
public JsonAdapter<?> create(Type type, Set<? extends Annotation> annotations, Moshi moshi) {
if (annotations != null && annotations.size() > 0) {
for (Annotation annotation : annotations) {
if (annotation instanceof IgnoreJsonArrayError) {
final JsonAdapter<Object> delegate = moshi.nextAdapter(this, type, Types.nextAnnotations(annotations, IgnoreJsonArrayError.class));
return new JsonAdapter<Object>() {
#Override
public Object fromJson(JsonReader reader) throws IOException {
JsonReader.Token peek = reader.peek();
if (peek != JsonReader.Token.BEGIN_ARRAY) {
reader.skipValue();
return null;
}
return delegate.fromJson(reader);
}
#Override
public void toJson(JsonWriter writer, Object value) throws IOException {
delegate.toJson(writer, value);
}
};
}
}
}
return null;
}
}
like suggested here: https://github.com/square/moshi/issues/295#issuecomment-299636385
And then annotate your listOfSomething with: IgnoreJsonArrayError annotation
What are the correct concepts and working of observables and observers in RxJava. I get confused by the words literal meaning. Whenever I change the values of observables its corresponding observers is not getting invoked i.e. I will explain this situation a bit more deeply, initially when I assign an observable with a list of strings(List list) and subscribe it to an observer, observer works perfectly but after that ,when I change the values of list(for example adding more String values to list) ...the observer's on next should automatically be invoked right.. but it isn't. Trying to implement in Android natively . I will be happy for some helps.
Observables work with three methods from Observer: onNext, onError and onCompleted. When you make Observable from a list and you subscribe it Observable will emit those values using onNext method and when it's finished it will call onCompleted method.
You can't change values that Observable is emitting by changing list you gave to some Observable operator. What would be you desired behaviour. Should Observable emit all elements on list change or should it emit only new changes.
This observable will emit all changes to collection made trough setCollection method:
public class CollectionObservable<T> extends Observable<T> {
private Collection<T> collection;
private List<Observer<? super T>> observers;
public CollectionObservable(Collection<T> collection) {
if (collection != null) {
this.collection = collection;
}
this.observers = new ArrayList<>(2);
}
public Collection<T> getCollection() {
return collection;
}
public void setCollection(Collection<T> collection) {
this.collection = collection;
emitValuesToAllObserver();
}
public void complete() {
if (this.collection != null) {
for (Observer<? super T> observer : this.observers) {
observer.onComplete();
}
}
}
#Override
protected void subscribeActual(Observer<? super T> observer) {
this.observers.add(observer);
emitValues(observer);
}
private void emitValuesToAllObserver() {
for (Observer<? super T> observer : this.observers) {
emitValues(observer);
}
}
private void emitValues(Observer<? super T> observer) {
if (this.collection != null) {
for (T obj : this.collection) {
observer.onNext(obj);
}
}
}
}
Note that in order to finish you manually have to call complete method.
My question is related to ViewModel second time returns null wherein I am not getting callback inobserve function if I make a repeated call to server. Following is the code I am using -
#Singleton
public class NetworkInformationViewModel extends AndroidViewModel {
private LiveData<Resource<NetworkInformation>> networkInfoObservable;
private final APIClient apiClient;
#Inject
NetworkInformationViewModel(#NonNull APIClient apiClient, #NonNull Application application) {
super(application);
this.apiClient = apiClient;
getNetworkInformation();
}
public LiveData<Resource<NetworkInformation>> getNetworkInfoObservable() {
return networkInfoObservable;
}
// making API calls and adding it to Observable
public void getNetworkInformation() {
networkInfoObservable = apiClient.getNetworkInformation();
}
}
In Activity, the ViewModel is defined as followed -
final NetworkInformationViewModel networkInformationViewModel =
ViewModelProviders.of(this, viewModelFactory).get(NetworkInformationViewModel.class);
observeViewModel(networkInformationViewModel);
The observeViewModel function is used to add observable on ViewModel.
public void observeViewModel(final NetworkInformationViewModel networkInformationViewModel) {
networkInformationViewModel.getNetworkInfoObservable()
.observe(this, networkInformationResource -> {
if (networkInformationResource != null) {
if (networkInformationResource.status == APIClientStatus.Status.SUCCESS) {
Timber.d("Got network information data");
} else {
final Throwable throwable = networkInformationResource.throwable;
if (throwable instanceof SocketTimeoutException) {
final NetworkInformation networkInformation = networkInformationResource.data;
String error = null;
if (networkInformation != null) {
error = TextUtils.isEmpty(networkInformation.error) ? networkInformation.reply : networkInformation.error;
}
Timber.e("Timeout error occurred %s %s", networkInformationResource.message, error);
} else {
Timber.e("Error occurred %s", networkInformationResource.message);
}
if (count != 4) {
networkInformationViewModel.getNetworkInformation();
count++;
// Uncommenting following line enables callback to be received every time
//observeViewModel(networkInformationViewModel);
}
}
}
});
}
Uncommenting the following line in above function allows the callback to come everytime, but there has to be a proper way of doing this.
//observeViewModel(networkInformationViewModel);
Please note:-
I don't need RxJava implementation for implementing this.
Right now in getNetworkInformation() you are:
Creating a new LiveData
Updating the the LiveData using setValue
Instead, you should have a single LiveData for APIClient created as a member variable, then in getNetworkInformation() just update that member LiveData.
More generally, your APIClient is a data source. For data sources, you can have them contain member LiveData objects that update when the data changes. You can provide getters to those LiveData objects to make them accessible in ViewModels, and ultimately listen to them in your Activities/Fragments. This is similar how you might take another data source, such as Room, and listen to a LiveData returned by Room.
So the code in this case would look like:
#Singleton
public class APIClient {
private final MutableLiveData<Resource<NetworkInformation>> mNetworkData = new MutableLiveData<>(); // Note this needs to be MutableLiveData so that you can call setValue
// This is basically the same code as the original getNetworkInformation, instead this returns nothing and just updates the LiveData
public void fetchNetworkInformation() {
apiInterface.getNetworkInformation().enqueue(new Callback<NetworkInformation>() {
#Override
public void onResponse(
#NonNull Call<NetworkInformation> call, #NonNull Response<NetworkInformation> response
) {
if (response.body() != null && response.isSuccessful()) {
mNetworkData.setValue(new Resource<>(APIClientStatus.Status.SUCCESS, response.body(), null));
} else {
mNetworkData.setValue(new Resource<>(APIClientStatus.Status.ERROR, null, response.message()));
}
}
#Override
public void onFailure(#NonNull Call<NetworkInformation> call, #NonNull Throwable throwable) {
mNetworkData.setValue(
new Resource<>(APIClientStatus.Status.ERROR, null, throwable.getMessage(), throwable));
}
});
}
// Use a getter method so that you can return immutable LiveData since nothing outside of this class will change the value in mNetworkData
public LiveData<Resource<NetworkInformation>> getNetworkData(){
return mNetworkData;
}
}
Then in your ViewModel...
// I don't think this should be a Singleton; ViewModelProviders will keep more than one from being instantiate for the same Activity/Fragment lifecycle
public class SplashScreenViewModel extends AndroidViewModel {
private LiveData<Resource<NetworkInformation>> networkInformationLiveData;
#Inject
SplashScreenViewModel(#NonNull APIClient apiClient, #NonNull Application application) {
super(application);
this.apiClient = apiClient;
// Initializing the observable with empty data
networkInfoObservable = apiClient.getNetworkData()
}
public LiveData<Resource<NetworkInformation>> getNetworkInfoObservable() {
return networkInformationLiveData;
}
}
Your activity can be the same as you originally coded it; it will just get and observe the LiveData from the ViewModel.
So what is Transformations.switchMap for?
switchMap isn't necessary here because you don't need to change the underlying LiveData instance in APIClient. This is because there's really only one piece of changing data. Let's say instead your APIClient needed 4 different LiveData for some reason, and you wanted to change which LiveData you observed:
public class APIClient {
private MutableLiveData<Resource<NetworkInformation>> mNetData1, mNetData2, mNetData3, mNetData4;
...
}
Then let's say that your fetchNetworkInformation would refer to different LiveData to observe depending on the situation. It might look like this:
public LiveData<Resource<NetworkInformation>> getNetworkInformation(int keyRepresentingWhichLiveDataToObserve) {
LiveData<Resource<NetworkInformation>> currentLiveData = null;
switch (keyRepresentingWhichLiveDataToObserve) {
case 1:
currentLiveData = mNetData1;
break;
case 2:
currentLiveData = mNetData2;
break;
//.. so on
}
// Code that actually changes the LiveData value if needed here
return currentLiveData;
}
In this case the actual LiveData coming from getNetworkInformation is changes, and you're also using some sort of parameter to determine which LiveData you want. In this case, you'd use a switchMap, because you want to make sure that the observe statement you called in your Activity/Fragment observes the LiveData returned from your APIClient, even if you change the underlying LiveData instance. And you don't want to call observe again.
Now this is a bit of an abstract example, but it's basically what your calls to a Room Dao do -- if you have a Dao method that queries your RoomDatabase based on an id and returns a LiveData, it will return a different LiveData instance based on the id.
I didn't met the same issue, but i came across a similar thing where the number of observers were increasing each time i was saving the data in db. The way i debugged was how many instances or different instances of observers were getting invoked and i came to know that when you are fetching the live data from view model it needs to be checked for non null or you can say only 1 instance is being returned -
private LiveData<T> data;
public LiveData<T> getLiveData(){
if(data ==null){
data = //api call or fetch from db
}
return data;
}
Before i was simply returning the data object and then after checking the source i came to the conclusion that livedata automatically updates your object and everytime without the null check new instance was getting created and new observers were getting attached. Someone can correct me if my understanding regarding livedata is wrong.
I have already updated the linked question's answer. Re-posting here since I have placed a bounty on the question and hopefully someone will verify that this is the proper way to handle the issue.
Following is the updated working solution -
#Singleton
public class SplashScreenViewModel extends AndroidViewModel {
private final APIClient apiClient;
// This is the observable which listens for the changes
// Using 'Void' since the get method doesn't need any parameters. If you need to pass any String, or class
// you can add that here
private MutableLiveData<Void> networkInfoObservable;
// This LiveData contains the information required to populate the UI
private LiveData<Resource<NetworkInformation>> networkInformationLiveData;
#Inject
SplashScreenViewModel(#NonNull APIClient apiClient, #NonNull Application application) {
super(application);
this.apiClient = apiClient;
// Initializing the observable with empty data
networkInfoObservable = new MutableLiveData<Void>();
// Using the Transformation switchMap to listen when the data changes happen, whenever data
// changes happen, we update the LiveData object which we are observing in the MainActivity.
networkInformationLiveData = Transformations.switchMap(networkInfoObservable, input -> apiClient.getNetworkInformation());
}
/**
* Function to get LiveData Observable for NetworkInformation class
* #return LiveData<Resource<NetworkInformation>>
*/
public LiveData<Resource<NetworkInformation>> getNetworkInfoObservable() {
return networkInformationLiveData;
}
/**
* Whenever we want to reload the networkInformationLiveData, we update the mutable LiveData's value
* which in turn calls the `Transformations.switchMap()` function and updates the data and we get
* call back
*/
public void setNetworkInformation() {
networkInfoObservable.setValue(null);
}
}
The Activity's code will be updated as -
final SplashScreenViewModel splashScreenViewModel =
ViewModelProviders.of(this, viewModelFactory).get(SplashScreenViewModel.class);
observeViewModel(splashScreenViewModel);
// This function will ensure that Transformation.switchMap() function is called
splashScreenViewModel.setNetworkInformation();
Watch her droidCon NYC video for more information on LiveData. The official Google repository for LiveData is https://github.com/googlesamples/android-architecture-components/ look for GithubBrowserSample project.
The apiClient.getNetworkInformation() call doesn't need it any parameters to get additional information. Hence, the 'Void' added in MutableLiveData.
public LiveData<Resource<NetworkInformation>> getNetworkInformation() {
final MutableLiveData<Resource<NetworkInformation>> data = new MutableLiveData<>();
apiInterface.getNetworkInformation().enqueue(new Callback<NetworkInformation>() {
#Override
public void onResponse(
#NonNull Call<NetworkInformation> call, #NonNull Response<NetworkInformation> response
) {
if (response.body() != null && response.isSuccessful()) {
data.setValue(new Resource<>(APIClientStatus.Status.SUCCESS, response.body(), null));
} else {
data.setValue(new Resource<>(APIClientStatus.Status.ERROR, null, response.message()));
}
}
#Override
public void onFailure(#NonNull Call<NetworkInformation> call, #NonNull Throwable throwable) {
data.setValue(
new Resource<>(APIClientStatus.Status.ERROR, null, throwable.getMessage(), throwable));
}
});
return data;
}
Is there any way to enforce non-nullability of LiveData values? Default Observer implementation seems to have #Nullable annotation which forces an IDE to suggest that the value might be null and should be checked manually:
public interface Observer<T> {
/**
* Called when the data is changed.
* #param t The new data
*/
void onChanged(#Nullable T t);
}
A new option is available if you use Kotlin. You can replace LiveData with StateFlow. It is more suitable for Kotlin code and provides built-in null safety.
Instead of using:
class MyViewModel {
val data: LiveData<String> = MutableLiveData(null) // the compiler will allow null here!
}
class MyFragment: Fragment() {
model.data.observe(viewLifecycleOwner) {
// ...
}
}
You can use:
class MyViewModel {
val data: StateFlow<String> = MutableStateFlow(null) // compilation error!
}
class MyFragment: Fragment() {
lifecycleScope.launch {
model.data.collect {
// ...
}
}
}
StateFlow is part of coroutines and to use the lifecycleScope you need to add the lifecycle-extensions dependency:
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
Note that this API has been experimental before coroutines 1.4.0.
Here's some additional reading about replacing LiveData with StateFlow.
As Igor Bubelov pointed out, another advantage of this approach is that it's not Android specific so it can be used in shared code in multiplatform projects.
If you use Kotlin, you can create much nicer non-null observe function with extension. There is an article about it. https://medium.com/#henrytao/nonnull-livedata-with-kotlin-extension-26963ffd0333
It's possible to do it safely only if you are in control of the code which sets the data because you'll also have to wrap the LiveData class. This way the data setting methods will be protected with #NonNull and you can be sure that the data has already been checked before reaching the Observer.
Wrap the LiveData class:
public class NonNullMutableLiveData<T> extends MutableLiveData<T> implements NonNullLiveData<T> {
private final #NonNull T initialValue;
public NonNullMutableLiveData(#NonNull T initialValue) {
this.initialValue = initialValue;
}
#Override
public void postValue(#NonNull T value) {
super.postValue(value);
}
#Override
public void setValue(#NonNull T value) {
super.setValue(value);
}
#NonNull
#Override
public T getValue() {
//the only way value can be null is if the value hasn't been set yet.
//for the other cases the set and post methods perform nullability checks.
T value = super.getValue();
return value != null ? value : initialValue;
}
//convenience method
//call this method if T is a collection and you modify it's content
public void notifyContentChanged() {
postValue(getValue());
}
public void observe(#NonNull LifecycleOwner owner, #NonNull NonNullObserver<T> observer) {
super.observe(owner, observer.getObserver());
}
}
Create an interface for exposing as immutable:
public interface NonNullLiveData<T> {
#NonNull T getValue();
void observe(#NonNull LifecycleOwner owner, #NonNull NonNullObserver<T> observer);
}
Finally, wrap the Observer:
//not implementing Observer<T> to make sure this class isn't passed to
//any class other than NonNullMutableLiveData.
public abstract class NonNullObserver<T> {
public Observer<T> getObserver() {
return new ActualObserver();
}
public abstract void onValueChanged(#NonNull T t);
private class ActualObserver implements Observer<T> {
#Override
public void onChanged(#Nullable T t) {
//only called through NonNullMutableLiveData so nullability check has already been performed.
//noinspection ConstantConditions
onValueChanged(t);
}
}
}
Now you can create your data like this:
class DataSource {
private NonNullMutableLiveData<Integer> data = new NonNullMutableLiveData<>(0);
public NonNullLiveData<Integer> getData() {
return data;
}
}
And use it like this:
dataSource.getData().observe(this, new NonNullObserver<Integer>() {
#Override
public void onValueChanged(#NonNull Integer integer) {
}
});
Completely null safe.
While there a few things you can do, it is your responsibility to make sure you don't pass null to the LiveData. In addition to that, every 'solution' is more a suppression of the warning, which can be dangerous (if you do get a null value, you might not handle it and Android Studio will not warn you).
Assert
You can add assert t != null;. The assert will not be executed on Android, but Android Studio understands it.
class PrintObserver implements Observer<Integer> {
#Override
public void onChanged(#Nullable Integer integer) {
assert integer != null;
Log.d("Example", integer.toString());
}
}
Suppress the warning
Add an annotation to suppress the warning.
class PrintObserver implements Observer<Integer> {
#Override
#SuppressWarnings("ConstantConditions")
public void onChanged(#Nullable Integer integer) {
Log.d("Example", integer.toString());
}
}
Remove the annotation
This also works in my installation of Android Studio, but it might not work for you, but you could try to just remove the #Nullable annotation from the implementation:
class PrintObserver implements Observer<Integer> {
#Override
public void onChanged(Integer integer) {
Log.d("Example", integer.toString());
}
}
Default methods
It's unlikely you can use this on Android, but purely from a Java perspective, you could define a new interface and add a null check in a default method:
interface NonNullObserver<V> extends Observer<V> {
#Override
default void onChanged(#Nullable V v) {
Objects.requireNonNull(v);
onNonNullChanged(v);
// Alternatively, you could add an if check here.
}
void onNonNullChanged(#NonNull V value);
}
fun <T> LiveData<T>.observeNonNull(owner: LifecycleOwner, observer: (t: T) -> Unit) {
this.observe(owner, Observer {
it?.let(observer)
})
}
You would have to do some additional work to handle null values that come from the library itself.
For example, when you return a LiveData from a #Dao in Room, like:
#Dao interface UserDao {
#get:Query("SELECT * FROM users LIMIT 1")
val user: LiveData<User>
}
And observe the user live data, it will call the onChanged callback with a null value if there is no user.
I am trying to test my ViewModel in my application, here is the constructor:
#Inject
public SearchUserViewModel(#Named("searchUser") UseCase searchUserUseCase) {
this.searchUserUseCase = searchUserUseCase;
}
In my test I create a SearchUserUseCase with mocks like this:
Observable error = Observable.error(new Throwable("Error"));
when(gitHubService.searchUser(MockFactory.TEST_USERNAME_ERROR)).thenReturn(error);
when(ObserverThread.getScheduler()).thenReturn(Schedulers.immediate());
when(SubscriberThread.getScheduler()).thenReturn(Schedulers.immediate());
searchUserUseCase = new SearchUserUseCase(gitHubService, SubscriberThread, ObserverThread);
In my ViewModel class I have this snippet which I want to test:
public void onClickSearch(View view) {
loadUsers();
}
private void loadUsers() {
if (username == null) {
fragmentListener.showMessage("Enter a username");
} else {
showProgressIndicator(true);
searchUserUseCase.execute(new SearchUserSubscriber(), username);
}
}
private final class SearchUserSubscriber extends DefaultSubscriber<SearchResponse> {
#Override
public void onCompleted() {
showProgressIndicator(false);
}
#Override
public void onError(Throwable e) {
showProgressIndicator(false);
fragmentListener.showMessage("Error loading users");
}
#Override
public void onNext(SearchResponse searchResponse) {
List<User> users = searchResponse.getUsers();
if (users.isEmpty()) {
fragmentListener.showMessage("No users found");
} else {
fragmentListener.addUsers(users);
}
}
}
Finally in my test I have this:
#Test
public void shouldDisplayErrorMessageIfErrorWhenLoadingUsers() {
SearchUserViewModel searchUserViewModel = new SearchUserViewModel(searchUserUseCase);
searchUserViewModel.setFragmentListener(mockFragmentListener);
searchUserViewModel.setUsername(MockFactory.TEST_USERNAME_ERROR);
searchUserViewModel.onClickSearch(view);
verify(mockFragmentListener).showMessage("Error loading users");
}
I get this error from Mockito:
Wanted but not invoked:
fragmentListener.showMessage(
"Error loading users"
);
I am not sure if this is a good test, but I somehow want to test the SearchUserSubscriber one way or another. Thanks
Edit: I have found similar questions to this problem here: Can't verify mock method call from RxJava Subscriber (which still isn't answered) and here: Verify interactions in rxjava subscribers. The latter question is similar but does not execute the subscriber in a separate class (which happens in SearchUserUseCase here).
I also tried RobolectricGradleTestRunner instead of MockitoJunitRunner and changed to Schedulers.io() and AndroidSchedulers.mainThread(), but I still get the same error.
Tried mocking SearchUserUseCase instead of GitHubService (which feels cleaner), but I'm not sure on how to test the subscriber that way since that is passed as an argument to the void method execute() in UseCase.
public void execute(Subscriber useCaseSubscriber, String query) {
subscription = buildUseCase(query)
.observeOn(postExecutionThread.getScheduler())
.subscribeOn(threadExecutor.getScheduler())
.subscribe(useCaseSubscriber);
}
And buildUseCase()
#Override
public Observable buildUseCase(String username) throws NullPointerException {
if (username == null) {
throw new NullPointerException("Query must not be null");
}
return getGitHubService().searchUser(username);
}
For me it worked out to add a Observable.Transformer<T, T> as followed:
void gatherData() {
service.doSomeMagic()
.compose(getSchedulerTransformer())
.subscribe(view::displayValue);
}
private <T> Observable.Transformer<T, T> getSchedulerTransformer() {
if (mTransformer == null) {
mTransformer = (Observable.Transformer<T, T>) observable -> observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
return mTransformer;
}
void setSchedulerTransformer(Observable.Transformer<Observable<?>, Observable<?>> transformer) {
mTransformer = transformer;
}
And to set the Transformer. I just passed this:
setSchedulerTransformer(observable -> {
if (observable instanceof Observable) {
Observable observable1 = (Observable) observable;
return observable1.subscribeOn(Schedulers.immediate())
.observeOn(Schedulers.immediate());
}
return null;
});
So just add a #Before method in your test and call presenter.setSchedulerTransformer and it should be able to test this. If you want more detail check this answer.
If you are using Mockito, you can probably get hold of a SearchUserSubscriber using an ArgumentCaptor, for example...
#Captor
private ArgumentCaptor<SearchUserSubscriber> subscriberCaptor;
private SearchUserSubscriber getSearchUserSubscriber() {
// TODO: ...set up the view model...
...
// Execute the code under test (making sure the line 'searchUserUseCase.execute(new SearchUserSubscriber(), username);' gets hit...)
viewModel.onClickSearch(view);
verify(searchUserUseCase).execute(subscriberCaptor.capture(), any(String.class));
return subscriberCaptor.getValue();
}
Now you can have test cases such as...
#Test
public void shouldDoSomethingWithTheSubscriber() {
SearchUserSubscriber subscriber = getSearchUserSubscriber();
...
}