In Glide 3.x we could add register a String model loader like following:
public class GlideService /* implements GlideModule*/ {
#Override
public void registerComponents(Context context, Glide glide) {
glide.register(String.class, InputStream.class, new HeaderedLoader.Factory());
}
private static class HeaderedLoader extends BaseGlideUrlLoader<String> {
public HeaderedLoader(Context context) {
super(context);
}
#Override
protected String getUrl(String model, int width, int height) {
return model;
}
#Override
protected Headers getHeaders(String model, int width, int height) {
LazyHeaders.Builder headersBuilder = new LazyHeaders.Builder();
if (BuildConfig.FLAVOR.equals("staging")) {
String auth = "username:password";
String base64 = Base64.encodeToString(auth.getBytes(), Base64.NO_WRAP);
headersBuilder.addHeader("Authorization", "Basic " + base64);
}
return headersBuilder.build();
}
public static class Factory implements ModelLoaderFactory<String, InputStream> {
#Override
public StreamModelLoader<String> build(Context context, GenericLoaderFactory factories) {
return new HeaderedLoader(context);
}
#Override
public void teardown() { }
}
}
}
But it's not clear from the docs how we can accomplish this for the lastest version.
I face the same issue as you.
Here is how i fixed it.
Your GlideService needs to extends AppGlideModule (if you are writing code for an application). For more details about this see glide documentation
As you are now using glide 4.x, your class HeaderedLoader now has overriden the two default constructor of BaseGlideUrlLoader<String>, these are protected HeaderedLoader(ModelLoader<GlideUrl, InputStream> concreteLoader) and protected HeaderedLoader(ModelLoader<GlideUrl, InputStream> concreteLoader, #Nullable ModelCache<String, GlideUrl> modelCache).
Create a factory for the HeaderedLoader class.
This should look like
static class Factory implements ModelLoaderFactory<String, InputStream> {
#Override
public ModelLoader<String, InputStream> build(MultiModelLoaderFactory multiFactory) {
ModelLoader<GlideUrl, InputStream> loader = multiFactory.build(GlideUrl.class, InputStream.class);
return new HeaderedLoader(loader);
}
#Override public void teardown() { /* nothing to free */ }
}
You will then override the public void registerComponents(Context context, Glide glide, Registry registry) method of glide module super class with the following
#Override
public void registerComponents(Context context, Glide glide, Registry registry) {
registry.append(String.class, InputStream.class, new HeaderedLoader.Factory());
}
You should be able to keep your logic for handling http header inside the HeaderedLoader class.
I have use this approach to add custom header to glide request with glide 4.0.
try this
import com.bumptech.glide.load.Options;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.load.model.ModelLoader;
import com.bumptech.glide.load.model.ModelLoaderFactory;
import com.bumptech.glide.load.model.MultiModelLoaderFactory;
import java.io.InputStream;
import okhttp3.Call;
import okhttp3.OkHttpClient;
/**
* A simple model loader for fetching media over http/https using OkHttp.
*/
public class OkHttpUrlLoader implements ModelLoader<GlideUrl, InputStream> {
private final Call.Factory client;
public OkHttpUrlLoader(Call.Factory client) {
this.client = client;
}
#Override
public boolean handles(GlideUrl url) {
return true;
}
#Override
public LoadData<InputStream> buildLoadData(GlideUrl model, int width, int height,
Options options) {
return new LoadData<>(model, new OkHttpStreamFetcher(client, model));
}
/**
* The default factory for {#link OkHttpUrlLoader}s.
*/
public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {
private static volatile Call.Factory internalClient;
private Call.Factory client;
private static Call.Factory getInternalClient() {
if (internalClient == null) {
synchronized (Factory.class) {
if (internalClient == null) {
internalClient = new OkHttpClient();
}
}
}
return internalClient;
}
/**
* Constructor for a new Factory that runs requests using a static singleton client.
*/
public Factory() {
this(getInternalClient());
}
/**
* Constructor for a new Factory that runs requests using given client.
*
* #param client this is typically an instance of {#code OkHttpClient}.
*/
public Factory(Call.Factory client) {
this.client = client;
}
#Override
public ModelLoader<GlideUrl, InputStream> build(MultiModelLoaderFactory multiFactory) {
return new OkHttpUrlLoader(client);
}
#Override
public void teardown() {
// Do nothing, this instance doesn't own the client.
}
}
}`
Related
so I have class constructor:
public class HealthDataStore { // this class is 3rd party api - can't modify
public HealthDataStore(Context context, HealthDataStore.ConnectionListener listener){ /* bla... */ }
/* bla... */
// with Listener Interface:
public interface ConnectionListener {
void onConnected();
void onConnectionFailed(HealthConnectionErrorResult var1);
void onDisconnected();
}
}
and in my repository class i have:
public class HealthRepository {
private string DSConnectionStatus;
public void connectDataStore(HealthDSConnectionListener listener) {
mStore = new HealthDataStore(app, listener);
mStore.connectService();
}
// with inner class:
public class HealthDSConnectionListener implements HealthDataStore.ConnectionListener{
#Override public void onConnected() { DSConnectionStatus = "Connected"; }
#Override public void onConnectionFailed(HealthConnectionErrorResult healthConnectionErrorResult) { DSConnectionStatus = "Connection Failed"; }
#Override public void onDisconnected() { DSConnectionStatus = "Disconnected"; }
};
}
and in my view model class i have below object:
public class SplashViewModel extends AndroidViewModel {
public void connectRepoDataStore(){
// repo is object of class HealthRepository
repo.connectDataStore(mConnectionListener)
// other things to do here
}
private final HealthRepository.HealthDSConnectionListener mConnectionListener = new HealthRepository.HealthDSConnectionListener(){
#Override public void onConnected() {
super.onConnected(); // i need this super to set DSConnectionStatus value
// other things to do here
}
#Override public void onConnectionFailed(HealthConnectionErrorResult error) {
super.onConnectionFailed(error); // i need this super to set DSConnectionStatus value
// other things to do here
}
#Override public void onDisconnected() {
super.onDisconnected(); // i need this super to set DSConnectionStatus value
// other things to do here
}
}
why is private final HealthRepository.HealthDSConnectionListener mConnectionListener = new HealthRepository.HealthDSConnectionListener() throw me error that the class is not enclosing class?
then how should i achieve this? to have my final listener class have capability to set DSConnectionStatus in healthrepository class?
Always try to avoid using inner classes if you know you'll have to extend them. Instead use a separate class, and swap the outer class with a field. If you need to modify a private field that you do not want to expose then create a package-private setter.
public class HealthRepository {
private String DSConnectionStatus;
public void connectDataStore(HealthDSConnectionListener listener) {
mStore = new HealthDataStore(app, listener);
mStore.connectService();
}
void setConnectionStatus(String status) {
DSConnectionStatus = status;
}
}
// create another class in the same package
public class HealthDSConnectionListener implements HealthDataStore.ConnectionListener {
private final HealthRepository repo;
public HealthDSConnectionListener(HealthRepository repo) {
this.repo = repo;
}
#Override public void onConnected() { repo.setConnectionStatus("Connected"); }
#Override public void onDisconnected() { repo.setConnectionStatus("Disconnected"); }
#Override public void onConnectionFailed(HealthConnectionErrorResult error) {
repo.setConnectionStatus("Connection Failed");
}
};
public class SplashViewModel extends AndroidViewModel {
private final HealthRepository repo;
public void connectRepoDataStore() {
// repo is object of class HealthRepository
repo.connectDataStore(mConnectionListener)
// other things to do here
}
private final HealthDSConnectionListener mConnectionListener = new HealthDSConnectionListener(repo) {
#Override public void onConnected() {
super.onConnected();
// ...
}
#Override public void onConnectionFailed(HealthConnectionErrorResult error) {
super.onConnectionFailed(error);
// ...
}
#Override public void onDisconnected() {
super.onDisconnected();
// ...
}
}
}
I was trying to refactor my code and move my code from Async Task to a loader. I came to know about the benefits of loaders through the Android Performance video series Loaders Android Performance
I know why Loaders are used and what classes it has and stuff (The theory). However I am unable to grasp the working concept and thus wrote this poor code. Thus I am also not able to debug it.
**EDIT: I was able to make it work but I still think I am calling it in a wrong manner.
new EarthquakeAsyncTaskLoader(this).forceLoad();
If anyone can help me out, on this.........**
import android.content.AsyncTaskLoader;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import java.io.IOException;
import java.util.ArrayList;
public class EarthQuakeActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<ArrayList<EarthQuakes>> {
ArrayList<EarthQuakes> earthquakes = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_earth_quake);
getSupportLoaderManager().initLoader(1, null, this);
}// End of onCreate
#Override
public Loader<ArrayList<EarthQuakes>> onCreateLoader(int id, Bundle args) {
**new EarthquakeAsyncTaskLoader(this).forceLoad();**
}
#Override
public void onLoadFinished(Loader<ArrayList<EarthQuakes>> loader, ArrayList<EarthQuakes> data) {
}
#Override
public void onLoaderReset(Loader<ArrayList<EarthQuakes>> loader) {
}
public class EarthquakeAsyncTaskLoader extends AsyncTaskLoader<ArrayList<EarthQuakes>> {
public EarthquakeAsyncTaskLoader(Context context) {
super(context);
}
#Override
protected void onStartLoading() {
// If the data is there, don't start again
if (earthquakes != null) {
deliverResult(earthquakes);
} else {
//Start the loader
forceLoad();
}
}
#Override
public ArrayList<EarthQuakes> loadInBackground() {
// Get the populated list from QueryUtils java class
try {
// Here in QueryUtils, I am making an HTTP network call
// Thus it has to be done in a helper background thread
earthquakes = QueryUtils.getArrayList();
} catch (IOException e) {
e.printStackTrace();
}
return earthquakes;
}
#Override
public void deliverResult(ArrayList<EarthQuakes> data) {
// Feed the adapter with data and display it
ListView earthquakesList = (ListView) findViewById(R.id.listV);
final EarthQuakeAdapter adapter = new EarthQuakeAdapter(getApplicationContext(), data);
earthquakesList.setAdapter(adapter);
earthquakesList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
EarthQuakes currentEarthquake = adapter.getItem(i);
Uri earthquakeUri = Uri.parse(currentEarthquake.getUrl());
// Create a new intent to view the earthquake URI
Intent websiteIntent = new Intent(Intent.ACTION_VIEW, earthquakeUri);
// Send the intent to launch a new activity
startActivity(websiteIntent);
}
});
}
}//End of Async Task Loader
//EarthQuakes is my class. I don't think you'll need this. But anyway:
public class EarthQuakes {
private double mMagnitude;
private String mLocationSmallText;
private String mLocationMainText;
private String mDateOfEarthquake;
private String mUrl;
// Default Constructor
public EarthQuakes(Double mag, String locationSmallText, String locationMainCityName, String dateE, String Url) {
this.mMagnitude = mag;
this.mLocationSmallText = locationSmallText;
this.mLocationMainText = locationMainCityName;
this.mDateOfEarthquake = dateE;
this.mUrl = Url;
}
// Public getters
public Double getMagnitude() {
return mMagnitude;
}
public String getLocationSmallTextEarthquake() {
return mLocationSmallText;
}
public String getLocationLargeTextEarthquake() {
return mLocationMainText;
}
public String getDateOfEarthquake() {
return mDateOfEarthquake;
}
public String getUrl() {
return mUrl;
}
}
This alternative will also work:
getSupportLoaderManager().initLoader(1, null, this).forceload();
However, this is just a way around an issue with loaders that is mentioned here.
This issue happens if you are using AsyncTaskLoader that is not a CursorLoader.
You need to implement onStartLoading() and handle calling forceLoad() there. Highly recommend going through the issue page.
If you are using multiple loaders throughout your app and don't want to implement onStartLoading() every time. Here's a custom loader class you can include in your app and inherit from this instead of the usual AsyncTaskLoader:
WrappedAsyncTaskLoader.java(Original Author: Alexander Blom)
public abstract class WrappedAsyncTaskLoader<D> extends AsyncTaskLoader<D> {
private D mData;
/**
* Constructor of <code>WrappedAsyncTaskLoader</code>
*
* #param context The {#link Context} to use.
*/
public WrappedAsyncTaskLoader(Context context) {
super(context);
}
/**
* {#inheritDoc}
*/
#Override
public void deliverResult(D data) {
if (!isReset()) {
this.mData = data;
super.deliverResult(data);
} else {
// An asynchronous query came in while the loader is stopped
}
}
/**
* {#inheritDoc}
*/
#Override
protected void onStartLoading() {
super.onStartLoading();
if (this.mData != null) {
deliverResult(this.mData);
} else if (takeContentChanged() || this.mData == null) {
forceLoad();
}
}
/**
* {#inheritDoc}
*/
#Override
protected void onStopLoading() {
super.onStopLoading();
// Attempt to cancel the current load task if possible
cancelLoad();
}
/**
* {#inheritDoc}
*/
#Override
protected void onReset() {
super.onReset();
// Ensure the loader is stopped
onStopLoading();
this.mData = null;
}
}
In Picasso there exists the RequestHandler class. And I can add custom RequestHandlers to Picasso.
How can this be done in Glide?
I for example want that following URI can be handled by a custom RequestHandler: "appicon:custom_data_to_interprete_manually"
EDIT - what I have so far
public class GlideConfiguration implements GlideModule {
#Override
public void applyOptions(Context context, GlideBuilder builder) {
// Apply options to the builder here.
builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888);
}
#Override
public void registerComponents(Context context, Glide glide) {
glide.register(CustomModelParams.class, CustomModelParams.class, new CustomFactory());
}
class CustomModelParams
{
final String data;
public CustomModelParams(String data)
{
this.data = data;
}
public String getId()
{
return data;
}
}
class CustomFactory implements ModelLoaderFactory<CustomModelParams, CustomModelParams>
{
#Override
public ModelLoader<CustomModelParams, CustomModelParams> build(Context context, GenericLoaderFactory loaderFactory) {
return new CustomModelLoader();
}
#Override
public void teardown() {
}
}
class CustomModelLoader implements ModelLoader<CustomModelParams, CustomModelParams>
{
public CustomModelLoader() {
super();
}
#Override
public DataFetcher<CustomModelParams> getResourceFetcher(final CustomModelParams model, int width, int height)
{
return new DataFetcher<CustomModelParams>()
{
#Override
public CustomModelParams loadData(Priority priority) throws Exception { return model; }
#Override
public void cleanup() { }
#Override
public String getId() { return model.getId(); }
#Override
public void cancel() { }
};
}
}
class CustomBitmapDecoder implements ResourceDecoder<CustomModelParams, Bitmap>
{
private final Context context;
public CustomBitmapDecoder(Context context)
{
this.context = context;
}
#Override
public Resource<Bitmap> decode(CustomModelParams source, int width, int height) throws IOException
{
BitmapPool pool = Glide.get(context).getBitmapPool();
Bitmap bitmap = pool.getDirty(width, height, Bitmap.Config.ARGB_8888);
if (bitmap == null) {
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
}
// TODO
// create custom bitmap from CustomModelParams!!!
return BitmapResource.obtain(bitmap, pool);
}
#Override
public String getId()
{
return CustomBitmapDecoder.class.getName();
}
}
}
QUESTION
How do I link those classes together? The Decoder must somehow be linked with the new Model
How do I define, that my custom loader can handle a request? I have to somehow determine if the url I get can be handled by this loader...
You can use ModelLoaders. See the Downloading custom sizes wiki for an example of a custom ModelLoader. Note that instead of a BaseGlideUrlLoader, you will want to register a ModelLoader that handles your particular data model.
I have implemented very simular code for my project (It loads other applications icons from Package Manager) and
#Override
public void registerComponents(Context context, Glide glide) {
glide.register(MyDataModel.class, Bitmap.class, new MyUrlLoader.Factory());
}
doesn't work for me.
So to make custom loaded works I just use Glide Builder
private final GenericRequestBuilder<MyDataModel, Bitmap, Bitmap, Bitmap> mGlideBuilder;
mGlideBuilder = Glide.with(mContext)
.using(new MyUrlLoader(mContext), Bitmap.class)
.from(MyDataModel.class)
.as(Bitmap.class)
.decoder(new MyBitmapDecoder())
.diskCacheStrategy(DiskCacheStrategy.NONE);
mGlideBuilder.load(entry).into(holder.icon);
and MyBitmapDecoder and MyUrlLoader declared as
public class MyBitmapDecoder implements ResourceDecoder<Bitmap, Bitmap> {
public class MyUrlLoader implements ModelLoader<MyDataModel, Bitmap> {
I am facing problem how to write test code for retrofit.The codes are as below.
MainActivity.java
import android.os.Bundle;
import java.util.List;
import gallery.com.brandlistview.Bird;
import retrofit.Callback;
import retrofit.client.Response;
public class MainActivity implements Callback<List<Bird>> {
#Override
protected void onCreate(Bundle savedInstanceState) {
Globals.getRestClient().getBirdClient().getBirdList(this);
}
#Override
public void success(List<Bird> birds, Response response) {
}
#Override
public void failure(--------) {
}
}
Bird.java
public class Bird {
private String image_url;
public String getImage_url() {
return image_url;
}
public void setImage_url(---------) {
-----------
}
}
Globals.java
public class globals{
public static RestClient restClient;
public static RestClient getRestClient() {
return restClient;
}
public static BirdClient getBirdClient() {
return getRestClient().getBirdClient();
}
}
RestClient.java
public class RestClient{
private BirdClient mBirdClient;
public RestClient() {
mBirdClient = new BirdClient(getBirdService());
}
public BirdClient getBirdClient() { return mBirdClient; }
public static BirdService getBirdService() {
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(URL)
.setLogLevel(RestAdapter.LogLevel.FULL)
.build();
return restAdapter.create(BirdService.class);
}
}
BirdClient.java
public class BirdClient{
private BirdService mAPIService;
public BirdClient(BirdService service) {
assert service != null;
mAPIService = service;
}
public void getBirdList(final Callback<List<Bird>> delegate) {
----------------------
}
}
BirdService.java
public interface BirdService {
#GET(URL)
void getBirdList(Callback<List<Bird>> objectManager);
}
I want to write test code to test if the number of items in List is equal to 15(for example) after successful callback of getBirdList() as shown is MainActivity.java . Also, I am using Robolectric and Mockito library for testing. I am trying to write since tuesday .please help.
I suggest that you don't test that the actual request to server is working, due to the server response might change and you may not have internet when running test, but test it with some mock data.
Here is an example where he's using mockito together with retrofit to unit-test api functionality:
http://www.mdswanson.com/blog/2013/12/16/reliable-android-http-testing-with-retrofit-and-mockito.html
Good luck!
I'm using RoboSpice with Google HTTP Client & GSON this way:
ApiSpiceService:
public class ApiSpiceService extends GoogleHttpClientSpiceService {
private static final int THREAD_COUNT = 3;
#Override
public CacheManager createCacheManager(Application application) throws CacheCreationException {
CacheManager cacheManager = new CacheManager();
GsonObjectPersisterFactory gsonObjectPersisterFactory = new GsonObjectPersisterFactory(application);
cacheManager.addPersister(gsonObjectPersisterFactory);
return cacheManager;
}
#Override
public int getThreadCount() {
return THREAD_COUNT;
}
}
Request:
public class InfoRequest extends GoogleHttpClientSpiceRequest<Contact> {
private final String url;
public InfoRequest() {
super(Contact.class);
this.url = "some-url/path.json";
}
#Override
public Contact loadDataFromNetwork() throws Exception {
HttpTransport httpTransport = new NetHttpTransport();
HttpRequestFactory httpRequestFactory = httpTransport.createRequestFactory(new InfoHttpRequestInitializer());
HttpRequest httpRequest = httpRequestFactory.buildGetRequest(new GenericUrl(url));
httpRequest.setParser(new GsonFactory().createJsonObjectParser());
return httpRequest.execute().parseAs(Contact.class);
}
private class InfoHttpRequestInitializer implements HttpRequestInitializer {
#Override
public void initialize(HttpRequest request) throws IOException {
}
}
}
Model (Contact.java):
public class Contact {
private String data;
}
BaseActivity:
public abstract class BaseActivity extends ActionBarActivity {
protected SpiceManager spiceManager = new SpiceManager(ApiSpiceService.class);
#Override
protected void onStart() {
super.onStart();
spiceManager.start(this);
}
#Override
protected void onStop() {
spiceManager.shouldStop();
super.onStop();
}
}
And MainActivity:
public class MainActivity extends BaseActivity {
private InfoRequest infoRequest;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
infoRequest = new InfoRequest();
}
#Override
protected void onStart() {
super.onStart();
spiceManager.execute(infoRequest, "txt", DurationInMillis.ALWAYS_EXPIRED, new TextRequestListener());
}
private class TextRequestListener implements RequestListener<Contact> {
#Override
public void onRequestFailure(SpiceException spiceException) {
//
}
#Override
public void onRequestSuccess(Contact s) {
//
}
}
It seems to be valid code, but, unfortunately, when it finish the request execution, field data in returned Contact instance is null.
There are no errors in logcat.
The requested content is 100% valid JSON. Why it is not being parsed?
Ok, I've found the solution. I need to add #Key annotation to fields in model. It's strange, because pure Gson does not require this.
public class Contact {
#Key
private String data;
}