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> {
Related
I try to display images from internet in Google map marker using Fresco (following this guide), but only a placeholder is displayed.
In callback marker is redrawing. Fresco's DraweeView is working fine with my url.
My marker:
public class MarkerHolder {
private final String TAG = MarkerHolder.class.getSimpleName();
private DraweeHolder mDraweeHolder;
private Handler mUiHandler;
public MarkerHolder() {
mUiHandler = new Handler(Looper.getMainLooper());
}
public void create(Context context, Uri uri, Drawable placeHolderDrawable, Callback<Drawable> drawCallback) {
mDraweeHolder = DraweeHolder.create(new GenericDraweeHierarchyBuilder(context.getResources())
.setPlaceholderImage(placeHolderDrawable)
.build(), context);
mDraweeHolder.setController(Fresco.newDraweeControllerBuilder()
.setUri(uri)
.setOldController(mDraweeHolder.getController())
.build());
mDraweeHolder.getTopLevelDrawable().setCallback(new Drawable.Callback() {
#Override
public void invalidateDrawable(#NonNull Drawable who) {
mDraweeHolder.onDraw();
drawCallback.onAction(who);
}
#Override
public void scheduleDrawable(#NonNull Drawable who, #NonNull Runnable what, long when) {
mUiHandler.postAtTime(what, who, when);
}
#Override
public void unscheduleDrawable(#NonNull Drawable who, #NonNull Runnable what) {
mUiHandler.removeCallbacks(what, who);
}
});
mDraweeHolder.onAttach();
mDraweeHolder.onDraw();
drawCallback.onAction(mDraweeHolder.getTopLevelDrawable());
}
public void destroy() {
mDraweeHolder.onDetach();
mDraweeHolder.getTopLevelDrawable().setCallback(null);
}
public interface Callback<T> {
public void onAction(T t);
}
}
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.
}
}
}`
I am trying to load a custom model into Glide but getting this error:
GlideExecutor: Request threw uncaught throwable
com.bumptech.glide.Registry$NoModelLoaderAvailableException: Failed to
find any ModelLoaders for model:
com.company.project.glide.Movie#aac331a
Glide Version: 4.0.0
My codes:
Model
public class Movie {
private String name;
private String artist;
public Movie(String name, String artist) {
this.name = name;
this.artist = artist;
}
public String getName() {
return name;
}
public String getArtist() {
return artist;
}
}
Module
#com.bumptech.glide.annotation.GlideModule
public class GlideModule extends AppGlideModule {
#Override
public boolean isManifestParsingEnabled() {
return false;
}
#Override
public void applyOptions(Context context, GlideBuilder builder) {
super.applyOptions(context, builder);
}
#Override
public void registerComponents(Context context, Registry registry) {
registry.append(Movie.class, InputStream.class, new MovieArtModel.Factory());
}
}
ModelLoader
public class MovieArtModel implements ModelLoader<Movie, InputStream> {
#Nullable
#Override
public LoadData<InputStream> buildLoadData(Movie movie, int width, int height, Options options) {
Timber.d("buildLoadData: ");
return new LoadData<>(new ObjectKey(movie), new MovieArtLoader(movie, width, height));
}
#Override
public boolean handles(Movie movie) {
return false;
}
public static class Factory implements ModelLoaderFactory<Movie, InputStream> {
#Override
public ModelLoader<Movie, InputStream> build(MultiModelLoaderFactory multiFactory) {
return new MovieArtModel();
}
#Override
public void teardown() {
}
}
static class MovieArtLoader implements DataFetcher<InputStream> {
private Movie movie;
private boolean isCancelled = false;
private int widthSize;
private int heightSize;
MovieArtLoader(Movie movie, int widthSize, int heightSize) {
Timber.d("MovieArtLoader: Initializing...width size = " + widthSize + " :: heightSize = " + heightSize);
this.movie = movie;
this.widthSize = widthSize;
this.heightSize = heightSize;
}
#Override
public void loadData(Priority priority, DataCallback<? super InputStream> callback) {
Timber.d("loadData");
//First check if request is not cancelled before starting request
if(!isCancelled()) {
InputStream inputStream = getMovieArtInputStream(movie);
if (inputStream != null) {
callback.onDataReady(inputStream);
} else {
callback.onLoadFailed(new IOException("Forced Glide network failure. Can't load Movie image"));
}
}
}
return null;
}
#Override public void cleanup() {
Timber.d("cleanup: ");
}
#Override public void cancel() {
Timber.d("cancel: ");
isCancelled = true;
}
#Override
public Class<InputStream> getDataClass() {
return null;
}
#Override
public DataSource getDataSource() {
return null;
}
private boolean isCancelled() {
return isCancelled;
}
}
Then I am loading it thus:
GlideApp.with(itemView.getContext())
.asBitmap()
.load(new Movie(book.getMovieName(), book.getArtist()))
.placeholder(R.drawable.movie_default_small)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.listener(this)
.into(imageView);
Please where am I getting it wrong?
EDIT
I applied the answer below but I began to get NPE. This is the stacktrace:
E/GlideExecutor: Request threw uncaught throwable
java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.Object.hashCode()' on a null object reference
at com.bumptech.glide.util.MultiClassKey.hashCode(MultiClassKey.java:66)
at android.support.v4.util.SimpleArrayMap.indexOfKey(SimpleArrayMap.java:320)
at android.support.v4.util.SimpleArrayMap.get(SimpleArrayMap.java:360)
at com.bumptech.glide.provider.LoadPathCache.get(LoadPathCache.java:34)
at com.bumptech.glide.Registry.getLoadPath(Registry.java:132)
at com.bumptech.glide.load.engine.DecodeHelper.getLoadPath(DecodeHelper.java:132)
at com.bumptech.glide.load.engine.DecodeHelper.hasLoadPath(DecodeHelper.java:128)
at com.bumptech.glide.load.engine.SourceGenerator.startNext(SourceGenerator.java:59)
at com.bumptech.glide.load.engine.DecodeJob.runGenerators(DecodeJob.java:282)
at com.bumptech.glide.load.engine.DecodeJob.runWrapped(DecodeJob.java:252)
at com.bumptech.glide.load.engine.DecodeJob.run(DecodeJob.java:222)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
at java.lang.Thread.run(Thread.java:818)
at com.bumptech.glide.load.engine.executor.GlideExecutor$DefaultThreadFactory$1.run(GlideExecutor.java:347)
#Override
public boolean handles(Movie movie) {
return true;
}
You need to do this or else Glide will ignore your ModelLoader, thinking it does not handle the provided Movie model.
NoModelLoaderAvailableException happened when no
{#linkcom.bumptech.glide.load.model.ModelLoader} is registered for a given
model class, and that fixed with #talkLittle answer, and plus on that Movie should implement equals() and hashCode() to get caching to work correctly.
The new NPE happened because you accepted #Nullable would try #NonNull annotation.
Check .load and .into:
Glide.with(this)
.load(check_here)
.apply(new RequestOptions().diskCacheStrategy(DiskCacheStrategy.NONE))
.into(new DrawableImageViewTarget(check_here));
I am using palette-v7:23.2.1 with glide:3.7.0 like mentioned below, but sometimes the dark vibrant color is not successfully extracted and I get the default color instead.
After I clear the glide's cache and try it with the same image again, I get the right color. The strange is, that light vibrant color is always extracted, but the dark one not.
What could be the problem and how to solve it?
In onCreateView():
Glide.with(mContext)
.load(artworkUrl)
.asBitmap()
.into(new BitmapImageViewTarget(mArtworkInToolbar) {
#Override
public void onResourceReady(Bitmap bitmap, GlideAnimation anim) {
new Palette.Builder(bitmap).generate(paletteAsyncListener);
super.onResourceReady(bitmap, anim);
}
});
Listener:
public final Palette.PaletteAsyncListener paletteAsyncListener =
new Palette.PaletteAsyncListener() {
#Override
public void onGenerated(Palette palette) {
if (palette == null) return;
int default = ContextCompat.getColor(mContext, R.color.colorPrimary);
int color = palette.getVibrantColor(default); //always ok
int colorDark = palette.getDarkVibrantColor(default); //not always
// --- Setting the color --
}
};
***Here this is worked for me***
make a class name PaletteBitmap.java
public class PaletteBitmap {
public final Palette palette;
public final Bitmap bitmap;
public PaletteBitmap(#NonNull Bitmap bitmap, #NonNull Palette palette) {
this.bitmap = bitmap;
this.palette = palette;
}
Bitmap getBitmap()
{
return bitmap;
}
}
class PaletteBitmapResource implements Resource<PaletteBitmap> {
private final PaletteBitmap paletteBitmap;
private final BitmapPool bitmapPool;
public PaletteBitmapResource(#NonNull PaletteBitmap paletteBitmap, #NonNull BitmapPool bitmapPool) {
this.paletteBitmap = paletteBitmap;
this.bitmapPool = bitmapPool;
}
#Override public PaletteBitmap get() {
return paletteBitmap;
}
#Override public int getSize() {
return Util.getBitmapByteSize(paletteBitmap.bitmap);
}
#Override public void recycle() {
if (!bitmapPool.put(paletteBitmap.bitmap)) {
paletteBitmap.bitmap.recycle();
}
}
}
class PaletteBitmapTranscoder implements ResourceTranscoder<Bitmap, PaletteBitmap> {
private final BitmapPool bitmapPool;
public PaletteBitmapTranscoder(#NonNull Context context) {
this.bitmapPool = Glide.get(context).getBitmapPool();
}
#Override public Resource<PaletteBitmap> transcode(Resource<Bitmap> toTranscode) {
Bitmap bitmap = toTranscode.get();
Palette palette = new Palette.Builder(bitmap).generate();
PaletteBitmap result = new PaletteBitmap(bitmap, palette);
return new PaletteBitmapResource(result, bitmapPool);
}
#Override public String getId() {
return PaletteBitmapTranscoder.class.getName();
}
}
**Now in Your onCreateView() do like this**
Glide.with(this).fromUri().asBitmap()
.transcode(new PaletteBitmapTranscoder(this),PaletteBitmap.class)
.fitCenter()load(uri).into(new ImageViewTarget<PaletteBitmap>(imageView){
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
#Override
protected void setResource(PaletteBitmap resource) {
imageView.setImageBitmap(resource.getBitmap());
int colorP = resource.palette.getMutedColor(ContextCompat.getColor(context, R.color.colorPrimary));
int colorD=resource.palette.getDarkMutedColor(ContextCompat.getColor(context,R.color .colorPrimaryDark));
// dowhatever you want
}
});;
Picasso is asynchronous, so i was wondering if there is any way i can test if an image is fully loaded, before executing any additional code?
Picasso.with(context).load(imageURI).into(ImageView);
// image fully loaded? do something else ..
If the image is fully loaded it will be set on the ImageView synchronously.
You can use the callback to assert this.
final AtomicBoolean loaded = new AtomicBoolean();
Picasso.with(context).load(imageURI).into(imageView, new Callback.EmptyCallback() {
#Override public void onSuccess() {
loaded.set(true);
}
});
if (loaded.get()) {
// The image was immediately available.
}
Using overloaded method .into(ImageView target, Callback callback) is appropriate for your case. You can use the base implementation or extend your own like
Base:
Picasso.with(context).load(url).into(target, new Callback(){
#Override
public void onSuccess() {
}
#Override
public void onError() {
}
});
Extended version:
package main.java.app.picasso.test;
/**
* Created by nikola on 9/9/14.
*/
public abstract class TargetCallback implements Callback {
private ImageView mTarget;
public abstract void onSuccess(ImageView target);
public abstract void onError(ImageView target);
public TargetCallback(ImageView imageView){
mTarget = imageView;
}
#Override
public void onSuccess() {
onSuccess(mTarget);
}
#Override
public void onError() {
onError(mTarget);
}
}
Usage:
Picasso.with(context).load(url).into(target, new TargetCallback(target) {
#Override
public void onSuccess(ImageView target) {
}
#Override
public void onError(ImageView target) {
}
});