Skip to content

Commit

Permalink
Create Util and Storage modules
Browse files Browse the repository at this point in the history
  • Loading branch information
matheus-corregiari committed Jun 10, 2024
1 parent 7c01ebb commit 89c235a
Show file tree
Hide file tree
Showing 25 changed files with 1,095 additions and 0 deletions.
2 changes: 2 additions & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ include(":toolkit:event-observer")
include(":toolkit:foldable")
include(":toolkit:recycler-adapter")
include(":toolkit:splinter")
include(":toolkit:storage")
include(":toolkit:util")
include(":toolkit:statemachine")

// Samples
Expand Down
1 change: 1 addition & 0 deletions toolkit/storage/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
2 changes: 2 additions & 0 deletions toolkit/storage/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Storage
> Everything here is under construction, be-aware
19 changes: 19 additions & 0 deletions toolkit/storage/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
plugins {
id("toolkit-android-library")
id("toolkit-publish")
}

android.namespace = "br.com.arch.toolkit.storage"

dependencies {
// JetBrains
implementation(libraries.jetbrains.stdlib.jdk8)
implementation(libraries.jetbrains.coroutines.core)

// Androidx
implementation(libraries.androidx.security)
implementation(libraries.androidx.startup)

// Tools
implementation(libraries.square.timber)
}
21 changes: 21 additions & 0 deletions toolkit/storage/consumer-proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.kts.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
17 changes: 17 additions & 0 deletions toolkit/storage/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<application>
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="br.com.arch.toolkit.storage.StorageInitializer"
android:value="androidx.startup" />
</provider>
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package br.com.arch.toolkit.storage

/**
* This code defines an interface called KeyValueLocalStorage in Kotlin, which outlines a contract for storing and retrieving key-value pairs in a local storage system. Let's break down its components:
* <br>
* ## Properties
* - type: A property of type StorageType (not shown here) indicating the underlying storage mechanism (e.g., SharedPreferences, DataStore, etc.).
* - name: A property of a String representing the name or identifier of this storage instance.
* <br>
* ## Methods
* - get(key: String): Retrieves the value associated with the given key. Returns null if the key doesn't exist.
* - get(key: String, default: T): Retrieves the value associated with the given key. Returns the provided default value if the key doesn't exist.
* - set(key: String, value: T?): Stores the given value under the specified key. The value can be nullable.
* - remove(key: String): Removes the key-value pair associated with the given key.
* - remove(regex: Regex): Removes all key-value pairs whose keys match the provided regular expression regex.
* - clear(): Removes all key-value pairs from the storage.
* - contains(key: String): Returns true if the storage contains a value for the given key, and false otherwise.
* - size(): Returns the number of key-value pairs currently stored.
* - keys(): Returns a Set containing all the keys present in the storage.
* <br>
* ## Purpose
* This interface provides a standardized way to interact with different local storage solutions in your Android application. By implementing this interface, you can create concrete storage classes (e.g., SharedPreferencesStorage, DataStoreStorage) that handle the actual storage logic while adhering to a common API.
* <br>
* This abstraction allows you to easily switch between storage implementations or use multiple storage mechanisms within your app without changing the code that interacts with the storage.
*/
interface KeyValueStorage {

val type: StorageType

val name: String

fun <T> get(key: String): T?

fun <T> get(key: String, default: T): T

fun <T> set(key: String, value: T?)

fun remove(key: String)

fun remove(regex: Regex)

fun clear()

fun contains(key: String): Boolean

fun size(): Int

fun keys(): List<String>

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package br.com.arch.toolkit.storage

import android.content.Context
import br.com.arch.toolkit.storage.impl.EncryptedSharedPrefStorage
import br.com.arch.toolkit.storage.impl.MemoryStorage
import br.com.arch.toolkit.storage.impl.SharedPrefStorage
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds

/**
* The provided code defines an object named StorageCreator in Kotlin, which serves as a factory and manager for different types of storage mechanisms.
*
* ## Usage
* Before using the encryptedSharedPref or sharedPref properties, you must call the init() method and provide an Android Context. This initializes the underlying storage instances.
* The createKeyValueStorage() methods provide a convenient way to create different types of storage instances based on your needs.
*
* ## Example
* ```kotlin
* // Initialize the storage creator
* StorageCreator.init(context)
*
* // Access the encrypted shared preferences storage
* val encryptedStorage = StorageCreator.encryptedSharedPref
*
* // Create a memory storage instance
* val memoryStorage = StorageCreator.createKeyValueStorage("temp", StorageType.MEMORY)
* ```
*/
object StorageCreator {

/**
* A nullable property to hold an instance of EncryptedSharedPrefStorage. It's initialized to null and later assigned a value within the init() method.
*/
private var _encryptedSharedPref: EncryptedSharedPrefStorage? = null

/**
* Similar to _encryptedSharedPref, it stores an instance of SharedPrefStorage.
*/
private var _sharedPref: SharedPrefStorage? = null

/**
* A property representing a time duration, initialized to 300 milliseconds. It can be modified using the setDefaultThreshold() method.
*/
internal var defaultThreshold: Duration = 300.milliseconds
private set

/**
* A property representing a function that returns an instance of KeyValueStorage.
* It's initialized to a function that returns an instance of encryptedSharedPref.
*/
internal var defaultStorage: () -> KeyValueStorage = { encryptedSharedPref }
private set

/**
* An instance of MemoryStorage with the name "default", likely used for temporary in-memory data storage.
*/
val memory = MemoryStorage("default")

/**
* A computed property that provides access to the _encryptedSharedPref instance. It throws an exception if accessed before initialization.
*/
val encryptedSharedPref: EncryptedSharedPrefStorage
get() = requireNotNull(_encryptedSharedPref) {
"Not initialized, Be aware to call init() before use"
}

/**
* Similar to encryptedSharedPref, it provides access to the _sharedPref instance, ensuring it's initialized.
*/
val sharedPref: EncryptedSharedPrefStorage
get() = requireNotNull(_encryptedSharedPref) {
"Not initialized, Be aware to call init() before use"
}

/**
* This function initializes the _encryptedSharedPref and _sharedPref properties using the provided Android Context. It's crucial to call this method before accessing these storage instances.
*/
fun init(context: Context) {
_encryptedSharedPref = EncryptedSharedPrefStorage(context, "default")
_sharedPref = SharedPrefStorage(context, "default")
}

/**
* A factory method that creates and returns a storage instance based on the specified StorageType.
* It supports only MemoryStorage
*/
fun createKeyValueStorage(name: String, type: StorageType) =
when (type) {
StorageType.MEMORY -> MemoryStorage(name)
StorageType.ENCRYPTED_SHARED_PREF -> error("To create this type of storage, you must provide a context")
StorageType.SHARED_PREF -> error("To create this type of storage, you must provide a context")
}

/**
* A factory method that creates and returns a storage instance based on the specified StorageType.
* It supports MemoryStorage, EncryptedSharedPrefStorage, and SharedPrefStorage.
*/
fun createKeyValueStorage(context: Context, name: String, type: StorageType) =
when (type) {
StorageType.MEMORY -> MemoryStorage(name)
StorageType.ENCRYPTED_SHARED_PREF -> EncryptedSharedPrefStorage(context, name)
StorageType.SHARED_PREF -> SharedPrefStorage(context, name)
}

/**
* Allows modification of the defaultThreshold property.
*/
fun setDefaultThreshold(threshold: Duration) {
defaultThreshold = threshold
}

/**
* Allows modification of the defaultStorage property.
*/
fun setDefaultStorage(storage: () -> KeyValueStorage) {
defaultStorage = storage
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package br.com.arch.toolkit.storage

import android.content.Context
import androidx.startup.Initializer

internal class StorageInitializer : Initializer<Unit> {
override fun create(context: Context) = StorageCreator.init(context)
override fun dependencies() = mutableListOf<Class<out Initializer<*>>>()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package br.com.arch.toolkit.storage

/**
* This Kotlin code defines an enum class named StorageType.
* In this case, StorageType represents different ways to store data in an Android app
*/
enum class StorageType {

/**
* This likely refers to storing data in the device's RAM, which is temporary and will be lost when the app closes.
*/
MEMORY,

/**
* This likely refers to storing data in the device's RAM, which is temporary and will be lost when the app closes.
*/
SHARED_PREF,

/**
* This suggests a more secure way of using SharedPreferences, likely employing encryption to protect the stored data.
*/
ENCRYPTED_SHARED_PREF;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package br.com.arch.toolkit.storage.delegate

import br.com.arch.toolkit.storage.KeyValueStorage
import br.com.arch.toolkit.storage.util.ThresholdData
import timber.log.Timber
import kotlin.time.Duration


open class BaseStorageDelegate<T> internal constructor(
private val name: () -> String,
private val default: (() -> T)?,
private val storage: () -> KeyValueStorage,
threshold: Duration,
) {

protected val lastAccess = ThresholdData<T>(threshold)

protected fun name() = name.runCatching { invoke().takeIf { it.isNotBlank() } }
.onFailure { it.log("[Storage] Failed to get name for key value storage") }
.getOrNull()

protected fun storage() = storage.runCatching { invoke() }
.onFailure { it.log("[Storage] Failed to get storage for key value storage") }
.getOrNull()

protected fun default() = default.runCatching { requireNotNull(this).invoke() }
.onFailure { it.log("[Storage] Failed to get default value for ${name()}") }
.getOrThrow()

private fun Throwable.log(message: String) {
Timber.tag("Storage Delegate").e(this, message)
}

protected fun log(message: String) {
Timber.tag("Storage Delegate").i(message)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package br.com.arch.toolkit.storage.delegate

import br.com.arch.toolkit.storage.KeyValueStorage
import br.com.arch.toolkit.storage.StorageCreator
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
import kotlin.time.Duration

fun <T : Any> keyValueStorage(
name: String,
default: T,
storage: () -> KeyValueStorage = StorageCreator.defaultStorage,
duration: Duration = StorageCreator.defaultThreshold
) = keyValueStorage(
name = { name },
default = { default },
storage = storage,
duration = duration
)

fun <T : Any> keyValueStorage(
name: String,
default: () -> T,
storage: () -> KeyValueStorage = StorageCreator.defaultStorage,
duration: Duration = StorageCreator.defaultThreshold
) = keyValueStorage(
name = { name },
default = default,
storage = storage,
duration = duration
)

fun <T : Any> keyValueStorage(
name: () -> String,
default: () -> T,
storage: () -> KeyValueStorage = StorageCreator.defaultStorage,
duration: Duration = StorageCreator.defaultThreshold
) = PrimitiveStorageDelegate(
name = name,
default = default,
threshold = duration,
storage = storage
)

class PrimitiveStorageDelegate<T> internal constructor(
name: () -> String,
default: () -> T,
storage: () -> KeyValueStorage,
threshold: Duration,
) : BaseStorageDelegate<T>(
name = name,
default = default,
storage = storage,
threshold = threshold
), ReadWriteProperty<Any?, T> {

private var savedData: T? by keyValueStorage(
name = name,
storage = storage,
threshold = threshold
)

override operator fun getValue(thisRef: Any?, property: KProperty<*>): T = savedData ?: default().also {
log("Delivering default value for ${property.name}")
}

override operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
savedData = value
}
}
Loading

0 comments on commit 89c235a

Please sign in to comment.