Дальнейшие эксперименты по скрещиванию Volley и Loader привели меня к мысли о создании библиотеки. Чтобы вызывался Loader с параметрами и в одну строку. На подобии как это реализовано в Picaso. После пары вечеров что-то получилось…
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 = new HashMap<Integer, GoodsItem>();
@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);
Доступ к библиотеке предоставлен через простой Singleton. Здесь все просто, как в 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;
}
Добавление Типов и Листнера делается через addLoader:
loaderId — ID лоадера, просто число
url — Адрес feed
loaderClazz — Это тип класса, который хотим распарсить
DelivererFeedLoader.Listener — Callback через который вернутся данные или ошибка
public FeedLoader addLoader(int loaderId, String url, Class<?> loaderClazz, DelivererFeedLoader.Listener callback){
delivererFeedLoader.loaderClazzMap.put(loaderId, loaderClazz);
delivererFeedLoader.callBackMap.put(loaderId, callback);
delivererFeedLoader.urlFeeds.put(loaderId, url);
return singleton;
}
Запускаем Loader через start(...):
public void start(int loaderId, final Context context) {
Bundle bundle = new Bundle();
bundle.putParcelable("delivererFeedLoader", delivererFeedLoader);
((FragmentActivity)context).getSupportLoaderManager().
restartLoader(loaderId, bundle, callback);
}
Собственно четыре файла реализации Alfa
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;
/**
* 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 DelivererFeedLoader delivererFeedLoader = new DelivererFeedLoader();
public FeedLoader(Context context) {
this.context = context;
}
LoaderManager.LoaderCallbacks<Object> callback = new LoaderManager.LoaderCallbacks<Object>() {
@Override
public Loader<Object> onCreateLoader(int id, Bundle args) {
return new FeedLoaderWrapper(context , args);
}
@Override
public void onLoadFinished(Loader<Object> loader, Object data) {
delivererFeedLoader.callBackMap.get(loader.getId()).onResponse(loader.getId(), data);
}
@Override
public void onLoaderReset(Loader<Object> loader) {
}
};
public FeedLoader addLoader(int loaderId, String url, Class<?> loaderClazz, DelivererFeedLoader.Listener callback){
delivererFeedLoader.loaderClazzMap.put(loaderId, loaderClazz);
delivererFeedLoader.callBackMap.put(loaderId, callback);
delivererFeedLoader.urlFeeds.put(loaderId, url);
return singleton;
}
public void start(int loaderId, final Context context) {
Bundle bundle = new Bundle();
bundle.putParcelable("delivererFeedLoader", delivererFeedLoader);
((FragmentActivity)context).getSupportLoaderManager().
restartLoader(loaderId, bundle, callback);
}
public static FeedLoader with(Context context) {
if (singleton == null) {
synchronized (FeedLoader.class) {
if (singleton == null) {
singleton = new FeedLoader(context);
}
}
}
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;
/**
*
* @param <D>
*/
class FeedLoaderWrapper<D> extends Loader<D> {
private boolean DEBUG = true;
private String TAG = getClass().getSimpleName();
private DelivererFeedLoader delivererFeedLoader = new DelivererFeedLoader();
private RequestQueue requestQueue;
/**
* 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);
delivererFeedLoader = bundle.getParcelable("delivererFeedLoader");
requestQueue = Volley.newRequestQueue(context);
// run only once
onContentChanged();
}
/**
* Get Data
*/
private void doRequest(Class<?> clazz) {
String urlFeed = delivererFeedLoader.urlFeeds.get(getId());
final GsonRequest gsonRequest = new GsonRequest(urlFeed,
clazz,
null,
new Response.Listener<D>() {
@Override
public void onResponse(D data) {
deliverResult(data);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError volleyError) {
if (volleyError != null)
//deliverResult(null);
if (DEBUG) Log.e(TAG, "volleyError: " + volleyError.getMessage());
delivererFeedLoader.callBackMap.get(getId()).onErrorResponse(volleyError);
}
});
requestQueue.add(gsonRequest);
}
@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 = delivererFeedLoader.urlFeeds.get(getId());
if (DEBUG) Log.d(TAG, "Loader onForceLoad() : feedUrl = " + urlFeed);
doRequest(delivererFeedLoader.loaderClazzMap.get(getId()));
}
}
DelivererFeedLoader.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 DelivererFeedLoader implements Parcelable {
HashMap<Integer, Class> loaderClazzMap = new HashMap<>();
HashMap<Integer, Listener> callBackMap = new HashMap<>();
HashMap<Integer, String> urlFeeds = new HashMap<>();
public DelivererFeedLoader(){}
protected DelivererFeedLoader(Parcel in) {
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(loaderClazzMap);
dest.writeValue(callBackMap);
dest.writeValue(urlFeeds);
}
@SuppressWarnings("unused")
public static final Parcelable.Creator<DelivererFeedLoader> CREATOR = new Parcelable.Creator<DelivererFeedLoader>() {
@Override
public DelivererFeedLoader createFromParcel(Parcel in) {
return new DelivererFeedLoader(in);
}
@Override
public DelivererFeedLoader[] newArray(int size) {
return new DelivererFeedLoader[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.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;
/**
* 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,
Response.Listener<T> listener, Response.ErrorListener errorListener) {
super(Method.GET, url, errorListener);
this.clazz = clazz;
this.headers = headers;
this.listener = listener;
}
@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),
HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e) {
return Response.error(new ParseError(e));
} catch (JsonSyntaxException e) {
return Response.error(new ParseError(e));
}
}
}
// TO DO
- Пока нет проверок на ошибки добавляемых параметров в FeedLoader
- Остановка Loader
- Rename DelivererFeedLoader to DispatcherLoader
Автор: app-z