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

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

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

प्राथमिकताएं DataStore और Proto DataStore

DataStore दो अलग-अलग तरीकों से लागू किया जा सकता है: Preferences DataStore और Proto DataStore.

  • Preferences DataStore, कुंजियों का इस्तेमाल करके डेटा को सेव और ऐक्सेस करता है. इस तरीके को लागू करने के लिए, पहले से तय किए गए स्कीमा की ज़रूरत नहीं होती. साथ ही, यह टाइप सेफ़्टी की सुविधा नहीं देता.
  • Proto DataStore, डेटा को कस्टम डेटा टाइप के इंस्टेंस के तौर पर सेव करता है. इसे लागू करने के लिए, आपको प्रोटोकॉल बफ़र का इस्तेमाल करके स्कीमा तय करना होगा. हालांकि, इससे डेटा सुरक्षित रहता है.

DataStore का सही तरीके से इस्तेमाल करना

DataStore को सही तरीके से इस्तेमाल करने के लिए, हमेशा इन नियमों का ध्यान रखें:

  1. एक ही प्रोसेस में, किसी फ़ाइल के लिए DataStore का एक से ज़्यादा इंस्टेंस कभी न बनाएं. ऐसा करने से, DataStore की सभी सुविधाएं काम करना बंद कर सकती हैं. अगर एक ही प्रोसेस में किसी फ़ाइल के लिए कई DataStore चालू हैं, तो डेटा को पढ़ते या अपडेट करते समय, DataStore IllegalStateException को फेंक देगा.

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

  3. एक ही फ़ाइल के लिए, SingleProcessDataStore और MultiProcessDataStore का इस्तेमाल कभी एक साथ न करें. अगर आपको DataStore को एक से ज़्यादा प्रोसेस से ऐक्सेस करना है, तो हमेशा MultiProcessDataStore का इस्तेमाल करें.

सेटअप

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

डेटा स्टोर की सेटिंग

ग्रूवी

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

        // optional - RxJava2 support
        implementation "androidx.datastore:datastore-preferences-rxjava2:1.1.1"

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

    // Alternatively - use the following artifact without an Android dependency.
    dependencies {
        implementation "androidx.datastore:datastore-preferences-core:1.1.1"
    }
    

Kotlin

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

        // optional - RxJava2 support
        implementation("androidx.datastore:datastore-preferences-rxjava2:1.1.1")

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

    // Alternatively - use the following artifact without an Android dependency.
    dependencies {
        implementation("androidx.datastore:datastore-preferences-core:1.1.1")
    }
    

प्रोटो डेटास्टोर

ग्रूवी

    // Typed DataStore (Typed API surface, such as Proto)
    dependencies {
        implementation "androidx.datastore:datastore:1.1.1"

        // optional - RxJava2 support
        implementation "androidx.datastore:datastore-rxjava2:1.1.1"

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

    // Alternatively - use the following artifact without an Android dependency.
    dependencies {
        implementation "androidx.datastore:datastore-core:1.1.1"
    }
    

Kotlin

    // Typed DataStore (Typed API surface, such as Proto)
    dependencies {
        implementation("androidx.datastore:datastore:1.1.1")

        // optional - RxJava2 support
        implementation("androidx.datastore:datastore-rxjava2:1.1.1")

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

    // Alternatively - use the following artifact without an Android dependency.
    dependencies {
        implementation("androidx.datastore:datastore-core:1.1.1")
    }
    

Preferences DataStore के साथ की-वैल्यू पेयर को स्टोर करें

प्राथमिकताएं DataStore लागू करने के लिए, DataStore और Preferences क्लास का इस्तेमाल किया जाता है. इससे डिस्क में आसान की-वैल्यू पेयर बनाए जाते हैं.

प्राथमिकताएं सेव करने के लिए डेटास्टोर बनाना

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

Kotlin

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

Java

RxDataStore<Preferences> dataStore =
  new RxPreferenceDataStoreBuilder(context, /*name=*/ "settings").build();

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

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

Kotlin

val EXAMPLE_COUNTER = intPreferencesKey("example_counter")
val exampleCounterFlow: Flow<Int> = context.dataStore.data
  .map { preferences ->
    // No type safety.
    preferences[EXAMPLE_COUNTER] ?: 0
}

Java

Preferences.Key<Integer> EXAMPLE_COUNTER = PreferencesKeys.int("example_counter");

Flowable<Integer> exampleCounterFlow =
  dataStore.data().map(prefs -> prefs.get(EXAMPLE_COUNTER));

प्राथमिकता वाले डेटास्टोर में लिखना

Preferences DataStore में एक ऐसा edit() फ़ंक्शन होता है जो DataStore में डेटा को ट्रांज़ैक्शन के हिसाब से अपडेट करता है. फ़ंक्शन का transform पैरामीटर, कोड का एक ब्लॉक स्वीकार करता है, जहां ज़रूरत के हिसाब से वैल्यू अपडेट की जा सकती हैं. ट्रांसफ़ॉर्म ब्लॉक में मौजूद सभी कोड को एक ही ट्रांज़ैक्शन माना जाता है.

Kotlin

suspend fun incrementCounter() {
  context.dataStore.edit { settings ->
    val currentCounterValue = settings[EXAMPLE_COUNTER] ?: 0
    settings[EXAMPLE_COUNTER] = currentCounterValue + 1
  }
}

Java

Single<Preferences> updateResult =  dataStore.updateDataAsync(prefsIn -> {
  MutablePreferences mutablePreferences = prefsIn.toMutablePreferences();
  Integer currentInt = prefsIn.get(INTEGER_KEY);
  mutablePreferences.set(INTEGER_KEY, currentInt != null ? currentInt + 1 : 1);
  return Single.just(mutablePreferences);
});
// The update is completed once updateResult is completed.

Proto DataStore की मदद से, टाइप किए गए ऑब्जेक्ट स्टोर करना

Proto DataStore लागू करने की प्रोसेस में, टाइप किए गए ऑब्जेक्ट को डिस्क में बनाए रखने के लिए DataStore और प्रोटोकॉल बफ़र का इस्तेमाल किया जाता है.

स्कीमा तय करना

Proto DataStore के लिए, app/src/main/proto/ डायरेक्ट्री में मौजूद प्रोटो फ़ाइल में पहले से तय स्कीमा होना ज़रूरी है. यह स्कीमा, उन ऑब्जेक्ट के टाइप के बारे में बताता है जिन्हें आपने अपने Proto DataStore में सेव किया है. प्रोटो स्कीमा तय करने के बारे में ज़्यादा जानने के लिए, प्रोटोबफ़ भाषा की गाइड देखें.

syntax = "proto3";

option java_package = "com.example.application";
option java_multiple_files = true;

message Settings {
  int32 example_counter = 1;
}

प्रोटो DataStore बनाना

टाइप किए गए ऑब्जेक्ट को सेव करने के लिए, Proto DataStore बनाने के दो चरण होते हैं:

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

Kotlin

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) = t.writeTo(output)
}

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

Java

private static class SettingsSerializer implements Serializer<Settings> {
  @Override
  public Settings getDefaultValue() {
    Settings.getDefaultInstance();
  }

  @Override
  public Settings readFrom(@NotNull InputStream input) {
    try {
      return Settings.parseFrom(input);
    } catch (exception: InvalidProtocolBufferException) {
      throw CorruptionException(Cannot read proto., exception);
    }
  }

  @Override
  public void writeTo(Settings t, @NotNull OutputStream output) {
    t.writeTo(output);
  }
}

RxDataStore<Byte> dataStore =
    new RxDataStoreBuilder<Byte>(context, /* fileName= */ "settings.pb", new SettingsSerializer()).build();

किसी Proto DataStore से डेटा पढ़ना

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

Kotlin

val exampleCounterFlow: Flow<Int> = context.settingsDataStore.data
  .map { settings ->
    // The exampleCounter property is generated from the proto schema.
    settings.exampleCounter
  }

Java

Flowable<Integer> exampleCounterFlow =
  dataStore.data().map(settings -> settings.getExampleCounter());

Proto DataStore में डेटा लिखना

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

Kotlin

suspend fun incrementCounter() {
  context.settingsDataStore.updateData { currentSettings ->
    currentSettings.toBuilder()
      .setExampleCounter(currentSettings.exampleCounter + 1)
      .build()
    }
}

Java

Single<Settings> updateResult =
  dataStore.updateDataAsync(currentSettings ->
    Single.just(
      currentSettings.toBuilder()
        .setExampleCounter(currentSettings.getExampleCounter() + 1)
        .build()));

सिंक करने वाले कोड में 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 ये गारंटी देता है:

  • रीड सिर्फ़ उस डेटा को दिखाती है जो डिस्क में सेव है.
  • डेटा को लिखने के बाद उसे पढ़ने पर, डेटा में कोई बदलाव न होना.
  • डेटा को क्रम से लिखा जाता है.
  • रीड को कभी भी राइड से ब्लॉक नहीं किया जाता.

किसी सेवा और गतिविधि वाले सैंपल ऐप्लिकेशन को आज़माएं:

  1. यह सेवा, अलग प्रोसेस में चल रही है और समय-समय पर DataStore को अपडेट करती है

    <service
      android:name=".MyService"
      android:process=":my_process_id" />
    
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
          scope.launch {
              while(isActive) {
                  dataStore.updateData {
                      Settings(lastUpdate = System.currentTimeMillis())
                  }
                  delay(1000)
              }
          }
    }
    
  2. ऐप्लिकेशन इन बदलावों को इकट्ठा करेगा और अपने यूज़र इंटरफ़ेस (यूआई) को अपडेट करेगा

    val settings: Settings by dataStore.data.collectAsState()
    Text(
      text = "Last updated: $${settings.timestamp}",
    )
    

अलग-अलग प्रोसेस में DataStore का इस्तेमाल करने के लिए, आपको MultiProcessDataStoreFactory का इस्तेमाल करके DataStore ऑब्जेक्ट बनाना होगा.

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

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

@Serializable
data class Settings(
   val lastUpdate: Long
)

@Singleton
class SettingsSerializer @Inject constructor() : Serializer<Settings> {

   override val defaultValue = Settings(lastUpdate = 0)

   override suspend fun readFrom(input: InputStream): Timer =
       try {
           Json.decodeFromString(
               Settings.serializer(), 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(Settings.serializer(), t)
               .encodeToByteArray()
       )
   }
}

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

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

फ़ाइल में हुए नुकसान को ठीक करना

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

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

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

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

सुझाव या राय दें

इन संसाधनों की मदद से, अपने सुझाव, शिकायत या राय हमारे साथ शेयर करें:

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

अन्य संसाधन

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

सैंपल

ब्लॉग

कोड लैब