📜 ⬆️ ⬇️

Android Volley Loader. Moving toward the library

Further experiments on crossing Volley and Loader led me to the idea of ​​creating a library. That Loader with parameters and in one line was caused. On the similarity as it is implemented in Picaso. After a couple of evenings, something happened ...

Json
{ "1":{"name":"Samsung","price":51200.6}, "2":{"name":"Lg","price":5400.6}, "3":{"name":"Alcatel","price":4500.6}, "4":{"name":"iPhone","price":4800.3}, "7":{"name":"iPad","price":2850.1} } 

Data
 public class GoodsItem { String name; float price; } 

Loader
  private String url = "http://192.168.1.103/shop.json"; private static final int LOADER_GOODS_ID = 1; Map<Integer, GoodsItem> mGoodsMap; @Override protected void onCreate(Bundle savedInstanceState) { ... FeedLoader.with(this).addLoader(LOADER_GOODS_ID, url, HashMap.class, new DelivererFeedLoader.Listener<Map<Integer, GoodsItem>>() { @Override public void onResponse(int loaderId, Map<Integer, GoodsItem> goodsMap) { mGoodsMap = goodsMap; for (Map.Entry<Integer, GoodsItem> entry : mGoodsMap.entrySet()) { Log.d(TAG , "Goods item : " + entry.getKey() + " : " + entry.getValue()); } } @Override public void onErrorResponse(VolleyError data) { Log.d(TAG , "onErrorResponse :" + data); } }).start(LOADER_GOODS_ID, this); 



Access to the library is provided through a simple singleton. Everything is as simple as Picaso
')
FeedLoader.with (this)
 public static FeedLoader with(Context context) { if (singleton == null) { synchronized (FeedLoader.class) { if (singleton == null) { singleton = new FeedLoader(context); } } } return singleton; } 


Adding Types and Listner is done via addLoader:
loaderId - loader ID, just a number
url - feed address
loaderClazz - This is the type of class that we want to parse.
DelivererFeedLoader.Listener - Callback through which data will return or an error
  public FeedLoader addLoader(int loaderId, String url, Class<?> loaderClazz, DispatcherData.Listener callback){ dispatcherData.putLoaderClazz(loaderId, loaderClazz); dispatcherData.putCallBack(loaderId, callback); dispatcherData.putUrlFeed(loaderId, url); dispatcherData.putUseCache(loaderId, false); // default return singleton; } 

Launch Loader via start (...):
  public void start(int loaderId, final FragmentActivity activity) { assert activity instanceof FragmentActivity : "Run possible only from FragmentActivity"; Bundle bundle = new Bundle(); bundle.putParcelable("dispatcherData", dispatcherData); activity.getSupportLoaderManager(). restartLoader(loaderId, bundle, callback); } } 

Actually four Alfa implementation files
FeedLoader.java
 package net.appz.feedloader; import android.content.Context; import android.os.Bundle; import android.support.v4.app.FragmentActivity; import android.support.v4.app.LoaderManager; import android.support.v4.content.Loader; import android.util.Log; import com.android.volley.Response; /** * Created by App-z.net on 04.04.15. */ public class FeedLoader { private static volatile FeedLoader singleton = null; private Context context = null; private boolean DEBUG = true; private String TAG = getClass().getSimpleName(); private DispatcherData dispatcherData = new DispatcherData(); public FeedLoader(Context context) { if (context == null) { throw new IllegalArgumentException("Context must not be null."); } this.context = context.getApplicationContext(); } LoaderManager.LoaderCallbacks<Response<Object>> callback = new LoaderManager.LoaderCallbacks<Response<Object>>() { @Override public Loader<Response<Object>> onCreateLoader(int id, Bundle args) { return new FeedLoaderWrapper(context , args); } @Override public void onLoadFinished(Loader<Response<Object>> loader, Response<Object> data) { if( data.isSuccess() ) dispatcherData.onResponse(loader.getId(), data.result); else dispatcherData.onErrorResponse(loader.getId(), data.error); } @Override public void onLoaderReset(Loader<Response<Object>> loader) { if( DEBUG ) Log.d(TAG, "onLoaderReset :" + loader.getId()); } }; public FeedLoader addLoader(int loaderId, String url, Class<?> loaderClazz, DispatcherData.Listener callback){ dispatcherData.putLoaderClazz(loaderId, loaderClazz); dispatcherData.putCallBack(loaderId, callback); dispatcherData.putUrlFeed(loaderId, url); dispatcherData.putUseCache(loaderId, false); // default return singleton; } public void start(int loaderId, final FragmentActivity activity) { assert activity instanceof FragmentActivity : "Run possible only from FragmentActivity"; Bundle bundle = new Bundle(); bundle.putParcelable("dispatcherData", dispatcherData); activity.getSupportLoaderManager(). restartLoader(loaderId, bundle, callback); } public void destroy(int loaderId, final FragmentActivity activity) { assert activity instanceof FragmentActivity : "Run possible only from FragmentActivity"; activity.getSupportLoaderManager().destroyLoader(loaderId); } public static FeedLoader with(Context context) { if (singleton == null) { synchronized (FeedLoader.class) { if (singleton == null) { singleton = new FeedLoader(context); } } } return singleton; } public FeedLoader useCache(int loaderId) { dispatcherData.putUseCache(loaderId, true); return singleton; } public FeedLoader resetCache(int loaderId) { dispatcherData.putUseCache(loaderId, false); return singleton; } } 


FeedLoaderWrapper.java
 package net.appz.feedloader; /** * Created by App-z.net on 05.04.15. */ import android.content.Context; import android.os.Bundle; import android.support.v4.content.Loader; import android.util.Log; import com.android.volley.RequestQueue; import com.android.volley.Response; import com.android.volley.VolleyError; import com.android.volley.toolbox.Volley; //http://www.androiddesignpatterns.com/2012/08/implementing-loaders.html /** * * * * @param <D> */ class FeedLoaderWrapper<D> extends Loader<Response<D>> { private boolean DEBUG = true; private String TAG = getClass().getSimpleName(); private DispatcherData dispatcherData; private RequestQueue requestQueue; private Response<D> mCachedResponse; /** * Stores away the application context associated with context. * Since Loaders can be used across multiple activities it's dangerous to * store the context directly; always use {@link #getContext()} to retrieve * the Loader's Context, don't use the constructor argument directly. * The Context returned by {@link #getContext} is safe to use across * Activity instances. * * @param context used to retrieve the application context. */ public FeedLoaderWrapper(Context context, Bundle bundle) { super(context); dispatcherData = bundle.getParcelable("dispatcherData"); requestQueue = Volley.newRequestQueue(context); // run only once onContentChanged(); } /** * Get Data */ private void doRequest(Class<?> clazz) { final boolean useCache = dispatcherData.getUseCache(getId()); if (DEBUG) Log.i(TAG, "useCache : " + getId() + " : " + useCache); final String urlFeed = dispatcherData.getUrlFeed(getId()); if ( !useCache ) requestQueue.getCache().remove(urlFeed); final GsonRequest gsonRequest = new GsonRequest(urlFeed, clazz, null, useCache, new Response.Listener<D>() { @Override public void onResponse(D data) { mCachedResponse = Response.success(data, null); deliverResult(mCachedResponse); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError volleyError) { mCachedResponse = Response.error(volleyError); deliverResult(mCachedResponse); } }); requestQueue.add(gsonRequest); } @Override public void deliverResult(Response<D> data) { if (isReset()) { // The Loader has been reset; ignore the result and invalidate the data. //releaseResources(data); if (DEBUG) Log.i(TAG, "Loader deliverResult() isReset()"); return; } // Hold a reference to the old data so it doesn't get garbage collected. // We must protect it until the new data has been delivered. Response<D> oldData = mCachedResponse; mCachedResponse = data; if (isStarted()) { // If the Loader is in a started state, deliver the results to the // client. The superclass method does this for us. super.deliverResult(data); if (DEBUG) Log.i(TAG, "Loader deliverResult() isStarted()"); } if (DEBUG) Log.i(TAG, "Loader deliverResult()"); } @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(); String urlFeed = dispatcherData.getUrlFeed(getId()); if (DEBUG) Log.d(TAG, "Loader onForceLoad() : feedUrl = " + urlFeed); doRequest(dispatcherData.getLoaderClazz(getId())); } } 


DispatcherData.java
 package net.appz.feedloader; import android.os.Parcel; import android.os.Parcelable; import com.android.volley.VolleyError; import java.util.HashMap; /** * Created by App-z.net on 05.04.15. */ public class DispatcherData implements Parcelable { private HashMap<Integer, Class> loaderClazzMap = new HashMap<>(); private HashMap<Integer, Listener> callBackMap = new HashMap<>(); private HashMap<Integer, String> urlFeeds = new HashMap<>(); private HashMap<Integer, Boolean> useCache = new HashMap<>(); public DispatcherData(){} void putUseCache(int loaderId, boolean cache){ useCache.put(loaderId, cache); } boolean getUseCache(int loaderId){ return useCache.get(loaderId); } void putCallBack(int loaderId, Listener callback){ callBackMap.put(loaderId, callback); } //Listener getCallBack(int loaderId){ // return callBackMap.get(loaderId); //} void onResponse(int loaderId, Object data){ callBackMap.get(loaderId).onResponse(loaderId, data); } void onErrorResponse(int loaderId, VolleyError error){ callBackMap.get(loaderId).onErrorResponse(error); } String getUrlFeed(int loaderId){ return urlFeeds.get(loaderId); } void putUrlFeed(int loaderId, String url){ urlFeeds.put(loaderId, url); } Class getLoaderClazz(int loaderId){ return loaderClazzMap.get(loaderId); } void putLoaderClazz(int loaderId, Class loaderClazz){ loaderClazzMap.put(loaderId, loaderClazz); } protected DispatcherData(Parcel in) { useCache = (HashMap) in.readValue(HashMap.class.getClassLoader()); loaderClazzMap = (HashMap) in.readValue(HashMap.class.getClassLoader()); callBackMap = (HashMap) in.readValue(HashMap.class.getClassLoader()); urlFeeds = (HashMap) in.readValue(HashMap.class.getClassLoader()); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeValue(useCache); dest.writeValue(loaderClazzMap); dest.writeValue(callBackMap); dest.writeValue(urlFeeds); } @SuppressWarnings("unused") public static final Parcelable.Creator<DispatcherData> CREATOR = new Parcelable.Creator<DispatcherData>() { @Override public DispatcherData createFromParcel(Parcel in) { return new DispatcherData(in); } @Override public DispatcherData[] newArray(int size) { return new DispatcherData[size]; } }; public interface Listener<D>{ void onResponse(int loaderId, D data); void onErrorResponse(VolleyError data); } } 


GsonRequest.java
 package net.appz.feedloader; import com.android.volley.AuthFailureError; import com.android.volley.Cache; import com.android.volley.NetworkResponse; import com.android.volley.ParseError; import com.android.volley.Request; import com.android.volley.Response; import com.android.volley.toolbox.HttpHeaderParser; import com.google.gson.Gson; import com.google.gson.JsonSyntaxException; import java.io.UnsupportedEncodingException; import java.util.Map; /** * Created by App-z.net on 29.03.15. */ public class GsonRequest<T> extends Request<T> { private final Gson gson = new Gson(); private final Class<T> clazz; private final Map<String, String> headers; private final Response.Listener<T> listener; private boolean useCache = false; /** * Make a GET request and return a parsed object from JSON. * * @param url URL of the request to make * @param clazz Relevant class object, for Gson's reflection * @param headers Map of request headers */ public GsonRequest(String url, Class<T> clazz, Map<String, String> headers, boolean useCache, Response.Listener<T> listener, Response.ErrorListener errorListener ) { super(Method.GET, url, errorListener); this.clazz = clazz; this.headers = headers; this.listener = listener; this.useCache = useCache; } @Override public Map<String, String> getHeaders() throws AuthFailureError { return headers != null ? headers : super.getHeaders(); } @Override protected void deliverResponse(T response) { listener.onResponse(response); } @Override protected Response<T> parseNetworkResponse(NetworkResponse response) { try { String json = new String( response.data, HttpHeaderParser.parseCharset(response.headers)); return Response.success( gson.fromJson(json, clazz), useCache ? parseIgnoreCacheHeaders(response) : HttpHeaderParser.parseCacheHeaders(response)); } catch (UnsupportedEncodingException e) { return Response.error(new ParseError(e)); } catch (JsonSyntaxException e) { return Response.error(new ParseError(e)); } } /** * Extracts a {@link Cache.Entry} from a {@link NetworkResponse}. * Cache-control headers are ignored. SoftTtl == 3 mins, ttl == 24 hours. * @param response The network response to parse headers from * @return a cache entry for the given response, or null if the response is not cacheable. */ public static Cache.Entry parseIgnoreCacheHeaders(NetworkResponse response) { long now = System.currentTimeMillis(); Map<String, String> headers = response.headers; long serverDate = 0; String serverEtag = null; String headerValue; headerValue = headers.get("Date"); if (headerValue != null) { serverDate = HttpHeaderParser.parseDateAsEpoch(headerValue); } serverEtag = headers.get("ETag"); final long cacheHitButRefreshed = 1 * 60 * 1000; // in 1 minutes cache will be hit, but also refreshed on background final long cacheExpired = 24 * 60 * 60 * 1000; // in 24 hours this cache entry expires completely final long softExpire = now + cacheHitButRefreshed; final long ttl = now + cacheExpired; Cache.Entry entry = new Cache.Entry(); entry.data = response.data; entry.etag = serverEtag; entry.softTtl = softExpire; entry.ttl = ttl; entry.serverDate = serverDate; entry.responseHeaders = headers; return entry; } } 



GitHub project
RoboSpice

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


All Articles