Search code examples
androidandroid-imageandroid-glide

Glide ModelLoader that loads either from local or remote


I have a custom Glide model to calculate the center crop for image. The same model is used for getting images from server and also from local storage.

Here's the model interface:

interface CenterCropImageInformation {
    fun getCenterCropUri(context: Context, @Px width: Int, @Px height: Int): Uri
}

And here is its ModelLoader that extends from BaseGlideUrlLoader:

class CenterCropImageInformationLoader private constructor(
    context: Context,
    concreteLoader: ModelLoader<GlideUrl, InputStream>,
    modelCache: ModelCache<CenterCropImageInformation, GlideUrl>?
) : BaseGlideUrlLoader<CenterCropImageInformation>(concreteLoader, modelCache) {
    private val applicationContext: Context = context.applicationContext

    override fun getUrl(
        model: CenterCropImageInformation, width: Int,
        height: Int, options: Options
    ): String {
        return model.getCenterCropUri(applicationContext, width, height).toString()
    }

    override fun handles(centerCropImageInformation: CenterCropImageInformation): Boolean {
        return true
    }

    /**
     * The default factory for [CenterCropImageInformation]s.
     */
    class Factory(
        private val applicationContext: Context,
        private val modelCache: ModelCache<CenterCropImageInformation, GlideUrl>?
    ) : ModelLoaderFactory<CenterCropImageInformation, InputStream> {

        override fun build(
            multiFactory: MultiModelLoaderFactory
        ): ModelLoader<CenterCropImageInformation, InputStream> {
            val modelLoader = multiFactory.build(GlideUrl::class.java, InputStream::class.java)
            return CenterCropImageInformationLoader(applicationContext, modelLoader, modelCache)
        }

        override fun teardown() {}
    }
}

This works fine for images with http/https scheme, but it doesn't work for file scheme - the one used for loading images from local device storage.

I had a look into Glide's source code and the closest ModelLoader that sounds like an option is UriLoader, but the issue that that one doesn't support custom models. It only supports Uri.

The optimal solution would be to use a pre-existing ModelLoader that is bundled with Glide, but unless I missed it, I couldn't find any that suits my needs. If that's really the case, how do I implement such ModelLoader?


Solution

  • I figured it out after reading Glide's ModelLoaders tutorial. The key is by delegating the loading to a ModelLoader that knows how to handle file and http/https scheme.

    What I had to do is to implement ModelLoader interface directly instead of extending BaseGlideUrlLoader. We already know that Glide's built-in UriLoader can handle both file and http/https scheme so we delegate to it. Now to get an instance of UriLoader we use the MultiModelLoaderFactory that is passed to our factory's build method. The default Glide configuration registers UriLoader for Uri + InputStream pair.

    class CenterCropImageInformationLoader(
            private val modelLoader: ModelLoader<Uri, InputStream>,
            private val modelCache: ModelCache< CenterCropImageInformation, Uri>
        ) : ModelLoader<CenterCropImageInformation, InputStream> {
            override fun buildLoadData(
                model: CenterCropImageInformation,
                width: Int,
                height: Int,
                options: Options
            ): ModelLoader.LoadData<InputStream>? {
                val uri: Uri = modelCache.get(model, width, height) ?: model.getUri(model, width, height)
                modelCache.put(model, width, height, uri)
                return modelLoader.buildLoadData(uri, width, height, options)
            }
    
        override fun handles(model: CenterCropImageInformation): Boolean = true
    
        class Factory(
            private val applicationContext: Context,
            private val modelCache: ModelCache<CenterCropImageInformation, Uri>
        ) : ModelLoaderFactory<CenterCropImageInformation, InputStream> {
    
             override fun build(
                 multiFactory: MultiModelLoaderFactory
             ): ModelLoader<CenterCropImageInformation, InputStream> {
                 val modelLoader = multiFactory.build(Uri::class.java, InputStream::class.java)
                 return CenterCropImageInformationLoader(applicationContext, modelLoader, modelCache)
             }
    
             override fun teardown() {}
         }
    }
    

    As we can see we no longer extend BaseGlideUrlLoader. Instead we implement ModelLoader interface and in buildLoadData() implementation we try to get the URI from cache (this is similar to what BaseGlideUrlLoader is doing) and then we call buildLoadData() on the ModelLoader that we passed to the constructor, which happens to be an instance of UriLoader as I mentioned earlier thanks to MultiModelLoaderFactory.

    It's really surprising that this type of ModelLoader is not part of Glide built-in model loaders.