Skip to content

Commit

Permalink
Merge pull request #1 from rommansabbir/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
rommansabbir authored Dec 23, 2021
2 parents f2bce07 + db20e8e commit 6c6b245
Show file tree
Hide file tree
Showing 12 changed files with 297 additions and 123 deletions.
46 changes: 27 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* Thread Safe

## How does it work?
Caching is just a simple key-value pair data saving procedure. StoreX follows the same approach. StoreX uses SharedPreference as storage for caching data. Since we really can't just save the original data because of security issues. StoreX 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.
Caching is just a simple key-value pair data saving procedure. StoreX follows the same approach. StoreX uses SharedPreference/Cache Directory (as File) as storage for caching data. Since we really can't just save the original data because of security issues. StoreX 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.

## Documentation

Expand Down Expand Up @@ -40,48 +40,43 @@ dependencies {

| Latest Releases
| ------------- |
| 1.2.0 |
| 2.0.0 |

---

### What's new in this version?
- Multiple instance of StoreX with differnet configuration & storage
- Multiple instance of StoreX with differnet configuration & different storage type support (SharedPref/Cache Storage as File)
- Added support for custom Coroutine Scopes

## Initialize
````
// Create multiple identifers
object StoreXIdentifiers {
val mainConfig : StoreXConfig = StoreXConfig("Something_1", "main_pref")
val anotherConfig : StoreXConfig = StoreXConfig("Something_2", "secondary_pref")
val mainConfig : StoreXConfig = StoreXConfig(
"Something_1",
"main_pref",
writeOrGetAsFileUsingCacheDirectory= true)
val anotherConfig : StoreXConfig = StoreXConfig(
"Something_2",
"secondary_pref",
writeOrGetAsFileUsingCacheDirectory= false)
}
````

````
// Deprecated. Will be removed in the future version
StoreXCore.init(this, getString(R.string.app_name))
// New method
StoreXCore.init(this, mutableListOf(
StoreXIdentifiers.mainConfig,
StoreXIdentifiers.mainConfig,
StoreXIdentifiers.anotherConfig,
))
````

## How To Access
````
// Deprecated. Will be removed in the future version
StoreXCore.instance()
// New method
StoreXCore.instance(configs: StoreXConfig)
````
or else, use the extension function
````
// Deprecated. Will be removed in the future version
storeXInstance()
// New method
fun storeXInstance(config: StoreXConfig)
````
which return an instance of `StoreX` [Note: You must initalize `StoreX` properly before accessing or else it will throw `NotInitializedException`]
Expand All @@ -99,13 +94,22 @@ Example:
`````
class MyClass:StoreAbleObject()
`````
How to save the object (Main Thread)

````
//How to save the object (Main Thread) [Deprecated]
StoreX.put(key: String, value: StoreAbleObject)
//How to save the object (Backed by Coroutine)
StoreX.put(scope: CoroutineScope, key: String, value: StoreAbleObject)
````

or use Async
````
//How to save the object with Callback [Deprecated]
StoreX.put<T : StoreAbleObject>(key: String, value: StoreAbleObject, callback: SaveCallback<T>)
//How to save the object with Callback (Backed by Coroutine)
StoreX.put<T : StoreAbleObject>(scope: CoroutineScope, key: String, value: StoreAbleObject, callback: SaveCallback<T>)
````


Expand All @@ -116,7 +120,11 @@ StoreX.get<T : StoreAbleObject>(key: String, objectType: Class<T>): T
````
or use Async
````
//Deprecated
StoreX.<T : StoreAbleObject>get(key: String, objectType: Class<T>, callback: GetCallback<T>)
//Backed by Coroutine
StoreX.<T : StoreAbleObject>get(scope: CoroutineScope,key: String, objectType: Class<T>, callback: GetCallback<T>)
````

## How to get notified on data changes?
Expand Down
2 changes: 1 addition & 1 deletion StoreX/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ android {
buildToolsVersion "30.0.3"

defaultConfig {
minSdkVersion 16
minSdkVersion 19
targetSdkVersion 30
versionCode 1
versionName "1.0"
Expand Down
93 changes: 93 additions & 0 deletions StoreX/src/main/java/com/rommansabbir/storex/BaseStoreXInstance.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package com.rommansabbir.storex

import android.app.Application
import android.content.Context
import android.content.SharedPreferences
import java.io.File
import java.io.FileOutputStream
import java.nio.charset.Charset
import java.util.*

abstract class BaseStoreXInstance(
private val application: Application,
private val prefRef: String
) : StoreXStorage {
// Storage
private var mSharedPreferences: SharedPreferences =
application.getSharedPreferences(prefRef, Context.MODE_PRIVATE)

private fun getSharedPref(): SharedPreferences {
return mSharedPreferences
}

protected fun notifyClients(key: String, instance: StoreX) {
StoreXCore.subscriberList().keys.forEach { cacheKey ->
StoreXCore.subscriberList()[cacheKey]?.let {
if (cacheKey.contains(key) && it.getKey() == cacheKey) {
it.callback.onDataChanges(it, instance)
}
}
}
}

@Throws(Exception::class)
override fun doCache(key: String, value: String, writeToCacheDirectory: Boolean): Boolean {
if (writeToCacheDirectory) {
return doCacheToCacheDirectory(key, value)
}
getSharedPref().edit().putString(key, value).apply()
return true
}

private fun doCacheToCacheDirectory(key: String, value: String): Boolean {
//Create new file
val file = File(application.cacheDir, key)
//If file already exists, delete the previous one and store the new one
if (file.exists()) {
file.delete()
}
file.createNewFile()

//Write the file to the cache dir
val fos = FileOutputStream(file)
fos.write(value.toByteArray(Charset.defaultCharset()))
fos.flush()
fos.close()
return true
}

@Throws(Exception::class)
override fun getCache(key: String, getFromCacheDirectory: Boolean): String? {
if (getFromCacheDirectory) {
return getCacheFromCacheDirectory(key)
}
return getSharedPref().getString(key, null)
}

private fun getCacheFromCacheDirectory(key: String): String? {
val file = File(application.cacheDir, key)
//If file exists decode the file to String else return null
return if (file.exists()) {
val content: StringBuilder = java.lang.StringBuilder(file.length().toInt())
val scanner = Scanner(file)
while (scanner.hasNext()) {
content.append(scanner.nextLine() + System.lineSeparator())
}
content.toString()
} else {
null
}
}

override fun registerListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) {
this.mSharedPreferences.registerOnSharedPreferenceChangeListener(listener)
}

override fun clearCacheByKey(key: String) {
mSharedPreferences.edit().remove(key).apply()
}

override fun clearAllCache() {
mSharedPreferences.edit().clear().apply()
}
}
28 changes: 25 additions & 3 deletions StoreX/src/main/java/com/rommansabbir/storex/StoreX.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,44 @@ import com.rommansabbir.storex.callbacks.SaveCallback
import kotlinx.coroutines.CoroutineScope

interface StoreX {
@Deprecated(
"Use new method with Coroutine Support",
replaceWith = ReplaceWith("StoreX.put(scope : CoroutineScope, key: String, value: StoreAbleObject)")
)
@Throws(RuntimeException::class)
fun put(key: String, value: StoreAbleObject): Boolean

@Throws(RuntimeException::class)
fun put(scope : CoroutineScope, key: String, value: StoreAbleObject)
fun put(scope: CoroutineScope, key: String, value: StoreAbleObject)

@Deprecated(
"Use new method with Coroutine Support",
replaceWith = ReplaceWith("StoreX.put(scope: CoroutineScope, key: String, value: StoreAbleObject, callback: SaveCallback<T>)")
)
fun <T : StoreAbleObject> put(key: String, value: StoreAbleObject, callback: SaveCallback<T>)

fun <T : StoreAbleObject> put(scope : CoroutineScope,key: String, value: StoreAbleObject, callback: SaveCallback<T>)
fun <T : StoreAbleObject> put(
scope: CoroutineScope,
key: String,
value: StoreAbleObject,
callback: SaveCallback<T>
)

@Throws(RuntimeException::class)
fun <T : StoreAbleObject> get(key: String, objectType: Class<T>): T

@Deprecated(
"Use new method with Coroutine Support",
replaceWith = ReplaceWith("StoreX.get(scope: CoroutineScope, key: String, objectType: Class<T>, callback: GetCallback<T>)")
)
fun <T : StoreAbleObject> get(key: String, objectType: Class<T>, callback: GetCallback<T>)

fun <T : StoreAbleObject> get(scope : CoroutineScope,key: String, objectType: Class<T>, callback: GetCallback<T>)
fun <T : StoreAbleObject> get(
scope: CoroutineScope,
key: String,
objectType: Class<T>,
callback: GetCallback<T>
)

@Throws(RuntimeException::class)
fun addSubscriber(subscriber: Subscriber)
Expand Down
9 changes: 8 additions & 1 deletion StoreX/src/main/java/com/rommansabbir/storex/StoreXConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,15 @@ package com.rommansabbir.storex
/**
* Represent the configuration for [StoreX].
* uId must be an unique identifier.
* [:: Having Encryption Enabled is a good practice for caching ::]
*
* @param uId, Unique identifier for the config
* @param prefName, SharedPref name for the config
* @param writeOrGetAsFileUsingCacheDirectory, If you want to write/get as file to/from Cache Directory.
* If you enabled [writeOrGetAsFileUsingCacheDirectory], [prefName] or [SharedPref] will be ignored.
*/
data class StoreXConfig(val uId : String, val prefName : String)
data class StoreXConfig(
val uId: String,
val prefName: String,
val writeOrGetAsFileUsingCacheDirectory: Boolean = false
)
25 changes: 18 additions & 7 deletions StoreX/src/main/java/com/rommansabbir/storex/StoreXCore.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ object StoreXCore {
instanceStates.clear()
}

@Deprecated("Use new method StoreXCore.instance(config: StoreXConfig) to get specific instance",
ReplaceWith("StoreXCore.instance(config: StoreXConfig)"))
@Deprecated(
"Use new method StoreXCore.instance(config: StoreXConfig) to get specific instance",
ReplaceWith("StoreXCore.instance(config: StoreXConfig)")
)
@Throws(RuntimeException::class)
fun instance(): StoreX {
if (isInitialized) {
Expand Down Expand Up @@ -51,15 +53,24 @@ object StoreXCore {
if (instanceStates[it.uId] != null) {
throw DuplicateStoreXConfigException()
}
instanceStates[it.uId] = StoreXState(StoreXInstance(application, it.prefName, gson), it)
instanceStates[it.uId] = StoreXState(
StoreXInstance(
application,
it.prefName,
gson,
it.writeOrGetAsFileUsingCacheDirectory
), it
)
}
this.isInitialized = true
}

@Deprecated("Use new method StoreXCore.init(application: Application, configs: MutableList<StoreXConfig>) initialize properly",
ReplaceWith("StoreXCore.init(application: Application, configs: MutableList<StoreXConfig>)"))
fun init(application: Application, prefName: String): StoreXCore {
this.instance = StoreXInstance(application, prefName, Gson())
@Deprecated(
"Use new method StoreXCore.init(application: Application, configs: MutableList<StoreXConfig>) initialize properly",
ReplaceWith("StoreXCore.init(application: Application, configs: MutableList<StoreXConfig>)")
)
fun init(application: Application, prefName: String, writeOrGetAsFileUsingCacheDirectory : Boolean = false): StoreXCore {
this.instance = StoreXInstance(application, prefName, Gson(), writeOrGetAsFileUsingCacheDirectory)
this.instance!!.registerListener(this.instance!!.listener)
this.isInitialized = true
return this
Expand Down
Loading

0 comments on commit 6c6b245

Please sign in to comment.