Trying to convert a kotlin method to java that uses RxJava - android

Android Studio 3.1 RC 2
kotlin 1.2.30
Signature for the fetchMessage
Single<Response> fetchMessage(final String messageId);
The kotlin code that I am trying to convert to Java. However, I am not sure where the returns are? As I am new to kotlin and lambda.
private fun getMessage(messageId: String): Observable<State> {
return repository
.fetchMessage(messageId)
.flatMap {
Single.fromCallable<State>({
update(messageId, it, State.COMPLETED)
State.COMPLETED
})
}
.toObservable()
}
This is my attempt at trying to convert it. However, the compiler complains of a missing return.
public Observable<TranslationChatState> translate(String messageId) {
return repository
.fetchMessage(messageId)
.flatMap(new Func1 <Response, Single<State >>() {
#Override
public Single<State> call(final Response response) {
Single.fromCallable(new Callable<State>() {
#Override
public State call() {
update(messageId, response, State.COMPLETED);
return State.COMPLETED;
}
});
} /* complains about no return here */
})
.toObservable();
}
Many thanks for any suggestions,

because it is missing the return statement. It should be
public Single<State> call(final Response response) {
return Single.fromCallable(new Callable<State>() {

Related

Moshi and Retrofit2 : Expected BEGIN_OBJECT but was STRING

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

Cannot use RX Android Observable.zip operator

I am using rx-android zip operator to merge two retrofit calls.
Previously the code was like this:
affinityService.rewardsStatusChanges()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.compose(this.<RewardsStatus>bindToLifecycle())
.subscribe(new Action1<RewardsStatus>() {
#Override
public void call(RewardsStatus rewardsStatus) {
onRewardStatus(rewardsStatus);
}
});
affinityService.affinityStatusChanges()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.compose(this.<AffinityStatus>bindToLifecycle())
.subscribe(new Action1<AffinityStatus>() {
#Override
public void call(AffinityStatus affinityStatus) {
onAffinityStatus(affinityStatus);
}
});
rewardsStatusChanges() and affinityStatusChanges() are two retrofit calls.
Now I need to merge them.
What I have tried:
affinityService.rewardsStatusChanges()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.compose(this.<RewardsStatus>bindToLifecycle())
.flatMap(new Func1<RewardsStatus, Observable<RewardsStatus>>() {
#Override
public Observable<RewardsStatus> call(RewardsStatus rewardsStatus) {
return Observable.just(rewardsStatus);
}
})
.flatMap(new Func1<RewardsStatus, Observable<RewardsStatus>>() {
#Override
public Observable<RewardsStatus> call(RewardsStatus rewardsStatus) {
return Observable.zip(Observable.just(rewardsStatus),
affinityService.affinityStatusChanges(),new Func2<RewardsStatus, AffinityStatus, RewardsStatus>() {
#Override
public RewardsStatus call(RewardsStatus rewardsStatus, AffinityStatus affinityStatus) {
onAffinityAndRewardsMerged(rewardsStatus,affinityStatus);
return null;
}
});
}
});
But unfortunately the above codebase is not working.
Any idea how to do this.
I am using:
RX_ANDROID_VERSION=1.0.1
RX_JAVA_VERSION=1.0.14
posting as you wished, however with that return null in the anonymous function you will get null in your consumer, so I think returning like Pair<RewardsStatus,AffinityStatus> would be nicer, and do that result processing in the consumer.
Observable.zip(affinityService.rewardsStatusChanges(), affinityService.affinityStatusChanges(),
object : Func2<RewardsStatus, AffinityStatus, RewardsStatus>() {
fun call(rewardsStatus: RewardsStatus, affinityStatus: AffinityStatus): RewardsStatus? {
onAffinityAndRewardsMerged(rewardsStatus, affinityStatus)
return null
}
})

What is the right way to mock an RxJava Observable

I have an API which returns Observable's to be used with RxJava. For testing I want to avoid network operations so plan to mock the responses. However as all responses must me wrapped with Observable and the from() method expects a Future not a concrete type, my mock class is convoluted with anonymous wrapper classes and I think there must be a better way.
Here is what I have:
public class MockApi implements MyApi {
#Override
public Observable<MyData> getMyData() {
return Observable.from(new Future<MyData>() {
#Override public boolean cancel(boolean mayInterruptIfRunning) { return false; }
#Override public boolean isCancelled() { return false; }
#Override public boolean isDone() { return false; }
#Override
public MyData get(long timeout, TimeUnit unit) throws InterruptedException,
ExecutionException, TimeoutException {
return get();
}
#Override
public MyData get() throws InterruptedException, ExecutionException {
return new MyData();
}
});
}
...
}
Is there a better way?
return Observable.just(new MyData());
You can find the documentation here. And for more complicated mock => list of creating operators.
You can use Observable.defer(() -> Observable.just(new MyData()), or use PublishSubject for sending data through it. Notice, you have to use .defer() operator in first case because it will use the same value from first call otherwise

Verify mock interactions within anonymous inner class

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

convert to lambda from rx java expression

I've started to grok into retrolambda and rxjava. Some expressions i'v converted by myself, but some of them i can't convert. I've added retrolambda to my project.Here is examples
public Observable<ImmutableList<Repository>> getUsersRepositories() {
return githubApiService.getUsersRepositories(user.login)
.map(repositoryResponses -> {
final ImmutableList.Builder<Repository> listBuilder = ImmutableList.builder();
for (RepositoryResponse repositoryResponse : repositoryResponses) {
Repository repository = new Repository();
repository.id = repositoryResponse.id;
repository.name = repositoryResponse.name;
repository.url = repositoryResponse.url;
listBuilder.add(repository);
}
return listBuilder.build();
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
But i don't know how to convert this peace of code:
obs.subscribe(new Observer<List<Integer>>() {
public void onCompleted() {
System.out.println("completed");
}
public void onError(Throwable e) {
System.out.println("failure");
}
public void onNext(List<Integer> value) {
System.out.println("onnext=" + value);
}
});
I think that you want to make something like this:
obs.subscribe(
(List<Integer> value) -> System.out.println("onnext=" + value),
(Throwable e) -> System.out.println("failure"),
()-> System.out.println("completed"));
You can check this blog and this example in GitHub.
Lambda is an anonymous function and you are creating an anonymous class, similar but different.
First one is already a Lambda: .map(repositoryResponses -> {...}, what do you want to change more? The second part is a "block code" so you can't reduce it more.
Second one you can't convert it because it's not a function.
I've found the way to solve it. Here is code before conversion
repositoriesManager.getUsersRepositories().subscribe(new SimpleObserver<ImmutableList<Repository>>() {
#Override
public void onNext(ImmutableList<Repository> repositories) {
repositoriesListActivity.showLoading(false);
repositoriesListActivity.setRepositories(repositories);
}
#Override
public void onError(Throwable e) {
repositoriesListActivity.showLoading(false);
}
});
after conversion
repositoriesManager.getUsersRepositories().subscribe(repositories -> {
repositoriesListActivity.showLoading(false);
repositoriesListActivity.setRepositories(repositories);
},throwable -> {
repositoriesListActivity.showLoading(false);
});

Categories

Resources