Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to implement custom AsyncDrawableLoader #109

Closed
yjouyang opened this issue Mar 25, 2019 · 9 comments
Closed

How to implement custom AsyncDrawableLoader #109

yjouyang opened this issue Mar 25, 2019 · 9 comments
Milestone

Comments

@yjouyang
Copy link

  • Markwon version: {3.0}

With version 3.0, how could I implement a custom AsyncDrawableLoader since the built-in one seems doesn't have cache

@wax911
Copy link

wax911 commented Mar 25, 2019

You could use the Image OkHttp that includes a factory overload method which will allow you to configure OkHttp client behaviour for caching. However I wanted to use glide instead, so I just tried to create my own plugin using the source code provided for the OkHttp implementation

@yjouyang
Copy link
Author

You could use the Image OkHttp that includes a factory overload method which will allow you to configure OkHttp client behaviour for caching. However I wanted to use glide instead, so I just tried to create my own plugin using the source code provided for the OkHttp implementation

Yeah, I want to use glide too. I just cannot find the way to use a custom AsyncDrawableLoader. Could you please show me some code?

@noties
Copy link
Owner

noties commented Mar 25, 2019

Hello @yjouyang !

Default image loader indeed doesn't have cache. But, as @wax911 has mentioned, you can use OkHttpImagesPlugin that allows configuring OkHttpClient. It's located in image-okhttp artifact, code usage will be:

// configure OkHttpClient
final OkHttpClient okHttpClient = obtainHttpClient();

final Markwon markwon = Markwon.builder(context)
        .usePlugin(ImagesPlugin.createWithAssets(context))
        .usePlugin(OkHttpImagesPlugin.create(okHttpClient))
        .build();

As for creating own implementation of AsyncDrawableLoader... Yeah, previously (before 3.0.0) it was possible. Now, image loading is kind of built-in into core functionality. Although it has a lot of functionality

  • GIF
  • SVG
  • Data scheme (that allows inlining images into markdown)
  • etc

it still could use some attention and it is one of the primary targets for future improvement. Anyway, if current set of features doesn't work for you, we can introduce a way to provide own implementation of loader, for example like this:

.usePlugin(new AbstractMarkwonPlugin() {
    @Override
    public void configureImages(@NonNull AsyncDrawableLoader.Builder builder) {
        builder.implementation(new GlideImageLoader(context));
    }
})

One down-side is that all configured things will be ignored (scheme handlers, media decoders, placeholder, error drawables, etc), but maybe it's not that bad. If you find it interesting I can deploy a snapshot version with this feature implemented

@Kaned1as
Copy link

I'm using Glide too, would like to test such snapshot.

@Kaned1as
Copy link

@yjouyang meanwhile, came up with this

class MarkwonGlidePlugin(private val ctx: Context): AbstractMarkwonPlugin() {

    companion object {
        fun create(ctx: Context): MarkwonGlidePlugin {
            return MarkwonGlidePlugin(ctx)
        }
    }

    inner class GlideHandler: SchemeHandler() {

        override fun handle(raw: String, uri: Uri): ImageItem? {
            val glide = Glide.with(ctx)
                    .downloadOnly()
                    .load(raw)
                    .apply(RequestOptions()
                            .placeholder(R.drawable.image)
                            .error(R.drawable.image_broken)
                            .centerInside())
                    .submit()

            return ImageItem("image/*", glide.get().inputStream())
        }

    }

    override fun configureImages(builder: AsyncDrawableLoader.Builder) {
        builder.addSchemeHandler(NetworkSchemeHandler.SCHEME_HTTP, GlideHandler())
        builder.addSchemeHandler(NetworkSchemeHandler.SCHEME_HTTPS, GlideHandler())
    }
}

@yjouyang
Copy link
Author

@noties
I think it's a good idea to allow own implementation of loader! By the way, this library is awesome. Thanks for your job!

@yjouyang
Copy link
Author

@Adonai Appreciate it! I'll try this

@noties
Copy link
Owner

noties commented Mar 26, 2019

I've just pushed 3.0.1-SNAPSHOT version (instructions can be found here). It has new implementation builder method on AsyncDrawableLoader.Builder class

.usePlugin(new AbstractMarkwonPlugin() {
    @Override
    public void configureImages(@NonNull AsyncDrawableLoader.Builder builder) {
        builder.implementation(new GlideLoader());
    }
})

I haven't included actual GlideLoader implementation, but it can be like this:

private static class GlideImagePlugin extends AbstractMarkwonPlugin {

    @NonNull
    public static GlideImagePlugin create(@NonNull Context context) {
        return new GlideImagePlugin(context);
    }

    private final Context context;

    GlideImagePlugin(@NonNull Context context) {
        this.context = context;
    }

    @Override
    public void configureImages(@NonNull AsyncDrawableLoader.Builder builder) {
        builder.implementation(new GlideAsyncDrawableLoader(context));
    }

    @NonNull
    @Override
    public Priority priority() {
        return Priority.after(ImagesPlugin.class);
    }

    private static class GlideAsyncDrawableLoader extends AsyncDrawableLoader {

        private final Context context;
        private final Map<String, WeakReference<AsyncDrawableTarget>> cache
                = Collections.synchronizedMap(new HashMap<String, WeakReference<AsyncDrawableTarget>>(3));

        GlideAsyncDrawableLoader(@NonNull Context context) {
            this.context = context;
        }

        @Override
        public void load(@NonNull String destination, @NonNull AsyncDrawable drawable) {
            final AsyncDrawableTarget target = new AsyncDrawableTarget(drawable);
            Glide.with(context)
                    .load(destination)
                    .into(target);
            cache.put(destination, new WeakReference<AsyncDrawableTarget>(target));
        }

        @Override
        public void cancel(@NonNull String destination) {

            final WeakReference<AsyncDrawableTarget> reference = cache.remove(destination);
            final AsyncDrawableTarget target = reference != null
                    ? reference.get()
                    : null;

            if (target != null) {
                Glide.with(context).clear(target);
                target.drawable.clearResult();
            }
        }

        @Nullable
        @Override
        public Drawable placeholder() {
            // we can also return placeholder here instead of specifying for Glide
            return null;
        }

        private static class AsyncDrawableTarget extends CustomTarget<Drawable> {

            private final AsyncDrawable drawable;

            AsyncDrawableTarget(@NonNull AsyncDrawable drawable) {
                this.drawable = drawable;
            }

            @Override
            public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {

                // it's important to set resource bounds here
                DrawableUtils.applyIntrinsicBoundsIfEmpty(resource);

                drawable.setResult(resource);
            }

            @Override
            public void onLoadStarted(@Nullable Drawable placeholder) {
                if (placeholder != null) {
                    DrawableUtils.applyIntrinsicBoundsIfEmpty(placeholder);
                    drawable.setResult(placeholder);
                }
            }

            @Override
            public void onLoadFailed(@Nullable Drawable errorDrawable) {
                if (errorDrawable != null) {
                    DrawableUtils.applyIntrinsicBoundsIfEmpty(errorDrawable);
                    drawable.setResult(errorDrawable);
                }
            }

            @Override
            public void onLoadCleared(@Nullable Drawable placeholder) {
                drawable.clearResult();
            }
        }
    }
}

I have tested it and it works for regular images, but somehow GIF and SVG are not loaded (java.io.IOException: java.lang.RuntimeException: setDataSource failed: status = 0x80000000), so beware.

@Adonai it's a possible solution, but it doesn't handle different from plain images content-types (GIF, SVG, etc). So they will most likely fail. Of cause it's fine, if you won't have such images.

Anyway, if some one would want to create a special artifact with Glide image loading (in a form of a plugin), pull request is welcome

@noties noties added this to the 3.0.1 milestone Mar 26, 2019
@yjouyang
Copy link
Author

@noties Good job!

@noties noties mentioned this issue Apr 25, 2019
noties added a commit that referenced this issue Apr 25, 2019
3.0.1

* Add `AsyncDrawableLoader.Builder#implementation` method (#109)
* AsyncDrawable allow placeholder to have independent size (#115)
* `addFactory` method for MarkwonSpansFactory
* Add optional spans for list blocks (bullet and ordered)
* AsyncDrawable placeholder bounds fix
* SpannableBuilder setSpans allow array of arrays
* Add `requireFactory` method to MarkwonSpansFactory
* Add DrawableUtils
@noties noties closed this as completed Apr 25, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants