Lightweight data loading and caching library for android

Overview

ColdStorage

A lightweight data loading and caching library for android

License Downloads Codacy Badge Build Status Gitter

Quicklinks

  • Feature requests: Got a new requirement? Request it here and it will be delivered.
  • Bugs: Report a bug here and it will be squashed.
  • Questions: Get your questions about contributing, usage or anything related to the library answered here.
  • Examples : Check out various examples here.
  • Suggestions/Complaints/Feedbacks: Got something in your mind regarding this library? Please share and help us improve.
  • Upcoming features : If you are interested to see all the exciting features lined up for delivery.

Articles

Setup

Latest version Release

  • Add kotlin-kapt gradle plugin to app build.gradle file

     apply plugin: "kotlin-kapt"
    
  • Add the dependencies

        implementation "com.github.crypticminds.ColdStorage:coldstoragecache:{enter latest version}"
        kapt "com.github.crypticminds.ColdStorage:coldstoragecompiler:{enter latest version}"
        implementation "com.github.crypticminds.ColdStorage:coldstorageannotation:{enter latest version}"
    
    

You need to initialize the cache when the application starts. The initialization takes care of pulling previously cached data and loading them into the memory .

  • Create an application class and initialize the cache in the onCreate() method.

        import android.app.Application
        import com.arcane.coldstoragecache.cache.Cache
    
        class Application : Application() {
    
        override fun onCreate() {
            super.onCreate()
            Cache.initialize(context = applicationContext)
            }
        }

    You can configure the cache with additional parameters such as a global time to live, maximum cache size etc. Refer the wiki for more details.

  • Register your application in the android manifest file by providing the android:name attribute

    <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/AppTheme"
            android:name=".application.Application">
    </application>

Quick guide

This guide will only provide a basic usage guide. For detailed description of each component, usage and examples check the wiki

@LoadImage Annotation

You can annotate any ImageView present in an Activity , fragement or another view to load images from an URL and cache it for future use.

@LoadImage(
        R.id.image_1,
        "https://images.unsplash.com/photo-1549740425-5e9ed4d8cd34?ixlib=rb-1.2.1&w=1000&q=80",
        placeHolder = R.drawable.loading, enableLoadingAnimation = true, persistImageToDisk = true
    )
    lateinit var imageWithAnimation: ImageView

Images can be persisted into the internal storage using the "persistImageToDisk" parameter.

After the image views have been annotated , bind the class where the image views are present using the method Cache.bind(object).

You can pass the activity, fragement or the view to which the annotated ImageViews belong to.

In an activity, the method should be called after setContentView and in a fragemnt it should be called in onViewCreated method.

     override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.load_image_example)
            Cache.bind(this)
        }

Currently the cache can only be bound to an Activity , fragment or view.

@Parent Annotation

An annotation that helps binding a nested view to a resource id. Suppose you have a layout layout_1.xml which contains an ImageView. You have added this layout in your main layout using the tag.You can now use @LoadImage annotation on the ImageView by :

  • Provide an id to the include tag

    <include android:id="@+id/my_included_layout"
    ......
    other attributes
    />
  • Use Parent annotation along with LoadImage annotation

        @Parent(R.id.my_included_layout)
        @LoadImage(R.id.my_nested_image_view,"http://url.jpg"
        lateinit var childImageView : ImageView

@Freeze Annotation

Annotate your class using the freeze annotation to apply caching logic on top of all the public methods present in the class.

@Freeze(generatedClassName = "MyBeautifulCacheLayer")
class MakeRemoteCallWithFreeze {

    
    fun makeRemoteCallToServiceA(value: String): String {
        val url = "https://httpbin.org/get?param1=$value"
        val textResponse = URL(url).readText()
        return textResponse
    }


    /**
     * Here I am marking the parameters that will together form the cache key
     * with @CacheKey
     */
    fun makeRemoteCallToServiceB(
        @CacheKey parameter1: String,
        @CacheKey parameter2: String,
        parameter3: String
    ): String {
        val url = "https://httpbin.org/get?param1=$parameter1&param2=$parameter2&param3=$parameter3"
        val textResponse = URL(url).readText()
        return textResponse
    }
}

This will generate a class called "MyBeautifulCacheLayer" . You can use this class to call the methods.

//you need to implement the OnOperationSuccessfulCallback interface.
val callback = object : OnOperationSuccessfulCallback<String>{
        override fun onSuccess(output: String?, operation: String) {
            //handle the output here.
	    //operation is the name of the method that returns the output. In this case the output
	    //can be "makeRemoteCallToServiceB" or "makeRemoteCallToServiceA" . You can handle the output
	    //based on which method is returning it.
        }
    }

val cacheLayer = MyBeautifulCacheLayer()

cacheLayer.makeRemoteCallToServiceA("someString" , callback)

cacheLayer.makeRemoteCallToServiceB(.... )

@Refrigerate Annotation

Annotate your functions with refrigerate to cache the output of the function for a given set of inputs.

    @Refrigerate(timeToLive : 2000, operation = "cacheImage")
    fun downloadImage(@CacheKey url : String , @CacheKey data : String , variableThatIsNotAKey : String) : Bitmap {
    .....
    }

This will keep the bitmap in the cache for 2 seconds.

In the above example url and data will together form the key of the cache , such that if it determines that the same url and data is passed to the function (until the value expires in the cache) irrespective of the value of "variableThatIsNotAKey" it will return the data from the cache.

During compilation the annotations will generate a class "GeneratedCacheLayer and instead of using your annotated functions directly you will use them via this class.

To invoke the above functions you will call :-

    GeneratedCacheLayer.downloadImage("myurl", objectOfTheClassWhereTheMethodBelongs , callback)

    GeneratedCacheLayer.callRemoteService("myurl", "mydata" , "myrandomVariable" , objectOfTheClassWhereTheMethodBelongs , callback)

The generated method will have the same name and accept the same variables as the original method but with two extra parameters , one is the object of the class where the original annotated method is present in and the second is the callback (OnOperationsuccessfulCallback) which will be used to pass the cached data to the main thread from the background thread.

Create a custom cache layer

Create your cache layer by extending the Cache class. You will have to implement the update method. The update method should take care of fetching the data when the data is stale or is not present in the cache.

    import android.graphics.Bitmap
    import android.graphics.BitmapFactory
    import android.util.Base64
    import android.util.Log
    import com.arcane.coldstoragecache.cache.Cache
    import java.io.ByteArrayOutputStream
    import java.net.URL
    
    
    /**
     * An example class that is used to cache images
     * after downloading them from a given url.
     *
     */
    class ImageCache : Cache() {
    
        /**
         * The update function is only required here to specify
         * where to fetch the data from if the key is not present in the
         * cache.
         *
         * @param key Here the key is the url from where the
         * image needs to be downloaded.
         */
        override fun update(key: String): String? {
            return try {
                val url = URL(key)
                val connection = url.openConnection()
                connection.doInput = true
                connection.connect()
                val input = connection.getInputStream()
                val myBitmap = BitmapFactory.decodeStream(input)
                bitMapToString(myBitmap)
            } catch (e: Exception) {
                Log.e("EXCEPTION", "Unable to download image", e)
                return null
            }
        }
    
        /**
         * A transformer to convert the bitmap to string.
         */
        private fun bitMapToString(bitmap: Bitmap): String? {
            val baos = ByteArrayOutputStream()
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos)
            val b: ByteArray = baos.toByteArray()
            return Base64.encodeToString(b, Base64.DEFAULT)
        }
    }

The update method should return the value to be cached in form of a string. If you are planning to store complex objects , serialize them into a string and return them from the method.

Your cache is now ready. To use the cache create an instance of it and call the get method of the cache.

The get method accepts the key that needs to be fetched from the cache and a callback which will be used to return the result to the main thread. The cache performs all operations in a background thread and will never block the UI thread. You will need to implement the OnValueFetchedCallback interface and pass it to the get method of the cache. The cache will fetch the value and pass it to the callback method from where you can access it and use in the UI thread.

Optionally you can also pass a time to live value and a converter. They are explained in detail below.

    import android.graphics.Bitmap
    import android.os.Bundle
    import android.view.Menu
    import android.view.MenuItem
    import android.widget.Button
    import android.widget.ImageView
    import androidx.appcompat.app.AppCompatActivity
    import com.arcane.coldstorage.cache.ImageCache
    import com.arcane.coldstoragecache.callback.OnValueFetchedCallback
    import com.arcane.coldstoragecache.converter.impl.StringToBitmapConverter
    
    class MainActivity : AppCompatActivity(), OnValueFetchedCallback<Any?> {
    
        companion object {
            val URLS = arrayListOf(
                "https://images.unsplash.com/photo-1452857297128-d9c29adba80b?ixlib=rb-1.2.1&w=1000&q=80",
                "https://i.ytimg.com/vi/Pc20_oJQusc/maxresdefault.jpg",
                "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS1HHodR1IgMESyE95LqwLRTRFnfCpmKKw5RQHqnP_kWV9ugKaiIQ&s"
            )
        }
    
        /**
         * An instance of image cache.
         */
        private val imageCache: ImageCache = ImageCache()
    
        /**
         * The image view where the images will be displayed.
         */
        private lateinit var imageView: ImageView
    
        /**
         * The button used to change the image.
         */
        private lateinit var changeButton: Button
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            imageView = findViewById(R.id.image_view)
            changeButton = findViewById(R.id.change)
            checkImageCaching()
            changeButton.setOnClickListener {
                checkImageCaching()
            }
        }
    
        /**
         * Method to test image caching.
         */
        private fun checkImageCaching() {
            val converter = StringToBitmapConverter()
            imageCache.get(
                URLS.shuffled().take(1)[0],
                this,
                converter
            )
    
        }
    
        /**
         * When the image is downloaded , adding the image to
         * the image view.
         */
        override fun valueFetched(output: Any?) {
            imageCache.commitToSharedPref(applicationContext)
            runOnUiThread {
                val outputAsBitmap = output as Bitmap
                imageView.setImageBitmap(outputAsBitmap)
            }
        }
    }
    

The time to live value in the get method to specify how long a data needs to be stored in the cache.

The converter object takes care of deserializing the string into the object you need. It is an optional parameter. If the converter is not passed the cache will return the value as string.

License

Copyright 2020 Anurag Mandal

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Author

Anurag Mandal LinkedIn

You might also like...
A Lightweight PDF Viewer Android library which only occupies around 125kb while most of the Pdf viewer occupies up to 16MB space.
A Lightweight PDF Viewer Android library which only occupies around 125kb while most of the Pdf viewer occupies up to 16MB space.

Pdf Viewer For Android A Simple PDF Viewer library which only occupies around 125kb while most of the Pdf viewer occupies upto 16MB space. How to inte

Easy lightweight SharedPreferences library for Android in Kotlin using delegated properties
Easy lightweight SharedPreferences library for Android in Kotlin using delegated properties

Easy lightweight SharedPreferences library for Android in Kotlin using delegated properties Idea Delegated properties in Kotlin allow you to execute a

Koi, a lightweight kotlin library for Android Development.

Koi - A lightweight Kotlin library for Android Koi include many useful extensions and functions, they can help reducing the boilerplate code in Androi

A lightweight cache library written in Kotlin

[NEW] Released to Maven Central: 'com.github.yundom:kache:1.x.x' Kache A runtime in-memory cache. Installation Put this in your build.gradle implemen

Lightweight Kotlin DSL dependency injection library
Lightweight Kotlin DSL dependency injection library

Warehouse DSL Warehouse is a lightweight Kotlin DSL dependency injection library this library has an extremely faster learning curve and more human fr

A fast, lightweight, entity component system library written in Kotlin.

Fleks A fast, lightweight, entity component system library written in Kotlin. Motivation When developing my hobby games using LibGDX, I always used As

Kotools Types - a lightweight library that provides commonly used types for Kotlin

Kotools Types is a lightweight library that provides commonly used types for Kotlin

StaticLog - super lightweight static logging for Kotlin, Java and Android
StaticLog - super lightweight static logging for Kotlin, Java and Android

StaticLog StaticLog is a super lightweight logging library implemented in pure Kotlin (https://kotlinlang.org). It is designed to be used in Kotlin, J

The most complete and powerful data-binding library and persistence infra for Kotlin 1.3, Android & Splitties Views DSL, JavaFX & TornadoFX, JSON, JDBC & SQLite, SharedPreferences.

Lychee (ex. reactive-properties) Lychee is a library to rule all the data. ToC Approach to declaring data Properties Other data-binding libraries Prop

Comments
  • Does Cold Storage have a size limit not specified in the documents?

    Does Cold Storage have a size limit not specified in the documents?

    Describe the bug We are trying to use this library to cache MP3 files on an android device. These files are streamed to the device, converted into a string so they can be stored with cold storage. This works for a lot of the MP3 files we have but we noticed that some larger files (~2 MB) are not placed into the cache. Even checking right after they are added they do not appear in the cache. No visible error is printed out either

    Is there a size limit not described in the documentation? Is this expected behaviour?

    opened by Al-Spheres 1
  • Annotation to load data in Recycler views

    Annotation to load data in Recycler views

    Is your feature request related to a problem? Please describe. The current annotations will not help in loading images in Recycler views from a URL.

    Describe the solution you'd like TBD

    enhancement 
    opened by crypticminds 0
  • Logic for resizing images

    Logic for resizing images

    Is your feature request related to a problem? Please describe. @ImageView can be configured with a height and width value so that the images are resized before they are put into the ImageView

    Describe the solution you'd like Optimized logic for resizing bitmaps before they are added to the ImageView if the required values are passed to the annotation.

    enhancement help wanted good first issue good for beginners beginner 
    opened by crypticminds 0
  • Implement a counter to keep track of frequency of cache hits for an item in the cache,

    Implement a counter to keep track of frequency of cache hits for an item in the cache,

    Is your feature request related to a problem? Please describe.

    A cache should be able to prioritize which data must be evicted from the app memory to make room for new ones and one of the methods is to keep frequently used data in the app memory.

    Describe the solution you'd like A counter needs to be implemented that will keep track of how many times a key is requested from the cache.

    Additional context Every item that is stored in the cache has certain metadata attached to it like timeToLive, timestamp etc. An additional parameter needs to be added to this metadata which should be incremented every time the cache is requested for a particular key.

    enhancement help wanted good first issue good for beginners beginner 
    opened by crypticminds 0
Releases(4.1.1)
Owner
Cryptic Minds
Cryptic Minds
A composite Github Action to execute the Kotlin Script with compiler plugin and dependency caching!

Kotlin Script Github Action Kotlin can also be used as a scripting language, which is more safer, concise, and fun to write than bash or python. Githu

Suresh 9 Nov 28, 2022
Easy to use cryptographic framework for data protection: secure messaging with forward secrecy and secure data storage. Has unified APIs across 14 platforms.

Themis provides strong, usable cryptography for busy people General purpose cryptographic library for storage and messaging for iOS (Swift, Obj-C), An

Cossack Labs 1.6k Jan 8, 2023
A basic application demonstrating IPFS for collaborative data analysis, from the perspective of a Data Analysis Provider.

Spacebox A basic application demonstrating IPFS for collaborative data analysis, from the perspective of a Data Analysis Provider. Description This pr

null 0 Jan 15, 2022
Image loading for Android backed by Kotlin Coroutines.

An image loading library for Android backed by Kotlin Coroutines. Coil is: Fast: Coil performs a number of optimizations including memory and disk cac

Coil 8.8k Jan 7, 2023
A lightweight and simple Kotlin library for deep link handling on Android πŸ”—.

A lightweight and simple Kotlin library for deep link handling on Android ??.

Jeziel Lago 101 Aug 14, 2022
A lightweight, simple, smart and powerful Android routing library.

RxRouter Read this in other languages: δΈ­ζ–‡, English A lightweight, simple, smart and powerful Android routing library. Getting started Setting up the d

Season 323 Nov 10, 2022
Clickstream - A Modern, Fast, and Lightweight Android Library Ingestion Platform.

Clickstream is an event agnostic, real-time data ingestion platform. Clickstream allows apps to maintain a long-running connection to send data in real-time.

Gojek 60 Dec 24, 2022
🚟 Lightweight, and simple scheduling library made for Kotlin (JVM)

Haru ?? Lightweight, and simple scheduling library made for Kotlin (JVM) Why did you build this? I built this library as a personal usage library to h

Noel 13 Dec 16, 2022
Lightweight compiler plugin intended for Kotlin/JVM library development and symbol visibility control.

Restrikt A Kotlin/JVM compiler plugin to restrict symbols access, from external project sources. This plugin offers two ways to hide symbols: An autom

Lorris Creantor 18 Nov 24, 2022