📜 ⬆️ ⬇️

Android Volley custom Loader

The article outlines the approach of implementing Loader for loading different objects in one Activity. Volley is used as a network boot library. The method is suitable when there are several simultaneously used fragments in one 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() ); } 



The main problem with using Fragment is that they “live their lives” (correct me if this is not the case). Especially at the time of turning the screen. Hence the constructor without parameters and static newInstance (..)
newInstance
  /** * Returns a new instance of this fragment * */ public static IconsGridFragment newInstance(Icons icons) { IconsGridFragment fragment = new IconsGridFragment(); Bundle args = new Bundle(); args.putParcelable(ARG_ICONS, icons); fragment.setArguments(args); return fragment; } public IconsGridFragment() { } 


All subclasses of Fragment must include a public no-argument constructor. It is often necessary to establish the framework. If a no-argument constructor is not available, a runtime exception will occur during the state restore.

Therefore, using asynchronous loading with the help of Retrofit or Volley cannot be one hundred percent sure when returning from a callback in what state of Activity and Fragment. There are internal states for the FragmentManager that can be checked, but this is a bad approach. For example:
')
 // Resolved After Loader implementation if( !fragmentManager.isDestroyed() ) { // Check problem after rotation screen 


Therefore, it was decided to write your own Loader. Feed for the test was chosen Iconfinder . I will say that feed is not always given on request without errors.
E / Volley ďą• [979] BasicNetwork.performRequest: Unexpected response code 429 for https ...
For example, you can make about 100 requests, and then an error will return. There was an attempt to write to the support service, but there was no answer. After ~ 5 seconds, the error disappears and normal requests are resumed.

Answered from Iconfinder: The 429 status code means you're making too many requests: developer.iconfinder.com/api/2.0/overview.html#rate-limiting
You could have guessed it yourself. On this occasion, made a pop-up window with animation, all the same through the fragment. Close the window and reset the boot flag after 5 seconds. A postponement message to close is called immediately after committing ft.commit (). According to the correct, it would be necessary to call up a fragment joining event. Callback from onAttach for example.
OverlayMessageFragment using
  private void closeOverlayDelay() { Handler handler = new Handler(); handler.postDelayed(new Runnable() { public void run() { closeOverlayFragment(); // Reset flag fo continue load after error gone Fragment fragment = getSupportFragmentManager(). findFragmentByTag(IconsGridFragment.class.getSimpleName()); if (fragment != null) ((IconsGridFragment)fragment).resetLoadingFlag(); } }, 5000); } @Override public void onLoadFinished(final Loader<DataHolder> loader, final DataHolder data) { VolleyError volleyError = data.getError(); if (volleyError != null) { if (DEBUG) Log.e(TAG, "volleyError message: " + volleyError.getMessage()); NetworkResponse networkResponse = volleyError.networkResponse; if (networkResponse != null && networkResponse.statusCode == 429) { // HTTP Status Code: 429 if (DEBUG) Log.e(TAG, "volleyError statusCode: " + networkResponse.statusCode); new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { Fragment fragment = getSupportFragmentManager(). findFragmentByTag(OverlayMessageFragment.class.getSimpleName()); if(fragment == null) { Fragment overlayMessageFragment = OverlayMessageFragment.newInstance("Server Error 429. Too many requests. Try later"); FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); ft.setCustomAnimations(R.anim.enter, R.anim.exit, R.anim.pop_enter, R.anim.pop_exit); ft.add(R.id.container, overlayMessageFragment, OverlayMessageFragment.class.getSimpleName()); ft.commit(); closeOverlayDelay(); } } }); return; } return; } ... 



The project itself is available on github Android Iconfinder demo

App-z.net

Loader looks like this:
 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(){ ... } } 


There is a moment. Returning data immediately from Volley’s callback will not work or, better to say, create a DataHolder for deliverResult on the fly, so you need a member of the DataHolder class in which the object references
Do not forget about requestQueue.cancelAll (this); when loader interrupts
Also for Volley made a small wrapper GsonRequest

But that's not all. After calling onLoadFinished, Fragment will not immediately start. Additional implementation via Handler will be required. On stackoverflow, they write about a bug and offer exactly this solution:
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); } }; 


It remains to add himself Fragment in BackStack. Moreover, we first check whether it has already been created, and if it has been created, simply add icons to the Adapter. Thus, a semblance of pagination is implemented, or as I called LazyLoadMore. Loading icons and feed in the background
addToBackStack
 private void fillIcons(Icons icons) { // Resolved After Loader implementation //if(!fragmentManager.isDestroyed()) { // Check problem after rotation screen FragmentManager fragmentManager = getSupportFragmentManager(); Fragment iconsGridFragment = fragmentManager.findFragmentByTag(IconsGridFragment.class.getSimpleName()); if (iconsGridFragment != null) { ((IconsGridFragment) iconsGridFragment).addIcons(icons); } else { iconsGridFragment = IconsGridFragment.newInstance(icons); // Add the fragment to the activity, pushing this transaction on to the back stack. FragmentTransaction ft = fragmentManager.beginTransaction(); ft.replace(R.id.container, iconsGridFragment, IconsGridFragment.class.getSimpleName()); ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN); ft.addToBackStack(null); ft.commit(); } } 


In MainActivity.onStop () we interrupt all downloads:
 private void destroyLoaders(){ mHandler.removeCallbacksAndMessages(null); // Because using Fix ! 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(); ... 


All methods for loading the same type. The next step is to implement a generic generic query method using
 Class<T> clazz; 

It looks like this:
 @Override public void onForceLoad() { super.onForceLoad(); doRequest(DataHolder.getClazz(getId())); } 

Classes in a DataHolder must be implements DataHolderItem
 public class Icons implements Parcelable, DataHolder.DataHolderItem{ 

Dataloader
 /** * Created by App-z.net on 02.04.15. */ public class DataLoader extends Loader<DataHolder> { private static final boolean DEBUG = true; private static final String TAG = "DataLoader>"; public static final String ARGS_URL = "url"; private String urlFeed; private RequestQueue requestQueue; private DataHolder dataHolder = new DataHolder(); 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() { if ( DEBUG ) Log.i(TAG, "Loader onStopLoading()"); requestQueue.cancelAll(this); super.onStopLoading(); } @Override protected void onReset() { if ( DEBUG ) Log.i(TAG, "Loader onReset()"); requestQueue.cancelAll(this); super.onReset(); } @Override public void onForceLoad() { super.onForceLoad(); if ( DEBUG ) Log.d(TAG, "Loader onForceLoad() : feedUrl = " + urlFeed); doRequest(DataHolder.getClazz(getId())); } /** * * Get Data */ private void doRequest(Class<?> clazz) { final GsonRequest gsonRequest = new GsonRequest(urlFeed, clazz, null, new Response.Listener<DataHolder.DataHolderItem>() { @Override public void onResponse(DataHolder.DataHolderItem data) { dataHolder.setData(getId(), data); deliverResult(dataHolder); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError volleyError) { if (volleyError != null) if (DEBUG) Log.e(TAG, "volleyError: " + volleyError.getMessage()); deliverResult(null); } }); requestQueue.add(gsonRequest); } } 


DataHolder
 /** * Created by App-z.net on 02.04.15. */ public class 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; private Styles styles; private Icons icons; private Iconsets iconsets; public Styles getStyles(){ return styles; } private void setStyles(Styles styles){ this.styles = styles; } public Iconsets getIconsets() { return iconsets; } private void setIconsets(Iconsets iconsets){ this.iconsets = iconsets; } public Icons getIcons(){ return icons; } private void setIcons(Icons icons){ this.icons = icons; } public void setData(int dataId, DataHolderItem item){ switch (dataId){ case LOADER_ICONS_ID: setIcons((Icons)item); break; case LOADER_STYLES_ID: setStyles((Styles)item); break; case LOADER_ICONSETS_ID: setIconsets((Iconsets)item); break; default: assert false : "Error LOADER ID"; } } public DataHolderItem getData(int dataId){ switch (dataId){ case LOADER_ICONS_ID: return getIcons(); case LOADER_STYLES_ID: return getStyles(); case LOADER_ICONSETS_ID: return getIconsets(); default: assert false : "Error LOADER ID"; } return null; } public static Class<?> getClazz(int dataId){ switch (dataId){ case LOADER_ICONS_ID: return Icons.class; case LOADER_STYLES_ID: return Styles.class; case LOADER_ICONSETS_ID: return Iconsets.class; default: assert false : "Error LOADER ID"; } return null; } public interface DataHolderItem{ } } 


Again, the full implementation of the project is available on github Android Iconfinder demo . Api iconfinder Api 2.0

Thus, an application with one Activity and a “sandwich” from Fragments with the correct screen rotation turned out

Bibliography:
Loader
Fragment
Implementing Loaders

Source: https://habr.com/ru/post/254801/


All Articles