Android Volley custom Loader

от автора

В статье изложен подход реализации Loader для загрузки разных объектов в одном Activity. В качестве сетевой библиотеки загрузки используется Volley. Метод подходит когда в одном Activity имеется несколько одновременно использующихся фрагментов

public class MainActivity extends ActionBarActivity         implements LoaderManager.LoaderCallbacks<DataHolder>{     ...     @Override     public void onLoadFinished(Loader<DataHolder> loader, DataHolder data) {         if ( loader.getId() == DataLoader.LOADER_ICONS_ID ){            doIcons( data.getIcons() );         } else if( loader.getId() == DataLoader.LOADER_STYLES_ID ){            doStyles( data.getStyles() );         } else if( loader.getId() == DataLoader.LOADER_ICONSETS_ID ){            doIconSets( data.getIconSets() );         } 


Основная проблема использования Fragment это то что они «живут своей жизнью» (Поправьте меня если это не так). Особенно в момент поворота экрана. Отсюда и конструктор без параметров и static newInstance(..)

All subclasses of Fragment must include a public no-argument constructor. The framework will often re-instantiate a fragment class when needed, in particular during state restore, and needs to be able to find this constructor to instantiate it. If the no-argument constructor is not available, a runtime exception will occur in some cases during state restore.

Поэтому используя асинхронную загрузку с помошью Retrofit или Volley нельзя на сто процентов быть уверенным во время возврата из callback в каком состоянии Activity и Fragment. Есть внутренние состояния для FragmentManager, которые можно проверить, но это плохой подход. Например:

// Resolved After Loader implementation if( !fragmentManager.isDestroyed() ) {    // Check problem after rotation screen 

Поэтому было решено написать собственный Loader. Feed для теста был выбран Iconfinder. Скажу что feed отдается не всегда по запросу без ошибок. Например можно сделать около 100 запросов, а потом вернется ошибка. Была попытка написать в службу поддержки, но ответа не последовало. Через ~5 секунд ошибка пропадает и возобновляются нормальные запросы. Для теста сойдет

Сам проект доступен на github Android Iconfinder demo

App-z.net

Loader выглядит следующим образом:

public class DataLoader extends Loader<DataHolder> {     public static final String ARGS_URL = "url";     private String urlFeed;     private RequestQueue requestQueue;     private DataHolder dataHolder = new DataHolder();      public static final int LOADER_ICONS_ID = 1;     public static final int LOADER_STYLES_ID = 2;     public static final int LOADER_ICONSETS_ID = 3;      public DataLoader(Context context, Bundle bundle) {         super(context);         urlFeed = bundle.getString(ARGS_URL);         requestQueue = Volley.newRequestQueue(context);         // run only once         onContentChanged();     }      @Override     protected void onStartLoading() {         if (takeContentChanged())             forceLoad();     }      @Override     protected void onStopLoading() {         requestQueue.cancelAll(this);         super.onStopLoading();     }      @Override     protected void onReset() {         requestQueue.cancelAll(this);         super.onReset();     }      @Override     public void onForceLoad() {         super.onForceLoad();         if( getId() == LOADER_STYLES_ID )             doStylesRequest();         else if ( getId() == LOADER_ICONS_ID )             doIconsRequest();         else if ( getId() == LOADER_ICONSETS_ID )             doIconsetsRequest();     }      private void doIconsetsRequest() {         final GsonRequest gsonRequest = new GsonRequest(urlFeed, Iconsets.class, null, new Response.Listener<Iconsets>() {             @Override             public void onResponse(Iconsets iconsets) {                 dataHolder.setIconsets(iconsets);                 deliverResult(dataHolder);             }         ...     }     void doStylesRequest(){       ...     }     void  doIconsRequest(){       ...     } } 

Есть момент. Вернуть данные сразу из callback Volley не получится или правильнее сказать создать на лету DataHolder для deliverResult, поэтому нужен именно член класса DataHolder в котором будут хранится ссылки на объекты
Не забываем про requestQueue.cancelAll(this); когда Loader прерывает работу
Также для Volley сделана небольшая обертка GsonRequest

Но и это еще не все. После вызова onLoadFinished сразу запустить Fragment не получится. Потребуется дополнительная реализация через Handler. На stackoverflow пишут о баге и предлагают именно такое решение:

Handler

    final int ICONS_HANDLER = 1;     final int STILES_HANDLER = 2;     final int ICONSETS_HANDLER = 3;      @Override     public void onLoadFinished(Loader<DataHolder> loader, DataHolder data) {         if(data == null ) {             // In Loader happened error             AppUtils.showDialog(MainActivity.this, "Error", "Server request error. Try again later", false);             return;         }          Message msg = mHandler.obtainMessage();         Bundle b = new Bundle();         if(loader.getId() == DataLoader.LOADER_ICONS_ID){             offset += count;    // Prepare for next lazy load             b.putParcelable("Icons", data.getIcons());             msg.what = ICONS_HANDLER;         } else if(loader.getId() == DataLoader.LOADER_STYLES_ID){             b.putParcelable("Styles", data.getStyles());             msg.what = STILES_HANDLER;         } else if(loader.getId() == DataLoader.LOADER_ICONSETS_ID){             b.putParcelable("IconSets", data.getIconSets());             msg.what = ICONSETS_HANDLER;         }         msg.setData(b);         mHandler.sendMessage(msg);     }      final Handler mHandler = new Handler(){         public void handleMessage(Message msg) {             Bundle b;             b=msg.getData();             if(msg.what == ICONS_HANDLER){                 Icons icons = b.getParcelable("Icons");                 fillIcons(icons);             } else if(msg.what == STILES_HANDLER){                 Styles styles = b.getParcelable("Styles");                 fillStyles(styles);             } else if(msg.what == ICONSETS_HANDLER){                 Iconsets iconSets = b.getParcelable("IconSets");                 fillIconSets(iconSets);             }             super.handleMessage(msg);         }     }; 

В MainActivity onStop () прерываем все загрузки:

private void destroyLoaders(){      LoaderManager loaderManager = getSupportLoaderManager();      loaderManager.destroyLoader(DataLoader.LOADER_ICONS_ID);      loaderManager.destroyLoader(DataLoader.LOADER_ICONSETS_ID);      loaderManager.destroyLoader(DataLoader.LOADER_STYLES_ID);     }     @Override     protected void onStop () {         super.onStop();         destroyLoaders();     ... 

Все методы для загрузки однотипные. В будущем возможно реализовать универсальный типовой метод запроса используя

Class<T> clazz;

Повторюсь, полная реализация проекта доступна на github Android Iconfinder demo. Api iconfinder Api 2.0

Таким образом получилось приложение с одним Activity и «бутербродом» из Fragments с корректным поворотом экрана

Список литературы:
Loader
Fragment
Implementing Loaders

ссылка на оригинал статьи http://habrahabr.ru/post/254801/