0

Glide Version: 4.9.0

I tried to customized the RequestBuilder class, to have a customized builder, in this case I want to customize RequestBuilder#addListener function to have a @FunctionalInterface or Function0<R> as argument and to be functional for Java 8.

I've tried to read documentation about @GlideExtensions here & the difference between:

  1. @GlideOption: for adding customized RequestOptions as long first parameter is extended from BaseRequestOptions<*> in this case RequestBuilder<TranscodeType> extends BaseRequestOptions<RequestBuilder<TranscodeType>>
  2. @GlideType: Definitely creating different type of drawable, not doing much of configuration, therefore I didn't try it.

Due to RequestListener doesn't have @FunctionalInterface annotation, I ended-up using both Kotlin & Java 8 to achieve the lambda-like parameter function.

I've created a SafeGlideApp class to handle context related problem using RequestManager inside onCreate & checking if the activity isFinishing

class SafeGlideApp {

    companion object {

        private lateinit var rm: RequestManager
        private lateinit var target: ViewTarget<ImageView, Drawable>

        /**
         * This is mandatory to setup inside onCreate() on any Activity/Fragment
         * @param Activity receive the current activity if not finishing
         */
        @JvmStatic
        fun init(activity: Activity) {
            rm = Glide.with(activity)
        }

        @JvmStatic
        fun destroy() {
            target.clearOnDetach()
            rm.pauseAllRequests()
            rm.clear(target)
        }

        @JvmStatic
        fun with(activity: Activity, safeAction: (RequestManager) -> ViewTarget<ImageView, Drawable>) {
            init(activity)
            activity.safeRunnable {
                check(::rm.isInitialized)
                target = safeAction(rm)
            }
        }
    }
}

Here's my desired functions to call custom listener on SafeGlideApp.

Activity.java

SafeGlideApp.with(this, requestManager -> requestManager
                        .load(url)
                        .listener(
                                Helpers.wrapper(() -> doSomething()),
                                Helpers.wrapper(() -> doSomething())
                        )
                        .into(imageView)
);

In order to have an interop between kotlin-java, I created this wrapper inspired by everyone in stackoverflow. So, in the invocation calls, cleaner and simplier.

Helpers.java

    /**
     * This lambda wrapper function takes nothing as argument and return Unit
     * E.g: f(wrapper(() -> doSomethingVoid()))
     *
     * @param callable
     * @return Unit
     */
    public static Function0<Unit> wrapper(Runnable callable) {
        return () -> {
            callable.run();
            return Unit.INSTANCE;
        };
    }

And here's my approach so far to customize the RequestBuilder.

My generic listener class

CustomListener.kt

fun <T : Any> makeLoadingRequestListener(
        failedAction: (() -> Unit)?,
        readyAction: (() -> Unit)?
): RequestListener<T> = object : RequestListener<T> {
    override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<T>?, isFirstResource: Boolean): Boolean {
        failedAction?.invoke()
        return false
    }

    override fun onResourceReady(resource: T, model: Any?, target: Target<T>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
        readyAction?.invoke()
        return false
    }
}

Troubleshooting

  • Using explicit type Drawable will have a compile-error:
e: [kapt] An exception occurred: java.lang.IllegalArgumentException:
   @GlideOption methods must take a BaseRequestOptions<?> object as
   their first parameter, but the first parameter in
   MyAppGlideExtension#listener(RequestBuilder, Unit, Unit) is
   RequestBuilder<Drawable>

Here's my extension class MyGlideExtension.kt

@GlideExtension
open class MyAppGlideExtension private constructor() {

    companion object {

        @JvmStatic
        @GlideOption
        fun listener(
                options: RequestBuilder<Drawable>,
                failedAction: () -> Unit,
                readyAction: () -> Unit
        ): BaseRequestOptions<Drawable> {
            return options.addListener(makeLoadingRequestListener<Drawable>(
                    failedAction = failedAction,
                    readyAction = readyAction
            ))
        }
    }
}

As stated in this issue #3552 & #4030. FlickrGlideExtension example is using Java. Also, the author stated to use ? wildcard in Kotlin, in fact that's not supported in Kotlin.

  • I've also try the java approach like this, and also have a compile-error
@GlideExtension
public final class MyGlideExtension {

    private MyGlideExtension GlideExtension() {
        return this;
    }

    @GlideOption
    public static BaseRequestOptions<?> test(RequestBuilder<?> requestBuilder,
                                             Function0<Unit> failed,
                                             Function0<Unit> ready) {
        return requestBuilder.listener(new RequestListener<Object>() {
            @Override
            public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Object> target, boolean isFirstResource) {
                return false;
            }

            @Override
            public boolean onResourceReady(Object resource, Object model, Target<Object> target, DataSource dataSource, boolean isFirstResource) {
                return false;
            }
        });
    }
}
  • Unfortunately, I didn't know how to use projection to solve this issue out-projected type prohibits for RequestBuilder#addListener function
@GlideExtension
open class MyAppGlideExtension private constructor() {

    companion object {

        @JvmStatic
        @GlideOption
        fun listener(
                options: RequestBuilder<*>,
                failedAction: () -> Unit,
                readyAction: () -> Unit
        ): BaseRequestOptions<*> {
            return options.addListener(makeLoadingRequestListener<Object>(
                    failedAction = failedAction,
                    readyAction = readyAction
            ))
        }
    }
}
  • Manually extending RequestManager & RequestBuilder
class CustomRequestManager(
        glide: Glide, lifecycle: Lifecycle,
        treeNode: RequestManagerTreeNode, context: Context
) : RequestManager(glide, lifecycle, treeNode, context) {

    override fun load(string: String?): CustomRequestBuilder<Drawable> {
        return super.load(string) as CustomRequestBuilder<Drawable>
    }
}

class CustomRequestBuilder<TranscodeType : Any>(
        glide: Glide,
        requestManager: RequestManager,
        transcodeClass: Class<TranscodeType>,
        context: Context
) : RequestBuilder<TranscodeType>(glide, requestManager, transcodeClass, context) {

    fun listener(failedAction: () -> Unit, readyAction: () -> Unit): RequestBuilder<TranscodeType> {
        return addListener(makeLoadingRequestListener<TranscodeType>(
                failedAction = failedAction,
                readyAction = readyAction
        ))
    }
}

but failed when casting GlideApp.with(this) as CustomRequestManager and throws compile-error: GlideRequest cannot be casted to CustomRequestManager

Last but not least.

  • Is there any approach possible to extends/customize RequestBuilder class?
  • Or is this possible creating a helper class without extending RequestBuilder to use & wrap RequestListener<R> interface to be functional?
mochadwi
  • 1,190
  • 9
  • 32
  • 87
  • edited: I think, I don't have to go through all of this and thinks an alternative options: 1. fork the glide, and create different GlideExtensions features 2. just migrate to kotlin (this will definitely match my desired to be functional method) 3. creating a wrapper method (static) to receive a lambda that return a `RequestListener` – mochadwi Jan 01 '20 at 18:52
  • What is the error for? "I've also try the java approach like this, and also have a compile-error" – TWiStErRob Jan 02 '20 at 13:36
  • @TWiStErRob `addListener(RequestListener>) cannot be applied to (RequestListener)` – mochadwi Jan 02 '20 at 15:09
  • That's good, I think. It sounds like the annotation processor passed, but your code had an error in it because of wrong type. – TWiStErRob Jan 02 '20 at 16:02

1 Answers1

0

I think you need to take the error message literally:

    @JvmStatic
    @GlideOption
    fun listener(
            options: RequestBuilder<Drawable>,
            failedAction: () -> Unit,
            readyAction: () -> Unit
    ): BaseRequestOptions<Drawable> {

to

    @JvmStatic
    @GlideOption
    fun listener(
            options: RequestBuilder<*>, // no explicit type, wildcard only (=== Java's <?>)
            failedAction: () -> Unit,
            readyAction: () -> Unit
    ): BaseRequestOptions<*> {

to

    @JvmStatic
    @GlideOption
    fun listener(
            options: BaseRequestOptions<*>, // not a subtype, exactly that type
            failedAction: () -> Unit,
            readyAction: () -> Unit
    ): BaseRequestOptions<*> {

and then you can probably cast the options parameter to RequestOptions or the generated class' type, if necessary.

TWiStErRob
  • 44,762
  • 26
  • 170
  • 254
  • yes, I've also tried using generic `BaseRequestOptions<*>` to `RequestBuilder` see this [image here](https://imgur.com/a/iB0pS0w) – mochadwi Jan 02 '20 at 15:18
  • hmm, based on https://stackoverflow.com/a/40139018/253468, you might need `@JvmSuppressWildcards` – TWiStErRob Jan 02 '20 at 16:00