A local persistence framework backed by SharedPreferences with simple migration and Flow support.
- Generates implementation code for your preference files.
- Generates a manager class that allows you to create the implementations.
- Migration support
Note: ksp
is needed to process annotations
In your project's build.gradle.kts
, under buildscript, add the ksp
plugin to the classpath
buildscript {
dependencies {
classpath(com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:<ksp-version>)
}
}
In your app's build.gradle.kts
add the ksp
plugin and the ArcticTern
dependencies. Also, manually add the kotlin generated path into the source sets.
plugins {
...
id("com.google.devtools.ksp")
}
kotlin {
sourceSets.debug {
kotlin.srcDir("build/generated/ksp/debug/kotlin")
}
sourceSets.release {
kotlin.srcDir("build/generated/ksp/release/kotlin")
}
}
dependencies {
implementation("io.github.tompee26:arctic-tern-nest:$latest_version")
implementation("io.github.tompee26:arctic-tern-annotation:$latest_version")
ksp("io.github.tompee26:arctic-tern-compiler:$latest_version")
}
Annotate your application class with @ArcticTernApp
. This is so a manager class can be created in the same package. The manager will be responsible for providing
instances of the autogenerated preference classes. The manager instance will be a singleton but the instances of the autogenerated classes are not, so it is your responsibility to scope them manually or using a DI service.
@ArcticTernApp
class MyApplication : Application()
Create an abstract class
that will be used to store your preference and annotate it with @ArcticTern
. The name
is optional and if not provided will default to ArcticTern<class-name>
, the preferenceFile
is the filename of the backing SharedPreferences
file and the version
of the file.
@ArcticTern(name = "ActualPreference", filename = "pref_sample", version = 1)
abstract class MyAbstractPreference
At this point, a class will be generated depending on the name
property. And the manager will generate 2 factory methods, 1 using your abstract class name and 1 using the actual class name, for you to use interchangeably, depending on your preference.
class ArcticTernFactory {
....
fun createMyAbstractPreference() : ActualPreference = ....
fun createActualPreference() : ActualPreference = ....
}
Properties are defined inside the @ArcticTern
file. They will be used as getters and setters for your data. Below are the rules regarding property definition
- Properties must be
open
- this is so the implementation class can override it - Properties must be declared
var
(or mutable) - for you to be able to get/set values - Properties must be assigned with a default value - to calculate default values when not yet set
- Property types must be a supported type - supported types are declared below
@ArcticTern(name = "ActualPreference", filename = "pref_sample", version = 1)
abstract class MyAbstractPreference {
@ArcticTern.Property
open var counter : Long = 0L
}
Property can be configured further. Supply key
if you want to use a different SharedPreferences
key, withFlow
to true, if you want to generate a function that exposes a Flow
for you to observe state changes, and withDelete
to true to generate a function that allows you to delete the property's value.
Note: All custom generated functions are available only in the implementation class.
Similar to Property
but this allows for custom object persistence. A Serializer
implementation is required in the annotation.
data class IntWrapper(val intData: Int) {
class Serializer : Serializer<IntWrapper> {
override fun serialize(input: IntWrapper): String {
return input.intData.toString()
}
override fun deserialize(input: String): IntWrapper {
return IntWrapper(Integer.parseInt(input))
}
}
@ArcticTern.ObjectProperty(IntWrapper.Serializer::class)
open var counter : IntWrapper = IntWrapper(0)
Similar to ObjectProperty
but this allows for nullable inputs and outputs. A NullableSerializer
implementation is required in the annotation.
data class StringWrapper(val value: String) {
class Serializer : NullableSerializer<StringWrapper?> {
override fun serialize(input: StringWrapper?): String? {
return input?.value
}
override fun deserialize(input: String?): StringWrapper? {
return input?.let(::StringWrapper)
}
}
@ArcticTern.NullableObjectProperty(StringWrapper.Serializer::class)
open var counter : IntWrapper = IntWrapper(0)
Migration can be achieved by implementing the Migration
interface and annotating it with @Migration
. Migrations are incremental so you only need to specify the target version.
@ArcticTern.Migration(version = 2)
class ResetMigration : Migration {
override fun onMigrate(version: Int, sharedPreferences: SharedPreferences) {
sharedPreferences.edit().putBoolean("isSuccessful", false).commit()
}
}
Note that onMigrate
gives you the instance of the SharedPreferences
so use this with caution.
Type | Nullable |
---|---|
Int | No |
Boolean | No |
Float | No |
Long | No |
String | Yes |
Set<String> | Yes |
Set<String?> | Yes |
MIT License
Copyright (c) 2019 tompee
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.