From 89c235a4dcbc19fe69df7b9e6181357635dbbf28 Mon Sep 17 00:00:00 2001 From: Matheus Barbieri Corregiari Date: Mon, 10 Jun 2024 11:00:05 -0300 Subject: [PATCH] Create Util and Storage modules --- settings.gradle.kts | 2 + toolkit/storage/.gitignore | 1 + toolkit/storage/README.md | 2 + toolkit/storage/build.gradle.kts | 19 +++ toolkit/storage/consumer-proguard-rules.pro | 21 ++++ toolkit/storage/src/main/AndroidManifest.xml | 17 +++ .../arch/toolkit/storage/KeyValueStorage.kt | 50 ++++++++ .../arch/toolkit/storage/StorageCreator.kt | 118 ++++++++++++++++++ .../toolkit/storage/StorageInitializer.kt | 9 ++ .../com/arch/toolkit/storage/StorageType.kt | 23 ++++ .../arch/toolkit/storage/delegate/_base.kt | 37 ++++++ .../storage/delegate/_primitiveNonOptional.kt | 70 +++++++++++ .../storage/delegate/_primitiveOptional.kt | 111 ++++++++++++++++ .../impl/EncryptedSharedPrefStorage.kt | 100 +++++++++++++++ .../toolkit/storage/impl/MemoryStorage.kt | 88 +++++++++++++ .../toolkit/storage/impl/SharedPrefStorage.kt | 115 +++++++++++++++++ .../toolkit/storage/util/ThresholdData.kt | 107 ++++++++++++++++ .../arch/toolkit/storage/util/_sharedPref.kt | 68 ++++++++++ toolkit/util/.gitignore | 1 + toolkit/util/README.md | 2 + toolkit/util/build.gradle.kts | 11 ++ toolkit/util/consumer-proguard-rules.pro | 21 ++++ toolkit/util/src/main/AndroidManifest.xml | 17 +++ .../com/arch/toolkit/util/ContextProvider.kt | 76 +++++++++++ .../toolkit/util/ToolkitUtilInitialization.kt | 9 ++ 25 files changed, 1095 insertions(+) create mode 100644 toolkit/storage/.gitignore create mode 100644 toolkit/storage/README.md create mode 100644 toolkit/storage/build.gradle.kts create mode 100644 toolkit/storage/consumer-proguard-rules.pro create mode 100644 toolkit/storage/src/main/AndroidManifest.xml create mode 100644 toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/KeyValueStorage.kt create mode 100644 toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/StorageCreator.kt create mode 100644 toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/StorageInitializer.kt create mode 100644 toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/StorageType.kt create mode 100644 toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/delegate/_base.kt create mode 100644 toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/delegate/_primitiveNonOptional.kt create mode 100644 toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/delegate/_primitiveOptional.kt create mode 100644 toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/impl/EncryptedSharedPrefStorage.kt create mode 100644 toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/impl/MemoryStorage.kt create mode 100644 toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/impl/SharedPrefStorage.kt create mode 100644 toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/util/ThresholdData.kt create mode 100644 toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/util/_sharedPref.kt create mode 100644 toolkit/util/.gitignore create mode 100644 toolkit/util/README.md create mode 100644 toolkit/util/build.gradle.kts create mode 100644 toolkit/util/consumer-proguard-rules.pro create mode 100644 toolkit/util/src/main/AndroidManifest.xml create mode 100644 toolkit/util/src/main/kotlin/br/com/arch/toolkit/util/ContextProvider.kt create mode 100644 toolkit/util/src/main/kotlin/br/com/arch/toolkit/util/ToolkitUtilInitialization.kt diff --git a/settings.gradle.kts b/settings.gradle.kts index 7576d670..696661c5 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -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 diff --git a/toolkit/storage/.gitignore b/toolkit/storage/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/toolkit/storage/.gitignore @@ -0,0 +1 @@ +/build diff --git a/toolkit/storage/README.md b/toolkit/storage/README.md new file mode 100644 index 00000000..91e42676 --- /dev/null +++ b/toolkit/storage/README.md @@ -0,0 +1,2 @@ +# Storage +> Everything here is under construction, be-aware \ No newline at end of file diff --git a/toolkit/storage/build.gradle.kts b/toolkit/storage/build.gradle.kts new file mode 100644 index 00000000..9475e17b --- /dev/null +++ b/toolkit/storage/build.gradle.kts @@ -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) +} diff --git a/toolkit/storage/consumer-proguard-rules.pro b/toolkit/storage/consumer-proguard-rules.pro new file mode 100644 index 00000000..2f9dc5a4 --- /dev/null +++ b/toolkit/storage/consumer-proguard-rules.pro @@ -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 diff --git a/toolkit/storage/src/main/AndroidManifest.xml b/toolkit/storage/src/main/AndroidManifest.xml new file mode 100644 index 00000000..8b143afd --- /dev/null +++ b/toolkit/storage/src/main/AndroidManifest.xml @@ -0,0 +1,17 @@ + + + + + + + + + + \ No newline at end of file diff --git a/toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/KeyValueStorage.kt b/toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/KeyValueStorage.kt new file mode 100644 index 00000000..261718e7 --- /dev/null +++ b/toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/KeyValueStorage.kt @@ -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: + *
+ * ## 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. + *
+ * ## 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. + *
+ * ## 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. + *
+ * 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 get(key: String): T? + + fun get(key: String, default: T): T + + fun 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 + +} diff --git a/toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/StorageCreator.kt b/toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/StorageCreator.kt new file mode 100644 index 00000000..44c64803 --- /dev/null +++ b/toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/StorageCreator.kt @@ -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 + } +} diff --git a/toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/StorageInitializer.kt b/toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/StorageInitializer.kt new file mode 100644 index 00000000..66be3492 --- /dev/null +++ b/toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/StorageInitializer.kt @@ -0,0 +1,9 @@ +package br.com.arch.toolkit.storage + +import android.content.Context +import androidx.startup.Initializer + +internal class StorageInitializer : Initializer { + override fun create(context: Context) = StorageCreator.init(context) + override fun dependencies() = mutableListOf>>() +} diff --git a/toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/StorageType.kt b/toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/StorageType.kt new file mode 100644 index 00000000..49583872 --- /dev/null +++ b/toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/StorageType.kt @@ -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; +} diff --git a/toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/delegate/_base.kt b/toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/delegate/_base.kt new file mode 100644 index 00000000..c747fbf4 --- /dev/null +++ b/toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/delegate/_base.kt @@ -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 internal constructor( + private val name: () -> String, + private val default: (() -> T)?, + private val storage: () -> KeyValueStorage, + threshold: Duration, +) { + + protected val lastAccess = ThresholdData(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) + } +} diff --git a/toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/delegate/_primitiveNonOptional.kt b/toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/delegate/_primitiveNonOptional.kt new file mode 100644 index 00000000..02427c00 --- /dev/null +++ b/toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/delegate/_primitiveNonOptional.kt @@ -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 keyValueStorage( + name: String, + default: T, + storage: () -> KeyValueStorage = StorageCreator.defaultStorage, + duration: Duration = StorageCreator.defaultThreshold +) = keyValueStorage( + name = { name }, + default = { default }, + storage = storage, + duration = duration +) + +fun keyValueStorage( + name: String, + default: () -> T, + storage: () -> KeyValueStorage = StorageCreator.defaultStorage, + duration: Duration = StorageCreator.defaultThreshold +) = keyValueStorage( + name = { name }, + default = default, + storage = storage, + duration = duration +) + +fun keyValueStorage( + name: () -> String, + default: () -> T, + storage: () -> KeyValueStorage = StorageCreator.defaultStorage, + duration: Duration = StorageCreator.defaultThreshold +) = PrimitiveStorageDelegate( + name = name, + default = default, + threshold = duration, + storage = storage +) + +class PrimitiveStorageDelegate internal constructor( + name: () -> String, + default: () -> T, + storage: () -> KeyValueStorage, + threshold: Duration, +) : BaseStorageDelegate( + name = name, + default = default, + storage = storage, + threshold = threshold +), ReadWriteProperty { + + 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 + } +} diff --git a/toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/delegate/_primitiveOptional.kt b/toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/delegate/_primitiveOptional.kt new file mode 100644 index 00000000..2056ff16 --- /dev/null +++ b/toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/delegate/_primitiveOptional.kt @@ -0,0 +1,111 @@ +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 + +/** + * This delegate simplifies the process of persisting optional primitive values in a KeyValueStorage with an optional caching mechanism to improve performance. It abstracts the storage and retrieval logic, allowing developers to focus on using the property directly. + * ## Example Usage + * ```kotlin + * var myOptionalInt by keyValueStorage("my_int_key") + * + * // Settinga value + * myOptionalInt = 42 + * + * // Retrieving the value + * val retrievedValue = myOptionalInt // retrievedValue will be 42 + * ``` + */ +fun keyValueStorage( + name: String, + storage: () -> KeyValueStorage = StorageCreator.defaultStorage, + threshold: Duration = StorageCreator.defaultThreshold +) = keyValueStorage( + name = { name }, + storage = storage, + threshold = threshold +) + +/** + * This delegate simplifies the process of persisting optional primitive values in a KeyValueStorage with an optional caching mechanism to improve performance. It abstracts the storage and retrieval logic, allowing developers to focus on using the property directly. + * ## Example Usage + * ```kotlin + * var myOptionalInt by keyValueStorage(name = { "my_int_key" }) + * + * // Settinga value + * myOptionalInt = 42 + * + * // Retrieving the value + * val retrievedValue = myOptionalInt // retrievedValue will be 42 + * ``` + */ +fun keyValueStorage( + name: () -> String, + storage: () -> KeyValueStorage = StorageCreator.defaultStorage, + threshold: Duration = StorageCreator.defaultThreshold +) = OptionalPrimitiveStorageDelegate( + name = name, + storage = storage, + threshold = threshold +) + +/** + * The provided code defines a Kotlin property delegate named OptionalPrimitiveStorageDelegate that facilitates storing and retrieving optional primitive values using a KeyValueStorage. + */ +class OptionalPrimitiveStorageDelegate internal constructor( + name: () -> String, + storage: () -> KeyValueStorage, + threshold: Duration, +) : BaseStorageDelegate( + name = name, + default = null, + storage = storage, + threshold = threshold +), ReadWriteProperty { + + /** + * - Retrieves the value associated with the given property. + * - If either the name or storage is unavailable, it returns null. + * - It first checks the in-memory cache (lastAccess) based on the threshold. If found, it returns the cached value. + * - Otherwise, it fetches the value from the KeyValueStorage, caches it, and returns it. + */ + override operator fun getValue(thisRef: Any?, property: KProperty<*>): T? { + val name = name() ?: return null + val storage = storage() ?: return null + + return lastAccess.get(storage.name, name)?.also { + log("Get key value storage from threshold: $name -> $it") + } ?: storage.get(name)?.also { + log("Get key value storage: $name -> $it") + lastAccess.set(storage.name, name, it) + } + } + + /** + * - Sets the value for the given property. + * - If either the name or storage is unavailable, it clears the cache and returns. + * - If the new value is null, it removes the entry from the KeyValueStorage and clears the cache. + * - Otherwise, it stores the value in the KeyValueStorage, updates the cache, and logs the operation. + */ + override operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) { + val name = name() ?: run { lastAccess.clear();return } + val storage = storage() ?: run { lastAccess.clear();return } + + when { + value == null -> { + storage.remove(name) + lastAccess.clear() + log("Removed key value storage: $name") + } + + else -> { + storage.set(name, value) + lastAccess.set(storage.name, name, value) + log("Set key value storage: $name -> $value") + } + } + } +} diff --git a/toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/impl/EncryptedSharedPrefStorage.kt b/toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/impl/EncryptedSharedPrefStorage.kt new file mode 100644 index 00000000..298dda55 --- /dev/null +++ b/toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/impl/EncryptedSharedPrefStorage.kt @@ -0,0 +1,100 @@ +package br.com.arch.toolkit.storage.impl + +import android.content.Context +import android.content.SharedPreferences +import androidx.security.crypto.EncryptedSharedPreferences +import androidx.security.crypto.MasterKeys +import br.com.arch.toolkit.storage.KeyValueStorage +import br.com.arch.toolkit.storage.StorageType +import br.com.arch.toolkit.storage.util.edit +import br.com.arch.toolkit.storage.util.set +import timber.log.Timber + +/** + * Encrypted Shared Preferences Storage + * > The provided code defines a Kotlin class named EncryptedSharedPrefStorage that implements a key-value storage mechanism using Android's EncryptedSharedPreferences. + * + * ## Purpose + * The primary purpose of this class is to provide a secure way to store key-value data on an Android device. It leverages the EncryptedSharedPreferences library to encrypt the data before it is persisted to disk, protecting sensitive information from unauthorized access. + * + * ## In Summary + * The EncryptedSharedPrefStorage class provides a robust and secure solution for storing key-value data in an Android application. It utilizes encryption to protect sensitive information and offers a convenient API for managing the stored data. + * + * ## Synchronization + * Notice the use of synchronized(lock) blocks in several methods. This ensures thread safety, preventing concurrent access and potential data corruption when multiple threads interact with the shared preferences. + * + * ## Storage Operations + * > The class provides several methods for interacting with the encrypted storage: + * - get(key: String): Retrieves the value associated with the given key, if it exists. + * - get(key: String, default: T): Retrieves the value associated with the given key, returning the provided default value if the key is not found. + * - set(key: String, value: T?): Stores the given value under the specified key. Handles null values and empty keys appropriately. + * - 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. + * - clear(): Clears all key-value pairs from the storage. + * - contains(key: String): Checks if the storage contains a value associated with the given key. + * - size(): Returns the number of key-value pairs in the storage. + * - keys(): Returns a list of all keys present in the storage. + */ +// TODO Make it work also with complex data (transforming in JSON to save it into the disk) +class EncryptedSharedPrefStorage internal constructor(context: Context, override val name: String) : + KeyValueStorage { + + override val type: StorageType = StorageType.ENCRYPTED_SHARED_PREF + private val lock = Object() + + private val sharedPref: SharedPreferences + + init { + val keyScheme = EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV + val valueScheme = EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM + val masterKey = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC) + sharedPref = EncryptedSharedPreferences.create( + /* fileName = */ name, + /* masterKeyAlias = */ masterKey, + /* context = */ context, + /* prefKeyEncryptionScheme = */ keyScheme, + /* prefValueEncryptionScheme = */ valueScheme + ) + sharedPref.registerOnSharedPreferenceChangeListener { _, key -> + Timber.tag("[Storage $name]").i("$key changed") + } + } + + @Suppress("UNCHECKED_CAST") + override fun get(key: String): T? = synchronized(lock) { + when { + contains(key) -> sharedPref.all[key] as? T? + else -> null + } + } + + override fun get(key: String, default: T): T = get(key) ?: default + + override fun set(key: String, value: T?) = when { + /* Validate Value */ + value == null -> remove(key) + value == null.toString() -> remove(key) + (value as? String).isNullOrBlank() -> remove(key) + + /* Validate Key */ + key.isBlank() -> remove(key) + key == null.toString() -> remove(key) + + /* If reaches here, the Key and the Value are good to go! */ + else -> synchronized(lock) { sharedPref[key] = value } + } + + override fun remove(key: String) = synchronized(lock) { + if (contains(key)) sharedPref.edit { remove(key) } + } + + override fun remove(regex: Regex) = keys().filter { it.matches(regex) }.forEach { remove(it) } + + override fun clear() = synchronized(lock) { sharedPref.edit { clear() } } + + override fun contains(key: String): Boolean = sharedPref.contains(key) + + override fun size(): Int = sharedPref.all.count() + + override fun keys(): List = sharedPref.all.keys.toList() +} diff --git a/toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/impl/MemoryStorage.kt b/toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/impl/MemoryStorage.kt new file mode 100644 index 00000000..869bee8b --- /dev/null +++ b/toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/impl/MemoryStorage.kt @@ -0,0 +1,88 @@ +package br.com.arch.toolkit.storage.impl + +import br.com.arch.toolkit.storage.KeyValueStorage +import br.com.arch.toolkit.storage.StorageType +import kotlin.collections.set + +/** + * This code defines a class named MemoryStorage that implements the KeyValueStorage interface. Let's break down its functionality step by step. + * Purpose + * The MemoryStorage class provides an in-memory key-value store. It allows you to store and retrieve data using string keys, all within the application's memory. This is useful for temporary data that doesn't need to persist beyond the app's current session. + * Implementation Details + * Constructor: The constructor takes a name parameter and initializes the type property to StorageType.MEMORY. It also creates a private lock object for thread safety. + * Storage Mechanism: A private mutable map (map) is used to store the key-value pairs. + *
+ * ## Methods + * - get() + * - The first get() method attempts to retrieve a value associated with the given key. It uses a type parameter T to allow for generic value retrieval. If the key exists, it returns the corresponding value cast to type T; otherwise, it returns null. + * - The second get() method provides a default value to return if the key is not found. + * - set() Method: + * - This method stores a value associated with a given key. + * - It performs several validations before storing the value: + * - If the value is null, empty, or blank, it removes the key (if it exists). + * - If the key is blank or null, it also removes the key. + * - If both key and value are valid, it stores the key-value pair in the map. + * - remove() Methods: + * - The first remove() method deletes the key-value pair associated with the given key. + * - The second remove() method deletes all key-value pairs where the key matches the provided regular expression (regex). + * - clear() Method: This method clears all key-value pairs from the storage. + * - contains() Method: This method checks if a key-value pair exists for the given key. + * - size() Method: This method returns the number of key-value pairs in the storage. + * - keys() Method: This method returns a list of all keys present in the storage. + *
+ * ## Thread Safety + * The synchronized blocks around critical sections (like get(), set(), remove(), and clear()) ensure that multiple threads can safely access the MemoryStorage instance without data corruption. + *
+ * ## Example Usage + * ```kotlin + * val storage = MemoryStorage("my_storage") + * storage.set("user_name", "John Doe") + * val userName = storage.get("user_name") + * ``` + *
+ */ +class MemoryStorage internal constructor(override val name: String) : KeyValueStorage { + + override val type: StorageType = StorageType.MEMORY + private val lock = Object() + + private val map: MutableMap = mutableMapOf() + + @Suppress("UNCHECKED_CAST") + override fun get(key: String): T? = synchronized(lock) { + when { + contains(key) -> map[key] as? T? + else -> null + } + } + + override fun get(key: String, default: T): T = get(key) ?: default + + override fun set(key: String, value: T?) = when { + /* Validate Value */ + value == null -> remove(key) + value == null.toString() -> remove(key) + (value as? String).isNullOrBlank() -> remove(key) + + /* Validate Key */ + key.isBlank() -> remove(key) + key == null.toString() -> remove(key) + + /* If reaches here, the Key and the Value are good to go! */ + else -> synchronized(lock) { map[key] = value } + } + + override fun remove(key: String) = synchronized(lock) { + if (contains(key)) map.remove(key) + } + + override fun remove(regex: Regex) = keys().filter { it.matches(regex) }.forEach { remove(it) } + + override fun clear() = synchronized(lock) { map.clear() } + + override fun contains(key: String): Boolean = map.containsKey(key) + + override fun size(): Int = map.count() + + override fun keys(): List = map.keys.toList() +} diff --git a/toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/impl/SharedPrefStorage.kt b/toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/impl/SharedPrefStorage.kt new file mode 100644 index 00000000..d0f5607e --- /dev/null +++ b/toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/impl/SharedPrefStorage.kt @@ -0,0 +1,115 @@ +package br.com.arch.toolkit.storage.impl + +import android.content.Context +import android.content.SharedPreferences +import br.com.arch.toolkit.storage.KeyValueStorage +import br.com.arch.toolkit.storage.StorageType +import br.com.arch.toolkit.storage.util.edit +import br.com.arch.toolkit.storage.util.set +import timber.log.Timber + +/** + * This code defines a class called SharedPrefStorage that implements an interface called KeyValueStorage. This class is designed to store key-value pairs using Android's SharedPreferences system. Let's break down the code step by step: + *
+ * ## Constructor and Properties + * - internal constructor(context: Context, override val name: String) + * - The constructor takes a Context (providing access to Android system resources) and a name for the SharedPreferences file. + * - override val type: StorageType = StorageType.SHARED_PREF + * - Indicates that this storage uses SharedPreferences. + * - private val lock = Object() + * - A lock object used for synchronization to prevent concurrent access issues. + * - private val sharedPref: SharedPreferences + * - An instance of SharedPreferences obtained using the provided context and name. + * - The init block registers a listener to log changes to SharedPreferences. + *
+ * ## Methods + * - get(key: String) + * - Retrieves a value associated with the given key, if it exists. It handles type casting and returns null if the key is not found. + * - get(key: String, default: T) + * - Similar to get(key), but returns the provided default value if the key is not found. + * - set(key: String, value: T?) + * - Stores a key-value pair. It performs validation on both the key and value before storing them. If the value is null or empty, it removes the key. + * - remove(key: String) + * - Removes the key-value pair associated with the given key. + * - remove(regex: Regex) + * - Removes all key-value pairs where the key matches the provided regular expression. + * - clear() + * - Clears all key-value pairs from the SharedPreferences. + * - contains(key: String) + * - Checks if a key exists in the SharedPreferences. + * - size() + * - Returns the number of key-value pairs. + * - keys() + * - Returns a list of all keys in the SharedPreferences. + *
+ * ## Synchronization + * The synchronized(lock) blocks in several methods ensure that only one thread can access the SharedPreferences at a time, preventing potential race conditions and data inconsistencies. + *
+ * ## Example Usage + * ```kotlin + * val storage = SharedPrefStorage(context, "my_preferences") + * storage.set("user_name", "John Doe") + * val userName = storage.get("user_name") + * ``` + *
+ * ## Limitations of SharedPreferences + * While SharedPreferences is a convenient way to store simple key-value data, it has limitations: + * - It's not designed for large amounts of data. + * - It doesn't support complex data structures directly. + * - It can have performance issues if used excessively. + * > For more complex data or larger datasets, consider using other storage options like Room database or DataStore. + */ +// TODO Make it work also with complex data (transforming in JSON to save it into the disk) +class SharedPrefStorage internal constructor(context: Context, override val name: String) : + KeyValueStorage { + + override val type: StorageType = StorageType.SHARED_PREF + private val lock = Object() + + private val sharedPref: SharedPreferences = + context.getSharedPreferences(name, Context.MODE_PRIVATE) + + init { + sharedPref.registerOnSharedPreferenceChangeListener { _, key -> + Timber.tag("[Storage $name]").i("$key changed") + } + } + + @Suppress("UNCHECKED_CAST") + override fun get(key: String): T? = synchronized(lock) { + when { + contains(key) -> sharedPref.all[key] as? T? + else -> null + } + } + + override fun get(key: String, default: T): T = get(key) ?: default + + override fun set(key: String, value: T?) = when { + /* Validate Value */ + value == null -> remove(key) + value == null.toString() -> remove(key) + (value as? String).isNullOrBlank() -> remove(key) + + /* Validate Key */ + key.isBlank() -> remove(key) + key == null.toString() -> remove(key) + + /* If reaches here, the Key and the Value are good to go! */ + else -> synchronized(lock) { sharedPref[key] = value } + } + + override fun remove(key: String) = synchronized(lock) { + if (contains(key)) sharedPref.edit { remove(key) } + } + + override fun remove(regex: Regex) = keys().filter { it.matches(regex) }.forEach { remove(it) } + + override fun clear() = synchronized(lock) { sharedPref.edit { clear() } } + + override fun contains(key: String): Boolean = sharedPref.contains(key) + + override fun size(): Int = sharedPref.all.count() + + override fun keys(): List = sharedPref.all.keys.toList() +} diff --git a/toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/util/ThresholdData.kt b/toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/util/ThresholdData.kt new file mode 100644 index 00000000..da0041d5 --- /dev/null +++ b/toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/util/ThresholdData.kt @@ -0,0 +1,107 @@ +package br.com.arch.toolkit.storage.util + +import kotlin.time.Duration + +/** + * The provided code defines a generic class named ThresholdData which appears to be designed for storing data with an expiration mechanism. Let's break down its functionality step by step. + *
+ * ## Class Definition: + * Generic Type: The class takes a generic type parameter T, allowing it to store data of any type. + * Constructor: It has a constructor that accepts a Duration object, specifying the expiration time for the stored data. + *
+ * ## Private Properties: + * - storageName (String, nullable) + * - Likely used to identify a storage location or context associated with the data. + * - data (T, nullable) + * - Holds the actual data of the generic type T. + * - name (String, nullable) + * - Possibly a specific identifier or key for the data within the storage. + * - timestamp (Long, nullable) + * - Stores the time (in milliseconds) when the data was last set. + *
+ * ## Methods + * - isExpired() Method: + * - Checks if the stored data has expired based on the following conditions: + * - All required properties (storageName, data, name, timestamp) must be non-null. + * - The time elapsed since the last timestamp must exceed the duration specified in the constructor. + * - get() Method: + * - Takes storageName and name as parameters to retrieve the stored data. + * - Clears the stored data and returns null if any of the following conditions are met: + * - The data is expired (isExpired() returns true). + * - The provided storageName doesn't match the stored one. + * - The provided name doesn't match the stored one. + * Otherwise, it returns the stored data. If data is null, it calls clear() and returns null. + * - set() Method: + * - Takes storageName, name, and data as parameters to store new data. + * - Clears any existing data first. + * - If the provided data is not null, it updates the properties with the new values and sets the timestamp to the current time. + * - clear() Method: + * - Resets all properties to null, effectively clearing the stored data and its associated information. + * - ifNull() Extension Function: + * - A private extension function on nullable types. + * - If the receiver object is null, it executes the provided block of code. + * - Returns the receiver object itself (for chaining). + *
+ * ## Purpose and Potential Use Cases: + * This ThresholdData class seems useful for scenarios where you need to temporarily store data with an expiration time, such as: + * Caching data fetched from a network or database. + * Storing user input or preferences with a timeout. + * Implementing rate limiting or throttling mechanisms. + *
+ * ## Example Usage: + * ```kotlin + * val dataStore = ThresholdData(Duration.ofMinutes(5)) + * + * // Store data + * dataStore.set("user_preferences", "theme", "dark") + * + * // Retrieve data + * val theme = dataStore.get("user_preferences", "theme") + * + * // Check if data is expired + * val isExpired = dataStore.isExpired() + * ``` + */ +class ThresholdData(private val duration: Duration) { + + private var storageName: String? = null + private var data: T? = null + private var name: String? = null + private var timestamp: Long? = null + + fun isExpired(): Boolean { + this.storageName ?: return true + this.data ?: return true + this.name ?: return true + val lastTimestamp = this.timestamp ?: return true + val deltaTime = System.currentTimeMillis() - lastTimestamp + + return deltaTime > duration.inWholeMilliseconds + } + + fun get(storageName: String, name: String): T? = when { + isExpired() -> run { clear();null } + storageName != this.storageName -> run { clear(); null } + name != this.name -> run { clear(); null } + else -> this.data.ifNull { clear() } + } + + fun set(storageName: String, name: String, data: T?) { + clear() + if (data == null) return + + this.storageName = storageName + this.data = data + this.name = name + this.timestamp = System.currentTimeMillis() + } + + fun clear() { + this.storageName = null + this.data = null + this.name = null + this.timestamp = null + } + + private fun T?.ifNull(block: () -> Unit) = apply { if (this == null) block() } +} diff --git a/toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/util/_sharedPref.kt b/toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/util/_sharedPref.kt new file mode 100644 index 00000000..949677ea --- /dev/null +++ b/toolkit/storage/src/main/kotlin/br/com/arch/toolkit/storage/util/_sharedPref.kt @@ -0,0 +1,68 @@ +package br.com.arch.toolkit.storage.util + +import android.content.SharedPreferences + +/** + * This Kotlin extension function provides a more concise way to edit a SharedPreferences object. + * + * ## Purpose: + * The code defines an extension function named edit on the SharedPreferences class. This function simplifies the process of modifying values within shared preferences. + * + * ## How it Works: + * - edit(): It calls the edit() method on the SharedPreferences instance to obtain a SharedPreferences.Editor object, which is used to make changes to the preferences. + * - apply(func): It takes a lambda function (func) as an argument. This lambda is executed within the context of the SharedPreferences.Editor, allowing you to perform modifications using the editor's methods (like putInt, putString, etc.). + * - apply(): Finally, it calls apply() on the editor to asynchronously save the changes to the shared preferences. + * + * ## Usage Example: + * > In this example, the edit extension function makes the code cleaner by eliminating the need to explicitly call apply() after making changes. + * ```kotlin + * val sharedPreferences = getSharedPreferences("my_prefs", Context.MODE_PRIVATE) + * + * sharedPreferences.edit { + * putInt("user_age", 30) + * putString("user_name", "Alice") + * } + * ``` + * + * ## Benefits: + * - Improved Readability: The code becomes more concise and easier to read. + * - Reduced Boilerplate: It eliminates the need for repetitive apply() calls. + * - Type Safety: The lambda function ensures that you only use methods available on the SharedPreferences.Editor. + */ +fun SharedPreferences.edit(func: SharedPreferences.Editor.() -> Unit) { + edit().apply(func).apply() +} + +/** + * This code defines an extension function named set for the SharedPreferences class. It provides a convenient way to store different data types in SharedPreferences using the operator overloading feature of Kotlin. + * + * ## Type Handling + * > The code handles several data types: + * - String? and String: Stores string values using putString. + * - Set<*>? and Set<*>: Stores sets of strings using putStringSet. It maps the elements of the set to strings and handles nullable sets. + * - Int, Boolean, Float, Long: Stores primitive data types using their respective put methods. + * + * ## Unsupported Types + * > If the value is of a type not handled by the when expression, it throws an UnsupportedOperationException with a message indicating that the type is not yet implemented. + * + * ## Example Usage: + * ```kotlin + * val sharedPrefs = getSharedPreferences("my_prefs", Context.MODE_PRIVATE) + * sharedPrefs.set("username", "JohnDoe") // Stores a string + * sharedPrefs.set("age", 30) // Stores an integer + * ``` + * > This extension function simplifies the process of storing various data types in SharedPreferences by providing a concise and type-safe way to do so. + */ +@Throws(UnsupportedOperationException::class, IllegalArgumentException::class) +operator fun SharedPreferences.set(key: String, value: T) = when (value) { + is String? -> edit { putString(key, value) } + is String -> edit { putString(key, value) } + is Set<*>? -> edit { putStringSet(key, value?.mapNotNull { it?.toString() }?.toSet()) } + is Set<*> -> edit { putStringSet(key, value.mapNotNull { it?.toString() }.toSet()) } + is Int -> edit { putInt(key, value) } + is Boolean -> edit { putBoolean(key, value) } + is Float -> edit { putFloat(key, value) } + is Long -> edit { putLong(key, value) } + + else -> throw UnsupportedOperationException("Not yet implemented: $value") +} diff --git a/toolkit/util/.gitignore b/toolkit/util/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/toolkit/util/.gitignore @@ -0,0 +1 @@ +/build diff --git a/toolkit/util/README.md b/toolkit/util/README.md new file mode 100644 index 00000000..87383f4c --- /dev/null +++ b/toolkit/util/README.md @@ -0,0 +1,2 @@ +# Util +> Everything here is under construction, be-aware \ No newline at end of file diff --git a/toolkit/util/build.gradle.kts b/toolkit/util/build.gradle.kts new file mode 100644 index 00000000..c4c80b09 --- /dev/null +++ b/toolkit/util/build.gradle.kts @@ -0,0 +1,11 @@ +plugins { + id("toolkit-android-library") + id("toolkit-publish") +} + +android.namespace = "br.com.arch.toolkit.util" + +dependencies { + // Androidx + implementation(libraries.androidx.startup) +} diff --git a/toolkit/util/consumer-proguard-rules.pro b/toolkit/util/consumer-proguard-rules.pro new file mode 100644 index 00000000..2f9dc5a4 --- /dev/null +++ b/toolkit/util/consumer-proguard-rules.pro @@ -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 diff --git a/toolkit/util/src/main/AndroidManifest.xml b/toolkit/util/src/main/AndroidManifest.xml new file mode 100644 index 00000000..7afba3a0 --- /dev/null +++ b/toolkit/util/src/main/AndroidManifest.xml @@ -0,0 +1,17 @@ + + + + + + + + + + \ No newline at end of file diff --git a/toolkit/util/src/main/kotlin/br/com/arch/toolkit/util/ContextProvider.kt b/toolkit/util/src/main/kotlin/br/com/arch/toolkit/util/ContextProvider.kt new file mode 100644 index 00000000..a403b781 --- /dev/null +++ b/toolkit/util/src/main/kotlin/br/com/arch/toolkit/util/ContextProvider.kt @@ -0,0 +1,76 @@ +package br.com.arch.toolkit.util + +import android.app.Activity +import android.app.Application +import android.app.Application.ActivityLifecycleCallbacks +import android.content.Context +import android.os.Bundle +import java.lang.ref.WeakReference + +/** + * This code defines an object called ContextProvider which is a common way in Kotlin to create a Singleton. This object aims to provide access to a Context throughout an Android application. Let's break down its functionality: + * 1. lastContext: + * - It's a nullable property of type WeakReference. + * - WeakReference is used to hold a reference to a Context (like an Activity or Application) without preventing the garbage collector from reclaiming it if it's no longer needed elsewhere. This helps avoid memory leaks. + * 2. current: + * - This is a computed property that returns the Context held by lastContext if it's still available, or null if the Context has been garbage collected. + * 3. register(application: Application): + * - This function is meant to be called early in your app's lifecycle, ideally within your Application class's onCreate() method. + * - It initializes lastContext with a WeakReference to the provided Application context. + * - It registers an ActivityLifecycleCallbacks listener to the Application. This listener updates lastContext whenever an Activity is created or resumed, ensuring you have access to the most current Activity context. + * ## Purpose: + * The ContextProvider object provides a convenient way to access a valid Context from anywhere in your application. This is often needed for tasks like: + * - Inflating layouts + * - Accessing resources (strings, drawables, etc.) + * - Starting services or other components + * ## Caveats: + * - Be mindful of potential memory leaks if you hold a strong reference to the Context returned by current for an extended period. + * - In some cases, using the Application context might be sufficient and safer than relying on an Activity context, especially if you need the context to persist beyond the lifecycle of a single Activity. + * ## Example Usage: + * ```kotlin + * // In your Application class's onCreate() + * ContextProvider.register(this) + * + * // Elsewhere in your app + * val context = ContextProvider.current + * if (context != null) { + * // Use the context, e.g., to inflate a layout + * } + * ``` + */ +object ContextProvider { + + private var lastContext: WeakReference? = null + private val callback = object : ActivityLifecycleCallbacks { + override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { + lastContext = WeakReference(activity) + } + + override fun onActivityResumed(activity: Activity) { + lastContext = WeakReference(activity) + } + + override fun onActivityStarted(activity: Activity) = Unit + override fun onActivityPaused(activity: Activity) = Unit + override fun onActivityStopped(activity: Activity) = Unit + override fun onActivitySaveInstanceState( + activity: Activity, + outState: Bundle + ) = Unit + + override fun onActivityDestroyed(activity: Activity) = Unit + } + + val current: Context? get() = lastContext?.get() + + fun init(context: Context) { + lastContext = WeakReference(context) + (context as? Application)?.let(::register) + ?: (context.applicationContext as? Application)?.let(::register) + } + + fun register(application: Application) { + if (current == null) lastContext = WeakReference(application) + application.registerActivityLifecycleCallbacks(callback) + } +} diff --git a/toolkit/util/src/main/kotlin/br/com/arch/toolkit/util/ToolkitUtilInitialization.kt b/toolkit/util/src/main/kotlin/br/com/arch/toolkit/util/ToolkitUtilInitialization.kt new file mode 100644 index 00000000..f1737b72 --- /dev/null +++ b/toolkit/util/src/main/kotlin/br/com/arch/toolkit/util/ToolkitUtilInitialization.kt @@ -0,0 +1,9 @@ +package br.com.arch.toolkit.util + +import android.content.Context +import androidx.startup.Initializer + +internal class ToolkitUtilInitialization : Initializer { + override fun create(context: Context) = ContextProvider.init(context) + override fun dependencies() = mutableListOf>>() +}