Дано:
API, возвращающее список с данными в формате JSON
Задача:
Получить эти данные силами Retrofit+RxJava
Проблема:
Необходимо сделать изначально один запрос и не дублировать его, если экран будет повёрнут до окончания задачи. Также нужно иметь возможность перезапустить задачу.
Что получилось:
Первое я решил, сделав Singlton и закешировав единственный экземпляр Observable с помощью cache().
Второе - полным пересозданием объекта Retrofit (1), экземпляра retrofit-интерфейса (2) и самого Observable(3). Если 1 и 2 не сделать - 3 - остаётся прежним и возвращает закэшированные данные, вместо новых.
Вопрос:
Использованный мной способ перезапуска задачи получения данных выглядит плохо. Как лучше/правильнее пересоздать Observable?
Синглтон для получения/пересоздания Observalbe
public class SingltonRetrofit
{
private static RxJavaCallAdapterFactory rxAdapter = RxJavaCallAdapterFactory.createWithScheduler(Schedulers.io());
private static Gson gson = new GsonBuilder().create();
private static Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Const.BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(rxAdapter)
.build();
private static GetModels apiService = retrofit.create(GetModels.class);
private static Observable
public static void reset()
{
retrofit = new Retrofit.Builder()
.baseUrl(Const.BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(rxAdapter)
.build();
apiService = retrofit.create(GetModels.class);
observableModelsList = null;
}
public static Observable
P.S.
Этот же вопрос на английском: How to recreate or reset cached Observable, used with Retrofit to get new data?
Ответ
В итоге сделал так:
Как верно написал @mit, метод cache() кэшировал запрос и пересоздание observable в итоге не перезапускало сетевой запрос. При этом в доках и в интернетах я нигде сему упоминания не находил. Видать это как-то связано с внутренней логикой связки Retrofit+OkHttp
При этом как верно предложил @Yura Ivanov, потребовался BehaviorSubject
На него подписывается фрагмент и его же можно пересоздать в случае нужды в свежих данных (без пересоздания будет отданы последние данные). При этом при каждом создании/пересоздании BehaviorSubject создаётся Subscriber для получения данных из сети через Observable, создаваемый Retrofit-ом. И он в onError и в onNext вызывает соответствующие методы у BehaviorSubject. При этом не транслируя onComplete, т.к. в этом случае может произойти ситуация, когда данные придут в процессе пересоздания фрагмента и фрагмент получит только последнее событие BehaviorSubject, т.е. событие завершения последовательности, вместо последних полученных данных. Т.е. соединять подпиской напрямую BehaviorSubject и Observable, получающий сетевые данные не стоит.
Итого все требования соблюдены: При поворотах экрана будет запущена всего однажды задача на скачивание данных, фрагмент получит данные (или сообщение об ошибке) в любом случае и пересоздавать объекты Retrofita-а не нужно.
Итоговый синглтон:
public class SingltonRetrofitNew
{
private static RxJavaCallAdapterFactory rxAdapter = RxJavaCallAdapterFactory.createWithScheduler(Schedulers.io());
private static Gson gson = new GsonBuilder().create();
private static Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Const.BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(rxAdapter)
.build();
private static GetModels apiService = retrofit.create(GetModels.class);
private static BehaviorSubject
private SingltonRetrofitNew()
{
}
public static void resetObservable()
{
observableModelsList = BehaviorSubject.create();
if (subscription != null && !subscription.isUnsubscribed())
{
subscription.unsubscribe();
}
subscription = observable.subscribe(new Subscriber
@Override
public void onError(Throwable e)
{
observableModelsList.onError(e);
}
@Override
public void onNext(ArrayList
public static Observable
Сокращённый фрагмент:
public class FragmentsList extends Fragment
{
private static final String TAG = FragmentList.class.getSimpleName();
private Subscription subscription;
private RecyclerView recyclerView;
private SwipeRefreshLayout swipeRef;
private ArrayList
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)
{
View v = inflater.inflate(R.layout.fragment, container, false);
//init views
recyclerView = (RecyclerView) v.findViewById(R.id.recycler);
swipeRef = (SwipeRefreshLayout) v.findViewById(R.id.swipe_ref);
swipeRefreshLayout.setOnRefreshListener(new OnRefreshListener()
{
@Override
public void onRefresh()
{
SingltonRetrofitNew.reset();
getModelsList();
}
});
if (savedInstanceState != null)
{
models = savedInstanceState.getParcelableArrayList(Const.KEY_MODELS);
isLoading = savedInstanceState.getBoolean(Const.KEY_IS_LOADING);
}
if (models.size() == 0 || isLoading)
{
getModelsList();
}
//TODO show saved data if is
return v;
}
@Override
public void onDestroy()
{
super.onDestroy();
if (subscription != null && !subscription.isUnsubscribed())
{
subscription.unsubscribe();
}
}
private void getModelsList()
{
isLoading = true;
swipeRef.setRefreshing(true);
if (subscription != null && !subscription.isUnsubscribed())
{
subscription.unsubscribe();
}
subscription = SingltonRetrofitNew.getModelsObservable().
subscribeOn(Schedulers.io()).
observeOn(AndroidSchedulers.mainThread()).
subscribe(new Subscriber
@Override
public void onError(Throwable e)
{
Log.d(TAG, "onError", e);
isLoading = false;
swipeRef.setRefreshing(false);
Snackbar.make(recyclerView, R.string.connection_error, Snackbar.LENGTH_SHORT)
.setAction(R.string.try_again, new View.OnClickListener()
{
@Override
public void onClick(View v)
{
SingltonRetrofitNew.reset();
getModelsList();
}
})
.show();
}
@Override
public void onNext(ArrayList
@Override
public void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
outState.putParcelableArrayList(Const.KEY_MODELS, models);
outState.putBoolean(Const.KEY_IS_LOADING, isLoading);
}
}
Всё вместе на gitHub: RxRetrofitAndScreenOrientation
Статья на ХабраХабр про решение: Используем RxJava и Retrofit на Android, учитывая поворот экрана
Комментариев нет:
Отправить комментарий