ऐप्लिकेशन आर्किटेक्चर: डेटा लेयर - डेटास्टोर - Android डेवलपर

प्रोजेक्ट: /architecture/_project.yaml किताब: /architecture/_book.yaml कीवर्ड: datastore, architecture, api:JetpackDataStore description: डेटा लेयर लाइब्रेरी के बारे में जानने के लिए, ऐप्लिकेशन के आर्किटेक्चर से जुड़ी इस गाइड को पढ़ें. इसमें Preferences DataStore और Proto DataStore, सेटअप करने के तरीके वगैरह के बारे में बताया गया है. hide_page_heading: true

DataStore   Android Jetpack का हिस्सा है.

Kotlin Multiplatform का इस्तेमाल करके देखें
Kotlin Multiplatform की मदद से, डेटा लेयर को अन्य प्लैटफ़ॉर्म के साथ शेयर किया जा सकता है. KMP में DataStore को सेट अप करने और उसका इस्तेमाल करने का तरीका जानें

Jetpack DataStore, डेटा स्टोरेज का एक समाधान है. इसकी मदद से, की-वैल्यू पेयर या टाइप किए गए ऑब्जेक्ट को प्रोटोकॉल बफ़र के साथ सेव किया जा सकता है. DataStore, डेटा को एसिंक्रोनस तरीके से, लगातार, और लेन-देन के हिसाब से स्टोर करने के लिए Kotlin कोरोटीन और फ़्लो का इस्तेमाल करता है.

अगर डेटा सेव करने के लिए SharedPreferences का इस्तेमाल किया जा रहा है, तो DataStore पर माइग्रेट करें.

DataStore API

DataStore इंटरफ़ेस, यह एपीआई उपलब्ध कराता है:

  1. ऐसा फ़्लो जिसका इस्तेमाल DataStore से डेटा पढ़ने के लिए किया जा सकता है

    val data: Flow<T>
    
  2. DataStore में डेटा अपडेट करने का फ़ंक्शन

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

DataStore कॉन्फ़िगरेशन

अगर आपको कुंजियों का इस्तेमाल करके डेटा को स्टोर और ऐक्सेस करना है, तो Preferences DataStore का इस्तेमाल करें. इसके लिए, पहले से तय किए गए स्कीमा की ज़रूरत नहीं होती. साथ ही, यह टाइप सेफ़्टी की सुविधा नहीं देता है. इसमें SharedPreferences जैसा एपीआई है, लेकिन इसमें शेयर की गई प्राथमिकताओं से जुड़ी कमियां नहीं हैं.

DataStore की मदद से, कस्टम क्लास को सेव किया जा सकता है. इसके लिए, आपको डेटा के लिए स्कीमा तय करना होगा. साथ ही, इसे सेव किए जा सकने वाले फ़ॉर्मैट में बदलने के लिए, Serializer देना होगा. आपके पास प्रोटोकॉल बफ़र, JSON या किसी अन्य सीरियलाइज़ेशन रणनीति का इस्तेमाल करने का विकल्प होता है.

सेटअप

अपने ऐप्लिकेशन में Jetpack DataStore का इस्तेमाल करने के लिए, अपनी Gradle फ़ाइल में यहां दी गई जानकारी जोड़ें. यह इस बात पर निर्भर करती है कि आपको कौनसी सुविधा इस्तेमाल करनी है:

Preferences DataStore

अपनी gradle फ़ाइल के dependencies सेक्शन में ये लाइनें जोड़ें:

Groovy

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

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

Kotlin

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

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

RxJava का इस्तेमाल करने के लिए, ये डिपेंडेंसी जोड़ें:

Groovy

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

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

Kotlin

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

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

DataStore

अपनी gradle फ़ाइल के dependencies सेक्शन में ये लाइनें जोड़ें:

Groovy

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

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

Kotlin

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

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

RxJava के साथ काम करने के लिए, यहां दी गई वैकल्पिक डिपेंडेंसी जोड़ें:

Groovy

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

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

Kotlin

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

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

कॉन्टेंट को क्रम से लगाने के लिए, Protocol Buffers या JSON serialization के लिए डिपेंडेंसी जोड़ें.

JSON सीरियलाइज़ेशन

JSON serialization का इस्तेमाल करने के लिए, अपनी 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 सीरियलाइज़ेशन का इस्तेमाल करने के लिए, अपनी 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 की ओर से उपलब्ध कराई गई स्थिरता खत्म हो जाती है. साथ ही, इससे ऐसी गंभीर गड़बड़ियां हो सकती हैं जिन्हें ठीक करना मुश्किल होता है. हमारा सुझाव है कि आप प्रोटोकॉल बफ़र का इस्तेमाल करें. इससे यह पक्का करने में मदद मिलती है कि डेटा में बदलाव नहीं किया जा सकता. साथ ही, इससे एपीआई को समझने में आसानी होती है और डेटा को सीरियल करने में कम समय लगता है.

  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 के लिए, app/src/main/proto/ डायरेक्ट्री में मौजूद किसी प्रोटो फ़ाइल में पहले से तय किया गया स्कीमा होना ज़रूरी है. यह स्कीमा, उन ऑब्जेक्ट के टाइप के बारे में बताता है जिन्हें Proto DataStore में सेव किया जाता है. प्रोटो स्कीमा तय करने के बारे में ज़्यादा जानने के लिए, प्रोटोबफ़ लैंग्वेज गाइड देखें.

src/main/proto फ़ोल्डर में settings.proto नाम की फ़ाइल जोड़ें:

syntax = "proto3";

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

message Settings {
  int32 example_counter = 1;
}

ऐसी क्लास तय करें जो Serializer<T> को लागू करती है. यहां Serializer<T>, प्रोटो फ़ाइल में तय किया गया टाइप है.T यह सीरियलाइज़र क्लास तय करती है कि DataStore आपके डेटा टाइप को कैसे पढ़ेगा और लिखेगा. पक्का करें कि आपने सीरियलाइज़र के लिए डिफ़ॉल्ट वैल्यू शामिल की हो, ताकि अगर अब तक कोई फ़ाइल नहीं बनाई गई है, तो उसका इस्तेमाल किया जा सके.

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)
    }
}

DataStore बनाना

आपको उस फ़ाइल का नाम बताना होगा जिसका इस्तेमाल डेटा को सेव करने के लिए किया जाता है.

Preferences DataStore

Preferences DataStore को लागू करने के लिए, DataStore और Preferences क्लास का इस्तेमाल किया जाता है. इससे की-वैल्यू पेयर को डिस्क में सेव किया जा सकता है. DataStore<Preferences> का इंस्टेंस बनाने के लिए, preferencesDataStore से बनाए गए प्रॉपर्टी डेलिगेट का इस्तेमाल करें. इसे Kotlin फ़ाइल के टॉप लेवल पर एक बार कॉल करें. अपने पूरे ऐप्लिकेशन में, इस प्रॉपर्टी के ज़रिए DataStore को ऐक्सेस करें. इससे DataStore को सिंगलटन के तौर पर बनाए रखना आसान हो जाता है. इसके अलावा, अगर RxJava का इस्तेमाल किया जा रहा है, तो RxPreferenceDataStoreBuilder का इस्तेमाल करें. name पैरामीटर का इस्तेमाल करना ज़रूरी है. यह पैरामीटर, Preferences DataStore का नाम है.

// 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 को पहले चरण में तय की गई सीरियलाइज़र क्लास के नाम के बारे में पता चलता है.

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

Proto DataStore

dataStore से बनाए गए प्रॉपर्टी डेलिगेट का इस्तेमाल करके, DataStore<T> का इंस्टेंस बनाएं. यहां T, प्रोटो फ़ाइल में तय किया गया टाइप है. इसे अपनी Kotlin फ़ाइल के टॉप लेवल पर एक बार कॉल करें. इसके बाद, अपने ऐप्लिकेशन के बाकी हिस्से में इस प्रॉपर्टी डेलिगेट के ज़रिए इसे ऐक्सेस करें. fileName पैरामीटर, DataStore को बताता है कि डेटा सेव करने के लिए किस फ़ाइल का इस्तेमाल करना है. वहीं, serializer पैरामीटर, DataStore को पहले चरण में तय की गई सीरियलाइज़र क्लास का नाम बताता है.

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

DataStore से डेटा पढ़ना

आपको उस फ़ाइल का नाम बताना होगा जिसका इस्तेमाल डेटा को सेव करने के लिए किया जाता है.

Preferences DataStore

Preferences DataStore में पहले से तय किए गए स्कीमा का इस्तेमाल नहीं किया जाता. इसलिए, आपको DataStore<Preferences> इंस्टेंस में सेव की जाने वाली हर वैल्यू के लिए, उससे जुड़े मुख्य टाइप फ़ंक्शन का इस्तेमाल करके एक कुंजी तय करनी होगी. उदाहरण के लिए, किसी पूर्णांक वैल्यू के लिए कुंजी तय करने के लिए, intPreferencesKey() का इस्तेमाल करें. इसके बाद, DataStore.data प्रॉपर्टी का इस्तेमाल करके, Flow का इस्तेमाल करके सही सेव की गई वैल्यू दिखाएं.

fun counterFlow(): Flow<Int> = context.dataStore.data.map { preferences ->
    preferences[EXAMPLE_COUNTER] ?: 0
}

JSON DataStore

स्टोर किए गए ऑब्जेक्ट से सही प्रॉपर्टी का Flow दिखाने के लिए, DataStore.data का इस्तेमाल करें.

fun counterFlow(): Flow<Int> = context.dataStore.data.map { settings ->
    settings.exampleCounter
}

Proto DataStore

स्टोर किए गए ऑब्जेक्ट से सही प्रॉपर्टी का Flow दिखाने के लिए, DataStore.data का इस्तेमाल करें.

fun counterFlow(): Flow<Int> = context.dataStore.data.map { settings ->
    settings.exampleCounter
}

DataStore में डेटा सेव करने की अनुमति

DataStore, updateData() फ़ंक्शन उपलब्ध कराता है. यह फ़ंक्शन, सेव किए गए ऑब्जेक्ट को लेन-देन के हिसाब से अपडेट करता है. updateData आपको डेटा टाइप के इंस्टेंस के तौर पर डेटा की मौजूदा स्थिति दिखाता है. साथ ही, यह डेटा को एटॉमिक रीड-राइट-बदलाव वाली कार्रवाई में अपडेट करता है. updateData ब्लॉक में मौजूद पूरे कोड को एक ही लेन-देन माना जाता है.

Preferences DataStore

suspend fun incrementCounter() {
    context.dataStore.updateData {
        it.toMutablePreferences().also { preferences ->
            preferences[EXAMPLE_COUNTER] = (preferences[EXAMPLE_COUNTER] ?: 0) + 1
        }
    }
}

JSON DataStore

suspend fun incrementCounter() {
    context.dataStore.updateData { settings ->
        settings.copy(exampleCounter = settings.exampleCounter + 1)
    }
}

Proto DataStore

suspend fun incrementCounter() {
    context.dataStore.updateData { settings ->
        settings.copy { exampleCounter = exampleCounter + 1 }
    }
}

सैंपल लिखना

इन फ़ंक्शन को एक क्लास में रखा जा सकता है. इसके बाद, Compose ऐप्लिकेशन में इनका इस्तेमाल किया जा सकता है.

Preferences DataStore

अब हम इन फ़ंक्शन को PreferencesDataStore नाम की क्लास में रख सकते हैं और इसका इस्तेमाल Compose ऐप्लिकेशन में कर सकते हैं.

val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
val preferencesDataStore = remember(context) { PreferencesDataStore(context) }

// Display counter value.
val exampleCounter by preferencesDataStore.counterFlow()
    .collectAsState(initial = 0, coroutineScope.coroutineContext)
Text(
    text = "Counter $exampleCounter",
    fontSize = 25.sp
)

// Update the counter.
Button(
    onClick = {
        coroutineScope.launch { preferencesDataStore.incrementCounter() }
    }
) {
    Text("increment")
}

JSON DataStore

अब हम इन फ़ंक्शन को JSONDataStore नाम की क्लास में रख सकते हैं और इसका इस्तेमाल Compose ऐप्लिकेशन में कर सकते हैं.

val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
val jsonDataStore = remember(context) { JsonDataStore(context) }

// Display counter value.
val exampleCounter by jsonDataStore.counterFlow()
    .collectAsState(initial = 0, coroutineScope.coroutineContext)
Text(
    text = "Counter $exampleCounter",
    fontSize = 25.sp
)

// Update the counter.
Button(onClick = { coroutineScope.launch { jsonDataStore.incrementCounter() } }) {
    Text("increment")
}

Proto DataStore

अब हम इन फ़ंक्शन को ProtoDataStore नाम की क्लास में रख सकते हैं और इसका इस्तेमाल Compose ऐप्लिकेशन में कर सकते हैं.

val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
val protoDataStore = remember(context) { ProtoDataStore(context) }

// Display counter value.
val exampleCounter by protoDataStore.counterFlow()
    .collectAsState(initial = 0, coroutineScope.coroutineContext)
Text(
    text = "Counter $exampleCounter",
    fontSize = 25.sp
)

// Update the counter.
Button(onClick = { coroutineScope.launch { protoDataStore.incrementCounter() } }) {
    Text("increment")
}

सिंक्रोनस कोड में DataStore का इस्तेमाल करना

DataStore का एक मुख्य फ़ायदा एसिंक्रोनस एपीआई है. हालांकि, हो सकता है कि आपके आस-पास के कोड को एसिंक्रोनस में बदलना हमेशा संभव न हो. ऐसा तब हो सकता है, जब किसी ऐसे मौजूदा कोडबेस पर काम किया जा रहा हो जो सिंक्रोनस डिस्क I/O का इस्तेमाल करता है. इसके अलावा, ऐसा तब भी हो सकता है, जब आपके पास ऐसी डिपेंडेंसी हो जो एसिंक्रोनस एपीआई उपलब्ध नहीं कराती है.

Kotlin कोरूटीन, सिंक्रोनस और एसिंक्रोनस कोड के बीच के अंतर को कम करने में मदद करने के लिए, runBlocking() कोरूटीन बिल्डर उपलब्ध कराते हैं. DataStore से डेटा को सिंक्रोनस तरीके से पढ़ने के लिए, runBlocking() का इस्तेमाल किया जा सकता है. RxJava, Flowable पर ब्लॉक करने के तरीके उपलब्ध कराता है. नीचे दिया गया कोड, DataStore से डेटा मिलने तक कॉलिंग थ्रेड को ब्लॉक करता है:

Kotlin

val exampleData = runBlocking { context.dataStore.data.first() }

Java

Settings settings = dataStore.data().blockingFirst();

यूज़र इंटरफ़ेस (यूआई) थ्रेड पर सिंक्रोनस I/O कार्रवाइयां करने से, ANR से जुड़ी गड़बड़ियां हो सकती हैं या यूज़र इंटरफ़ेस (यूआई) काम नहीं कर सकता. इन समस्याओं को कम करने के लिए, DataStore से डेटा को एसिंक्रोनस तरीके से प्रीलोड करें:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    lifecycleScope.launch {
        context.dataStore.data.first()
        // You should also handle IOExceptions here.
    }
}

Java

dataStore.data().first().subscribe();

इस तरह, DataStore डेटा को एसिंक्रोनस तरीके से पढ़ता है और उसे मेमोरी में कैश मेमोरी के तौर पर सेव करता है. बाद में, runBlocking() का इस्तेमाल करके सिंक्रोनस रीड ऑपरेशन तेज़ी से हो सकता है. साथ ही, अगर शुरुआती रीड ऑपरेशन पूरा हो गया है, तो हो सकता है कि डिस्क I/O ऑपरेशन की ज़रूरत ही न पड़े.

एक से ज़्यादा प्रोसेस वाले कोड में DataStore का इस्तेमाल करना

DataStore को कॉन्फ़िगर करके, अलग-अलग प्रोसेस में एक जैसा डेटा ऐक्सेस किया जा सकता है. साथ ही, डेटा की स्थिरता से जुड़ी प्रॉपर्टी भी एक जैसी होती हैं. खास तौर पर, DataStore ये सुविधाएं देता है:

  • रीड ऑपरेशन से सिर्फ़ वह डेटा मिलता है जो डिस्क में सेव किया गया है.
  • लिखने के बाद पढ़ने की सुविधा.
  • लिखने के अनुरोधों को क्रम से प्रोसेस किया जाता है.
  • रीड ऑपरेशन को कभी भी राइट ऑपरेशन से ब्लॉक नहीं किया जाता.

एक ऐसे सैंपल ऐप्लिकेशन के बारे में सोचें जिसमें एक सेवा और एक गतिविधि हो. इसमें सेवा, अलग प्रोसेस में चल रही हो और समय-समय पर DataStore को अपडेट करती हो.

इस उदाहरण में, JSON डेटास्टोर का इस्तेमाल किया गया है. हालांकि, आपके पास प्राथमिकताओं या प्रोटोडेटास्टोर का इस्तेमाल करने का विकल्प भी है.

@Serializable
data class Time(
    val lastUpdateMillis: Long
)

सीरियलाइज़र, DataStore को बताता है कि आपके डेटा टाइप को कैसे पढ़ा और लिखा जाए. पक्का करें कि आपने सीरियलाइज़र के लिए डिफ़ॉल्ट वैल्यू शामिल की हो, ताकि अगर अब तक कोई फ़ाइल नहीं बनाई गई है, तो उसका इस्तेमाल किया जा सके. यहां kotlinx.serialization का इस्तेमाल करके, लागू करने का एक उदाहरण दिया गया है:

object TimeSerializer : Serializer<Time> {

    override val defaultValue: Time = Time(lastUpdateMillis = 0L)

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

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

अलग-अलग प्रोसेस में DataStore का इस्तेमाल करने के लिए, आपको ऐप्लिकेशन और सेवा के कोड, दोनों के लिए DataStore का इस्तेमाल करके DataStore ऑब्जेक्ट बनाना होगा:MultiProcessDataStoreFactory

val dataStore = MultiProcessDataStoreFactory.create(
    serializer = TimeSerializer,
    produceFile = {
        File("${context.cacheDir.path}/time.pb")
    },
    corruptionHandler = null
)

अपनी AndroidManifiest.xml में यह जोड़ें:

<service
    android:name=".TimestampUpdateService"
    android:process=":my_process_id" />

यह सेवा समय-समय पर updateLastUpdateTime() को कॉल करती है. यह updateData का इस्तेमाल करके, डेटास्टोर में लिखता है.

suspend fun updateLastUpdateTime() {
    dataStore.updateData { time ->
        time.copy(lastUpdateMillis = System.currentTimeMillis())
    }
}

डेटा फ़्लो का इस्तेमाल करके, ऐप्लिकेशन सेवा की लिखी गई वैल्यू को पढ़ता है:

fun timeFlow(): Flow<Long> = dataStore.data.map { time ->
    time.lastUpdateMillis
}

अब हम इन सभी फ़ंक्शन को MultiProcessDataStore नाम की क्लास में एक साथ रख सकते हैं और इसका इस्तेमाल किसी ऐप्लिकेशन में कर सकते हैं.

यहां सेवा का कोड दिया गया है:

class TimestampUpdateService : Service() {
    val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
    val multiProcessDataStore by lazy { MultiProcessDataStore(applicationContext) }


    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        serviceScope.launch {
            while (true) {
                multiProcessDataStore.updateLastUpdateTime()
                delay(1000)
            }
        }
        return START_NOT_STICKY
    }

    override fun onDestroy() {
        super.onDestroy()
        serviceScope.cancel()
    }
}

साथ ही, ऐप्लिकेशन कोड:

val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
val multiProcessDataStore = remember(context) { MultiProcessDataStore(context) }

// Display time written by other process.
val lastUpdateTime by multiProcessDataStore.timeFlow()
    .collectAsState(initial = 0, coroutineScope.coroutineContext)
Text(
    text = "Last updated: $lastUpdateTime",
    fontSize = 25.sp
)

DisposableEffect(context) {
    val serviceIntent = Intent(context, TimestampUpdateService::class.java)
    context.startService(serviceIntent)
    onDispose {
        context.stopService(serviceIntent)
    }
}

Hilt डिपेंडेंसी इंजेक्शन का इस्तेमाल किया जा सकता है, ताकि हर प्रोसेस के लिए आपका DataStore इंस्टेंस यूनीक हो:

@Provides
@Singleton
fun provideDataStore(@ApplicationContext context: Context): DataStore<Settings> =
   MultiProcessDataStoreFactory.create(...)

फ़ाइल खराब होने की समस्या को ठीक करना

बहुत कम मामलों में, DataStore की डिस्क पर सेव की गई फ़ाइल खराब हो सकती है. डिफ़ॉल्ट रूप से, DataStore में डेटा करप्ट होने पर, वह अपने-आप ठीक नहीं होता. साथ ही, इससे डेटा पढ़ने की कोशिश करने पर, सिस्टम CorruptionException दिखाएगा.

DataStore, डेटा करप्ट होने की समस्या को ठीक करने वाला एपीआई उपलब्ध कराता है. इससे आपको इस तरह की समस्या को ठीक करने में मदद मिल सकती है. साथ ही, अपवाद से बचा जा सकता है. कॉन्फ़िगर किए जाने पर, डेटा करप्शन हैंडलर, खराब हुई फ़ाइल को एक नई फ़ाइल से बदल देता है. इस नई फ़ाइल में पहले से तय की गई डिफ़ॉल्ट वैल्यू होती है.

इस हैंडलर को सेट अप करने के लिए, by dataStore() में DataStore इंस्टेंस बनाते समय या DataStoreFactory फ़ैक्ट्री मेथड में corruptionHandler दें:

val dataStore: DataStore<Settings> = DataStoreFactory.create(
   serializer = SettingsSerializer(),
   produceFile = {
       File("${context.cacheDir.path}/myapp.preferences_pb")
   },
   corruptionHandler = ReplaceFileCorruptionHandler { Settings(lastUpdate = 0) }
)

सुझाव/राय दें या शिकायत करें

इन संसाधनों के ज़रिए, अपने सुझाव/राय दें या शिकायत करें:

समस्या को ट्रैक करने वाला टूल:
समस्याओं की शिकायत करें, ताकि हम बग ठीक कर सकें.

अन्य संसाधन

Jetpack DataStore के बारे में ज़्यादा जानने के लिए, यहां दिए गए अन्य संसाधन देखें:

सैंपल

ब्लॉग

कोडलैब