Ściągnąłem sobie chyba najprostszy przykład implementacji biblioteki Retrofit który krąży po internecie ( ten z pogodą z openweathermap).
Wszystko działa, ale chciałem zaimplementować tam RxJava do komunikacji pomiędzy warstwą UI oraz serwisami web, i pojawił mi się problem z kolejnością wykonywania działań z wątku asynchronicznego. Przykład przewija się bardzo często, ale mam kilka pytań odnośnie tego. Wstawię co najważniejsze, chociaż jeżeli ktoś pracuje z tym to pewnie dwa ostatnie paragrafy wystarczą do przedstawienia problemu. Wstawiłem kilka System.out z wypisaniem wątku który jest aktualnie używany żeby pokazać co się po kolei wykonuje.
Interfejs
public interface WeatherApi {
@GET("/data/2.5/weather")
Call<WeatherData> getWeatherFromApi (
@Query("q") String cityName,
@Query("APPID") String appId);
}
Subskrypcja z warstwy UI
public void onButtonClick() {
System.out.println("called onButtonClick, current thread is: " + Thread.currentThread());
mWeatherAdapter.getWeatherObservable("Nazwa Miasta")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<WeatherData>() {
...
@Override
public void onNext(WeatherData data) {
// obiekt data zwracany jest jako null, zanim zostanie zdefiniowany w żądaniu Retrofit
System.out.println("called onNext RxJava, current thread is: " + Thread.currentThread());
System.out.println("is data null?: " + data);
}
});
}
metoda wywołana z warstwy UI
public Observable<WeatherData> getWeatherObservable(final String city) {
return Observable.defer(new Func0<Observable<WeatherData>>() {
@Override
public Observable<WeatherData> call() {
// wywołanie asynchronicznej metody która zwraca null object zamiast poczekać na jego inicjację
return Observable.just(testWeatherApi(city));
}
});
}
główny winowajca
private WeatherData testWeatherApi(String city) {
System.out.println("called testWeatherApi, current thread is: " + Thread.currentThread());
mApi.getWeatherFromApi(city, Cons.OPEN_WEATHER_API).enqueue(new Callback<WeatherData>() {
@Override
public void onResponse(Call<WeatherData> call, Response<WeatherData> response) {
System.out.println("called onResponse Retrofit, current thread is: " + Thread.currentThread());
mData = response.body();
}
@Override
public void onFailure(Call<WeatherData> call, Throwable t) {}
});
System.out.println("calling return mData, current thread is: " + Thread.currentThread());
return mData;
}
Po odpaleniu i kliknięciu, kolejność pokazana przez konsolę:
1 - called onButtonClick, current thread is: Thread[main,5,main]
2 - called testWeatherApi, current thread is: Thread[RxIoScheduler-2,5,main]
3 - calling return mData, current thread is: Thread[RxIoScheduler-2,5,main] ( obiekt zwrócony zanim doszło do wykonania zapytania web, obiekt data jest oczywiście null)
4 - called onNext RxJava, current thread is: Thread[main,5,main]
4.1 - is data null?: null
5 - called onResponse Retrofit, current thread is: Thread[main,5,main] zapełnienie obiektu "data" danymi ( to po tym powinien zostać zwrócony)
Teraz moje przemyślenia/pytania:
- Problem spowodowany zapewne przeskokiem pomiędzy wątkami, tzn. zapytanie na wątku Thread[RxIoScheduler-2,5,main] a odpowiedź zostanie nadana na wątku UI, tak więc wątek poboczny "nie czeka" na wykonanie się operacji ściągnięcia danych, tylko od razu przechodzi do zwrócenia niezainicjowanego obiektu data ( metoda private WeatherData testWeatherApi(String city))
- Dlaczego w ogóle "onResponse" z retofit jest na wątku UI zamiast pobocznym? Są jakieś niesamowite profity tego rozwiązania?
- Jak zabrać się za sklejenie tego z RxJava?
Jestem oczywiście świadom tego, że mógłbym zrobić sobie dodatkowy interfejs callback z jedną metodą ( np. void onResponseRetrofitCalled()) i za jego pomocą zwrócić obiekt "data" do warstwy UI, tylko, że stawia to pod zapytaniem całe stosowanie RxJava w tym przypadku ( poza tym, staram się ograniczać interfejsy callback po ostatnim projekcie).
Oczywiście mógłbym też przenieść to do innej warstwy, ale napotkałem fajny problem i chciałbym go rozwiązać.
Mam nadzieję, że ktoś się zainteresuje i wypowie, i rozwiązanie nie będzie trywialne:).