‫DataStore   חלק מ-Android Jetpack.

כדאי לנסות את Kotlin Multiplatform
‫Kotlin Multiplatform מאפשרת לשתף את שכבת הנתונים עם פלטפורמות אחרות. איך מגדירים את DataStore ב-KMP ואיך עובדים איתו

‫Jetpack DataStore הוא פתרון לאחסון נתונים שמאפשר לכם לאחסן זוגות של מפתח/ערך או אובייקטים מוקלדים באמצעות מאגרי פרוטוקולים. ‫DataStore משתמש ב-Kotlin coroutines וב-Flow כדי לאחסן נתונים באופן אסינכרוני, עקבי וטרנזקציונלי.

אם אתם משתמשים ב-SharedPreferences לאחסון נתונים, כדאי לשקול מעבר ל-DataStore.

DataStore API

ממשק DataStore מספק את ה-API הבא:

  1. תזרים שאפשר להשתמש בו כדי לקרוא נתונים מ-DataStore

    val data: Flow<T>
    
  2. פונקציה לעדכון נתונים ב-DataStore

    suspend updateData(transform: suspend (t) -> T)
    

הגדרות של DataStore

אם רוצים לאחסן נתונים ולגשת אליהם באמצעות מפתחות, אפשר להשתמש בהטמעה של Preferences DataStore שלא דורשת סכימה מוגדרת מראש ולא מספקת בטיחות סוגים. יש לו API שדומה ל-SharedPreferences, אבל הוא לא כולל את החסרונות שקשורים להעדפות משותפות.

‫DataStore מאפשר לכם לשמור מחלקות בהתאמה אישית. כדי לעשות זאת, צריך להגדיר סכימה לנתונים ולספק Serializer כדי להמיר אותם לפורמט שניתן לשמירה. אתם יכולים לבחור להשתמש ב-Protocol Buffers, ב-JSON או בכל שיטת סריאליזציה אחרת.

הגדרה

כדי להשתמש ב-Jetpack DataStore באפליקציה, מוסיפים את השורות הבאות לקובץ Gradle, בהתאם להטמעה שרוצים להשתמש בה:

‫Preferences DataStore

מוסיפים את השורות הבאות לחלק של יחסי התלות בקובץ gradle:

Groovy

    dependencies {
        // Preferences DataStore (SharedPreferences like APIs)
        implementation "androidx.datastore:datastore-preferences:1.2.0"

        // Alternatively - without an Android dependency.
        implementation "androidx.datastore:datastore-preferences-core:1.2.0"
    }
    

Kotlin

    dependencies {
        // Preferences DataStore (SharedPreferences like APIs)
        implementation("androidx.datastore:datastore-preferences:1.2.0")

        // Alternatively - without an Android dependency.
        implementation("androidx.datastore:datastore-preferences-core:1.2.0")
    }
    

כדי להוסיף תמיכה אופציונלית ב-RxJava, מוסיפים את יחסי התלות הבאים:

Groovy

    dependencies {
        // optional - RxJava2 support
        implementation "androidx.datastore:datastore-preferences-rxjava2:1.2.0"

        // optional - RxJava3 support
        implementation "androidx.datastore:datastore-preferences-rxjava3:1.2.0"
    }
    

Kotlin

    dependencies {
        // optional - RxJava2 support
        implementation("androidx.datastore:datastore-preferences-rxjava2:1.2.0")

        // optional - RxJava3 support
        implementation("androidx.datastore:datastore-preferences-rxjava3:1.2.0")
    }
    

DataStore

מוסיפים את השורות הבאות לחלק של יחסי התלות בקובץ gradle:

Groovy

    dependencies {
        // Typed DataStore for custom data objects (for example, using Proto or JSON).
        implementation "androidx.datastore:datastore:1.2.0"

        // Alternatively - without an Android dependency.
        implementation "androidx.datastore:datastore-core:1.2.0"
    }
    

Kotlin

    dependencies {
        // Typed DataStore for custom data objects (for example, using Proto or JSON).
        implementation("androidx.datastore:datastore:1.2.0")

        // Alternatively - without an Android dependency.
        implementation("androidx.datastore:datastore-core:1.2.0")
    }
    

מוסיפים את יחסי התלות האופציונליים הבאים לתמיכה ב-RxJava:

Groovy

    dependencies {
        // optional - RxJava2 support
        implementation "androidx.datastore:datastore-rxjava2:1.2.0"

        // optional - RxJava3 support
        implementation "androidx.datastore:datastore-rxjava3:1.2.0"
    }
    

Kotlin

    dependencies {
        // optional - RxJava2 support
        implementation("androidx.datastore:datastore-rxjava2:1.2.0")

        // optional - RxJava3 support
        implementation("androidx.datastore:datastore-rxjava3:1.2.0")
    }
    

כדי לבצע סריאליזציה של תוכן, מוסיפים תלות בסריאליזציה של Protocol Buffers או JSON.

סריאליזציה של JSON

כדי להשתמש בסריאליזציה של JSON, מוסיפים את הקוד הבא לקובץ Gradle:

Groovy

    plugins {
        id("org.jetbrains.kotlin.plugin.serialization") version "2.2.20"
    }

    dependencies {
        implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0"
    }
    

Kotlin

    plugins {
        id("org.jetbrains.kotlin.plugin.serialization") version "2.2.20"
    }

    dependencies {
        implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
    }
    

סריאליזציה של Protobuf

כדי להשתמש בסריאליזציה של Protobuf, מוסיפים את השורה הבאה לקובץ Gradle:

Groovy

    plugins {
        id("com.google.protobuf") version "0.9.5"
    }
    dependencies {
        implementation "com.google.protobuf:protobuf-kotlin-lite:4.32.1"

    }

    protobuf {
        protoc {
            artifact = "com.google.protobuf:protoc:4.32.1"
        }
        generateProtoTasks {
            all().forEach { task ->
                task.builtins {
                    create("java") {
                        option("lite")
                    }
                    create("kotlin")
                }
            }
        }
    }
    

Kotlin

    plugins {
        id("com.google.protobuf") version "0.9.5"
    }
    dependencies {
        implementation("com.google.protobuf:protobuf-kotlin-lite:4.32.1")
    }

    protobuf {
        protoc {
            artifact = "com.google.protobuf:protoc:4.32.1"
        }
        generateProtoTasks {
            all().forEach { task ->
                task.builtins {
                    create("java") {
                        option("lite")
                    }
                    create("kotlin")
                }
            }
        }
    }
    

שימוש נכון ב-DataStore

כדי להשתמש ב-DataStore בצורה נכונה, חשוב לזכור תמיד את הכללים הבאים:

  1. לעולם אל תיצרו יותר ממופע אחד של DataStore עבור קובץ נתון באותו תהליך. פעולה כזו עלולה לשבור את כל הפונקציונליות של DataStore. אם יש כמה DataStore פעילים לקובץ נתון באותו תהליך, DataStore יחזיר IllegalStateException כשקוראים או מעדכנים נתונים.

  2. הסוג הגנרי של DataStore<T> חייב להיות בלתי ניתן לשינוי. שינוי של סוג שמשמש ב-DataStore מבטל את העקביות ש-DataStore מספק, ויוצר באגים שעלולים להיות חמורים וקשים לאיתור. מומלץ להשתמש ב-protocol buffers, שעוזרים להבטיח אי-שינוי, API ברור וסדרות יעילות.

  3. אל תערבבו בין שימושים במאפיינים SingleProcessDataStore ו-MultiProcessDataStore באותו קובץ. אם אתם מתכוונים לגשת אל DataStore מיותר מתהליך אחד, אתם צריכים להשתמש ב-MultiProcessDataStore.

הגדרת נתונים

‫Preferences DataStore

הגדרת מפתח שישמש לשמירת נתונים בדיסק.

val EXAMPLE_COUNTER = intPreferencesKey("example_counter")

JSON DataStore

במאגר נתונים מסוג JSON, מוסיפים הערה @Serialization לנתונים שרוצים לשמור

@Serializable
data class Settings(
    val exampleCounter: Int
)

מגדירים מחלקה שמטמיעה את Serializer<T>, כאשר T הוא הסוג של המחלקה שאליה הוספתם את ההערה הקודמת. חשוב להוסיף ערך ברירת מחדל לסריאליזציה, שישמש אם עדיין לא נוצר קובץ.

object SettingsSerializer : Serializer<Settings> {

    override val defaultValue: Settings = Settings(exampleCounter = 0)

    override suspend fun readFrom(input: InputStream): Settings =
        try {
            Json.decodeFromString<Settings>(
                input.readBytes().decodeToString()
            )
        } catch (serialization: SerializationException) {
            throw CorruptionException("Unable to read Settings", serialization)
        }

    override suspend fun writeTo(t: Settings, output: OutputStream) {
        output.write(
            Json.encodeToString(t)
                .encodeToByteArray()
        )
    }
}

Proto DataStore

ההטמעה של Proto DataStore משתמשת ב-DataStore ובמאגרי פרוטוקולים כדי לשמור אובייקטים מוקלדים בדיסק.

‫Proto DataStore דורש סכימה מוגדרת מראש בקובץ proto בספרייה app/src/main/proto/. הסכימה הזו מגדירה את הסוג של האובייקטים שאתם שומרים ב-Proto DataStore. מידע נוסף על הגדרת סכמת פרוטו זמין במדריך השפה של protobuf.

מוסיפים קובץ בשם settings.proto בתוך התיקייה src/main/proto:

syntax = "proto3";

option java_package = "com.example.datastore.snippets.proto";
option java_multiple_files = true;

message Settings {
  int32 example_counter = 1;
}

מגדירים מחלקה שמטמיעה את Serializer<T>, כאשר T הוא הסוג שמוגדר בקובץ הפרוטו. מחלקת הסריאליזציה הזו מגדירה איך DataStore קורא וכותב את סוג הנתונים שלכם. חשוב לוודא שכוללים ערך ברירת מחדל עבור ה-serializer, שישמש אם עדיין לא נוצר קובץ.

object SettingsSerializer : Serializer<Settings> {
    override val defaultValue: Settings = Settings.getDefaultInstance()

    override suspend fun readFrom(input: InputStream): Settings {
        try {
            return Settings.parseFrom(input)
        } catch (exception: InvalidProtocolBufferException) {
            throw CorruptionException("Cannot read proto.", exception)
        }
    }

    override suspend fun writeTo(t: Settings, output: OutputStream) {
        return t.writeTo(output)
    }
}

יצירת מאגר נתונים

צריך לציין שם לקובץ שמשמש לשמירת הנתונים.

‫Preferences DataStore

ההטמעה של Preferences DataStore משתמשת במחלקות DataStore ו-Preferences כדי לשמור צמדי מפתח-ערך בדיסק. משתמשים בנציג המאפיין שנוצר על ידי preferencesDataStore כדי ליצור מופע של DataStore<Preferences>. קוראים לה פעם אחת ברמה העליונה של קובץ Kotlin. גישה ל-DataStore דרך הנכס הזה בכל שאר חלקי האפליקציה. כך קל יותר לשמור על DataStore כ-singleton. אפשרות אחרת היא להשתמש ב-RxPreferenceDataStoreBuilder אם אתם משתמשים ב-RxJava. הפרמטר name הוא חובה והוא השם של מאגר נתוני ההעדפות.

// At the top level of your kotlin file:
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")

JSON DataStore

משתמשים בנציג המאפיין שנוצר על ידי dataStore כדי ליצור מופע של DataStore<T>, כאשר T הוא מחלקת הנתונים שניתנת לסריאליזציה. קוראים לה פעם אחת ברמה העליונה של קובץ ה-Kotlin, וניגשים אליה דרך נציג המאפיין הזה בכל שאר האפליקציה. הפרמטר fileName מציין ל-DataStore באיזה קובץ להשתמש כדי לאחסן את הנתונים, והפרמטר serializer מציין ל-DataStore את השם של מחלקת הסריאליזציה שהוגדרה בשלב 1.

val Context.dataStore: DataStore<Settings> by dataStore(
    fileName = "settings.json",
    serializer = SettingsSerializer,
)

Proto DataStore

משתמשים בנציג המאפיין שנוצר על ידי dataStore כדי ליצור מופע של DataStore<T>, כאשר T הוא הסוג שמוגדר בקובץ הפרוטו. קוראים לה פעם אחת ברמה העליונה של קובץ Kotlin וניגשים אליה דרך נציג המאפיין הזה בכל שאר האפליקציה. הפרמטר fileName מציין ל-DataStore באיזה קובץ להשתמש כדי לאחסן את הנתונים, והפרמטר serializer מציין ל-DataStore את השם של מחלקת הסריאליזציה שהוגדרה בשלב 1.

val Context.dataStore: DataStore<Settings> by dataStore(
    fileName = "settings.pb",
    serializer = SettingsSerializer,
)

קריאה מ-DataStore

צריך לציין שם לקובץ שמשמש לשמירת הנתונים.

‫Preferences DataStore

מכיוון ש-Preferences DataStore לא משתמש בסכימה מוגדרת מראש, צריך להשתמש בפונקציה של סוג המפתח המתאים כדי להגדיר מפתח לכל ערך שרוצים לאחסן במופע DataStore<Preferences>. לדוגמה, כדי להגדיר מפתח לערך int, משתמשים ב-