├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── rommansabbir │ │ └── cachexdemo │ │ ├── AnotherActivity.kt │ │ ├── MainActivity.kt │ │ ├── MainViewModel.kt │ │ ├── MyApplication.kt │ │ └── UserAuth.kt │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ └── ic_launcher_background.xml │ ├── layout │ ├── activity_another.xml │ └── activity_main.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── cachex ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── rommansabbir │ │ └── cachex │ │ ├── base │ │ ├── CacheX.kt │ │ └── CacheXImpl.kt │ │ ├── callback │ │ └── CacheXCallback.kt │ │ ├── converter │ │ └── CacheXDataConverter.kt │ │ ├── core │ │ └── CacheXCore.kt │ │ ├── exceptions │ │ ├── CacheXException.kt │ │ ├── CacheXInitException.kt │ │ ├── CacheXListLimitException.kt │ │ └── CacheXNoDataException.kt │ │ ├── functional │ │ └── Either.kt │ │ ├── params │ │ └── Params.kt │ │ ├── security │ │ └── CacheXEncryptionTool.kt │ │ ├── storage │ │ ├── CacheXStorage.kt │ │ └── CacheXStorageImpl.kt │ │ ├── usecase │ │ ├── GetListCacheUseCase.kt │ │ ├── GetSingleCacheUseCase.kt │ │ ├── ListCacheUseCase.kt │ │ ├── SingleCacheUseCase.kt │ │ └── UseCase.kt │ │ └── worker │ │ ├── CacheXWorkers.kt │ │ └── CacheXWorkersImpl.kt │ └── res │ └── values │ └── strings.xml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Release](https://jitpack.io/v/jitpack/android-example.svg)](https://jitpack.io/#rommansabbir/CacheX) 2 | # CacheX 3 | A feasible caching library for Android. 4 | 5 | ## Features 6 | * Real-time update 7 | * Lightweight 8 | * Secure 9 | * Thread Safe 10 | 11 | ## How does it work? 12 | Caching is just a simple key-value pair data saving procedure. CacheX follows the same approach. CacheX uses SharedPreference as storage for caching data. Since we really can't just save the original data because of security issues. CacheX uses AES encryption & decryption behind the scene when you are caching data or fetching data from the cache. Also, you can observer cached data in real-time. 13 | 14 | ## Documentation 15 | 16 | ### Installation 17 | 18 | --- 19 | Step 1. Add the JitPack repository to your build file 20 | 21 | ```gradle 22 | allprojects { 23 | repositories { 24 | maven { url 'https://jitpack.io' } 25 | } 26 | } 27 | ``` 28 | 29 | Step 2. Add the dependency 30 | 31 | ```gradle 32 | dependencies { 33 | implementation 'com.github.rommansabbir:CacheX:Tag' 34 | } 35 | ``` 36 | 37 | --- 38 | 39 | ### Version available 40 | 41 | | Latest Releases 42 | | ------------- | 43 | | 2.1.0 | 44 | 45 | --- 46 | 47 | ## What's new in this version? 48 | * Implementation of UseCase which interact between the app layer & library layer 49 | * CacheX works asynchronously 50 | * Performance Improved 51 | 52 | ## What is UseCase & what its implementation? 53 | * Simply UseCase in an interactor between two layer to communicate with each others (If you know you about Android Clean Architecture, you must be familiar with UseCase. It is one of the core component of ACA) 54 | * UseCase take input from the respective thread and execute the whole operatin under a Background Thread & return the data on Main Thread 55 | 56 | ### What about the implementation or the work flow? 57 | * Initialize CacheX properly 58 | * Get an instance of CacheX (Instance is singleton) 59 | * Call proper method to cahce data or get data from cache 60 | * Method return a respective use case 61 | * Provide the proper param to the use case & use case will return either expected data or an exception 62 | 63 | Simple, huh? (Feel free to give any suggestion, I would love to get suggestion for further improvement) 64 | 65 | --- 66 | 67 | # Usages 68 | ## Instantiate CacheX components in your app application class 69 | 70 | **This step is important!!** 71 | 72 | Define an encryption key to instantiate CacheX components. 73 | This key will be used to encrypt or decrypt data using AES algorithm. 74 | 75 | Initialize CacheX component by calling `CacheXCore.init()` pass contex, encryption key & app name for CacheX session. 76 | 77 | Don't forget to call at `CacheXCore.init()` from 78 | application layer otherwise, CacheX will not work properly and 79 | will throw an exception. 80 | 81 | ```` 82 | private val encryptionKey = "!x@4#w$%f^g&h*8(j)9b032ubfu8238!" 83 | 84 | override fun onCreate() { 85 | super.onCreate() 86 | CacheXCore.init(this, encryptionKey, getString(R.string.app_name)) 87 | } 88 | ```` 89 | 90 | --- 91 | 92 | ## How to access? 93 | 94 | Call `CacheXCore.getInstance()` to get reference of `CacheX` and you can access all public methods. 95 | 96 | --- 97 | 98 | ## What's the purpose of the UseCase? 99 | 100 | ## Available public methods 101 | 102 | * `fun cacheSingle(): SingleCacheUseCase` - To cache single data which return an instance of `SingleCacheUseCase` 103 | 104 | * `fun getCacheSingle(): GetSingleCacheUseCase` - To get single data from cache which return an instance of `GetSingleCacheUseCase` 105 | 106 | * `fun cacheList(): ListCacheUseCase` - To cache list of data which return an instance of `ListCacheUseCase` 107 | 108 | * `fun getCacheList(): GetListCacheUseCase` - To get list of cached data from cache which return an instance of `GetListCacheUseCase` 109 | 110 | * `fun registerListener(callback: CacheXCallback, key: String)` - To register listener respective to a `Key` & notify the listener through the callback on data changes 111 | 112 | * `fun registerListener(callback: CacheXCallback, key: ArrayList)` - - To register list of listeners respective to a `Key List` & notify the listener through the callback on data changes 113 | 114 | * `fun unregisterListener(key: String)` - Unregister any listener according to the respective key 115 | 116 | * `fun unregisterListener(key: ArrayList)` - Unregister list of listeners according to the respective key list 117 | 118 | * `fun clearListeners()` - Clear all listeners 119 | 120 | * `fun clearCacheByKey(key: String)` - Clear any cache by it's key 121 | 122 | * `fun clearAllCache()` - Clear all cache 123 | 124 | --- 125 | 126 | ## How to use the callback to get notified on data changes? 127 | 128 | * Implement the callback to `Activity` or `Fragment` or `ViewModel`. 129 | ```` 130 | class MainViewModel : ViewModel(), CacheXCallback { 131 | override fun onChanges(key: String) { 132 | when (key) { 133 | MainActivity.SINGLE_KEY -> { 134 | getSingleFromCache(key) 135 | } 136 | MainActivity.LIST_KEY -> { 137 | getListFromCache(key) 138 | } 139 | } 140 | } 141 | 142 | private fun getListFromCache(key: String) {...} 143 | 144 | private fun cacheLSingle(key: String, data: String) {...} 145 | } 146 | ```` 147 | 148 | * Register a listener or list of listener from your `Activity` or `Fragment` `onResume()` method. In this example, 149 | I have implemented the callback on my respective view model. 150 | ```` 151 | override fun onResume() { 152 | super.onResume() 153 | CacheXCore.getInstance().registerListener(viewModel, arrayListOf(LIST_KEY, SINGLE_KEY)) 154 | //or 155 | CacheXCore.getInstance().registerListener(viewModel, LIST_KEY) 156 | } 157 | ```` 158 | 159 | * To unregister a listener or list of listeners from your `Activity` or `Fragment` `onStop()` method. 160 | ```` 161 | override fun onStop() { 162 | CacheXCore.getInstance().unregisterListener(arrayListOf(LIST_KEY, SINGLE_KEY)) 163 | //or 164 | CacheXCore.getInstance().unregisterListener(LIST_KEY) 165 | super.onStop() 166 | } 167 | ```` 168 | --- 169 | 170 | ##### Need more information regarding the solid implementation? - Check out the sample application. 171 | 172 | --- 173 | 174 | ### Contact me 175 | [Portfolio](https://www.rommansabbir.com/) | [LinkedIn](https://www.linkedin.com/in/rommansabbir/) | [Twitter](https://www.twitter.com/itzrommansabbir/) | [Facebook](https://www.facebook.com/itzrommansabbir/) 176 | 177 | ### License 178 | 179 | --- 180 | [Apache Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html) 181 | 182 | ```` 183 | Copyright (C) 2021 Romman Sabbir 184 | 185 | Licensed under the Apache License, Version 2.0 (the "License"); 186 | you may not use this file except in compliance with the License. 187 | You may obtain a copy of the License at 188 | 189 | http://www.apache.org/licenses/LICENSE-2.0 190 | 191 | Unless required by applicable law or agreed to in writing, software 192 | distributed under the License is distributed on an "AS IS" BASIS, 193 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 194 | See the License for the specific language governing permissions and 195 | limitations under the License. 196 | ```` 197 | 198 | 199 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply plugin: 'kotlin-kapt' 5 | 6 | android { 7 | compileSdkVersion 29 8 | buildToolsVersion "29.0.2" 9 | 10 | defaultConfig { 11 | applicationId "com.rommansabbir.cachexdemo" 12 | minSdkVersion 19 13 | targetSdkVersion 29 14 | versionCode 1 15 | versionName "1.0" 16 | 17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 18 | } 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | dataBinding.enabled = true 26 | compileOptions { 27 | sourceCompatibility JavaVersion.VERSION_1_8 28 | targetCompatibility JavaVersion.VERSION_1_8 29 | } 30 | kotlinOptions { 31 | jvmTarget = "1.8" 32 | } 33 | } 34 | 35 | dependencies { 36 | implementation fileTree(dir: 'libs', include: ['*.jar']) 37 | implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 38 | implementation 'androidx.appcompat:appcompat:1.2.0' 39 | implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" 40 | implementation 'androidx.core:core-ktx:1.3.2' 41 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4' 42 | testImplementation 'junit:junit:4.13.1' 43 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' 44 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 45 | 46 | implementation project(path: ':cachex') 47 | // implementation 'com.github.rommansabbir:CacheX:1.0-beta' 48 | 49 | implementation 'com.fasterxml.jackson.core:jackson-core:2.5.3' 50 | implementation 'com.fasterxml.jackson.core:jackson-annotations:2.5.3' 51 | implementation 'com.fasterxml.jackson.core:jackson-databind:2.5.3' 52 | 53 | 54 | def lifecycle_version = "2.2.0" 55 | def arch_version = "2.1.0" 56 | 57 | // ViewModel 58 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" 59 | // LiveData 60 | implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" 61 | // Lifecycles only (without ViewModel or LiveData) 62 | implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version" 63 | 64 | // Saved state module for ViewModel 65 | implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version" 66 | 67 | // Annotation processor 68 | //noinspection LifecycleAnnotationProcessorWithJava8 69 | kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version" 70 | // alternately - if using Java8, use the following instead of lifecycle-compiler 71 | implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" 72 | 73 | // optional - helpers for implementing LifecycleOwner in a Service 74 | implementation "androidx.lifecycle:lifecycle-service:$lifecycle_version" 75 | 76 | // optional - ProcessLifecycleOwner provides a lifecycle for the whole application process 77 | implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version" 78 | 79 | // optional - ReactiveStreams support for LiveData 80 | implementation "androidx.lifecycle:lifecycle-reactivestreams-ktx:$lifecycle_version" 81 | 82 | // optional - Test helpers for LiveData 83 | testImplementation "androidx.arch.core:core-testing:$arch_version" 84 | 85 | 86 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9' 87 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9' 88 | 89 | implementation 'com.rugovit.eventlivedata:eventlivedata:1.0' 90 | } 91 | 92 | allprojects { 93 | repositories { 94 | maven { url 'https://jitpack.io' } 95 | } 96 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/rommansabbir/cachexdemo/AnotherActivity.kt: -------------------------------------------------------------------------------- 1 | package com.rommansabbir.cachexdemo 2 | 3 | import android.os.Bundle 4 | import android.widget.Toast 5 | import androidx.appcompat.app.AppCompatActivity 6 | 7 | class AnotherActivity : AppCompatActivity() { 8 | var repeater = 0 9 | private val realtimeCache = "RealtimeTesting" 10 | 11 | override fun onCreate(savedInstanceState: Bundle?) { 12 | super.onCreate(savedInstanceState) 13 | setContentView(R.layout.activity_another) 14 | repeater = intent.getIntExtra("counter", 0) 15 | // val cacheX = CacheX.getInstance() 16 | // doTheMagic(cacheX) 17 | // cacheX.getCache(String::class.java, realtimeCache, { 18 | // showMessage(it) 19 | // }, { 20 | // showMessage(it.message.toString()) 21 | // }, this) 22 | } 23 | 24 | private fun showMessage(msg: String) { 25 | Toast.makeText(this, msg, Toast.LENGTH_SHORT).show() 26 | } 27 | 28 | // private fun doTheMagic(cacheX: CacheX) { 29 | // CoroutineScope(Dispatchers.IO).launch { 30 | // delay(3000) 31 | // repeater += 1 32 | // cacheX.doCache("Cached $repeater times", realtimeCache, { 33 | // doTheMagic(cacheX) 34 | // }, { 35 | // 36 | // }) 37 | // } 38 | // } 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rommansabbir/cachexdemo/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.rommansabbir.cachexdemo 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import android.util.Log 6 | import android.widget.Button 7 | import android.widget.TextView 8 | import android.widget.Toast 9 | import androidx.appcompat.app.AppCompatActivity 10 | import androidx.lifecycle.ViewModelProvider 11 | import androidx.lifecycle.observe 12 | import com.rommansabbir.cachex.core.CacheXCore 13 | import kotlinx.android.synthetic.main.activity_main.* 14 | import kotlinx.coroutines.launch 15 | import kotlin.random.Random 16 | 17 | 18 | class MainActivity : AppCompatActivity() { 19 | companion object { 20 | val LIST_KEY = "ListKey" 21 | val SINGLE_KEY = "SingleKey" 22 | } 23 | 24 | private var repeater = 0 25 | 26 | 27 | private lateinit var viewModel: MainViewModel 28 | 29 | override fun onCreate(savedInstanceState: Bundle?) { 30 | super.onCreate(savedInstanceState) 31 | setContentView(R.layout.activity_main) 32 | 33 | viewModel = ViewModelProvider(this).get(MainViewModel::class.java) 34 | viewModel.onChanges.observe(this) { it -> 35 | it.let { arrayList -> 36 | var dataFound = "List Update" 37 | arrayList.forEach { 38 | dataFound += "${it.username}, " 39 | } 40 | findViewById(R.id.tv_list_update).text = dataFound 41 | } 42 | } 43 | viewModel.onChangesSingle.observe(this) { 44 | findViewById(R.id.tv_single_update).text = "Single Update: $it" 45 | } 46 | 47 | findViewById