DataStore Android Jetpack का हिस्सा है.
Jetpack DataStore, डेटा स्टोरेज का एक समाधान है. इसकी मदद से, प्रोटोकॉल बफ़र के साथ की-वैल्यू पेयर या टाइप किए गए ऑब्जेक्ट सेव किए जा सकते हैं. DataStore, डेटा को एसिंक्रोनस तरीके से, लगातार, और लेन-देन के हिसाब से सेव करने के लिए Kotlin कोरोटीन और फ़्लो का इस्तेमाल करता है.
अगर फ़िलहाल डेटा सेव करने के लिए SharedPreferences
का इस्तेमाल किया जा रहा है, तो DataStore पर माइग्रेट करें.
Preferences DataStore और Proto DataStore
DataStore को दो अलग-अलग तरीकों से लागू किया जा सकता है: Preferences DataStore और Proto DataStore.
- Preferences DataStore, कुंजियों का इस्तेमाल करके डेटा को सेव करता है और उसे ऐक्सेस करता है. इस लागू करने के लिए, पहले से तय किए गए स्कीमा की ज़रूरत नहीं होती है. साथ ही, यह टाइप सुरक्षा भी नहीं देता है.
- Proto DataStore, डेटा को कस्टम डेटा टाइप के इंस्टेंस के तौर पर सेव करता है. इस इस्तेमाल के लिए, आपको प्रोटोकॉल बफ़र का इस्तेमाल करके स्कीमा तय करना होगा. हालांकि, इससे टाइप सेफ़्टी मिलती है.
DataStore का सही तरीके से इस्तेमाल करना
DataStore का सही तरीके से इस्तेमाल करने के लिए, इन नियमों का हमेशा ध्यान रखें:
एक ही प्रोसेस में, किसी फ़ाइल के लिए
DataStore
का एक से ज़्यादा इंस्टेंस कभी न बनाएं. ऐसा करने से, DataStore की सभी सुविधाएं काम करना बंद कर सकती हैं. अगर एक ही प्रोसेस में किसी फ़ाइल के लिए एक से ज़्यादा DataStore चालू हैं, तो डेटा को पढ़ने या अपडेट करने के दौरान DataStoreIllegalStateException
दिखाएगा.DataStore<T>
का सामान्य टाइप, बदला नहीं जा सकने वाला होना चाहिए. DataStore में इस्तेमाल किए गए टाइप में बदलाव करने से, DataStore की ओर से दी गई सभी गारंटी अमान्य हो जाती हैं. साथ ही, इससे गंभीर बग पैदा हो सकते हैं जिन्हें ठीक करना मुश्किल होता है. हमारा सुझाव है कि आप प्रोटोकॉल बफ़र का इस्तेमाल करें. ये अपरिवर्तनीयता की गारंटी देते हैं, इनमें आसान एपीआई होता है, और ये बेहतर सीरियलाइज़ेशन उपलब्ध कराते हैं.एक ही फ़ाइल के लिए,
SingleProcessDataStore
औरMultiProcessDataStore
का इस्तेमाल कभी भी एक साथ न करें. अगर आपको एक से ज़्यादा प्रोसेस सेDataStore
को ऐक्सेस करना है, तो हमेशाMultiProcessDataStore
का इस्तेमाल करें.
सेटअप
अपने ऐप्लिकेशन में Jetpack DataStore का इस्तेमाल करने के लिए, अपनी Gradle फ़ाइल में यहां दी गई जानकारी जोड़ें. यह इस बात पर निर्भर करती है कि आपको कौनसी सुविधा इस्तेमाल करनी है:
Preferences DataStore
Groovy
// Preferences DataStore (SharedPreferences like APIs) dependencies { implementation "androidx.datastore:datastore-preferences:1.1.7" // optional - RxJava2 support implementation "androidx.datastore:datastore-preferences-rxjava2:1.1.7" // optional - RxJava3 support implementation "androidx.datastore:datastore-preferences-rxjava3:1.1.7" } // Alternatively - use the following artifact without an Android dependency. dependencies { implementation "androidx.datastore:datastore-preferences-core:1.1.7" }
Kotlin
// Preferences DataStore (SharedPreferences like APIs) dependencies { implementation("androidx.datastore:datastore-preferences:1.1.7") // optional - RxJava2 support implementation("androidx.datastore:datastore-preferences-rxjava2:1.1.7") // optional - RxJava3 support implementation("androidx.datastore:datastore-preferences-rxjava3:1.1.7") } // Alternatively - use the following artifact without an Android dependency. dependencies { implementation("androidx.datastore:datastore-preferences-core:1.1.7") }
Proto DataStore
Groovy
// Typed DataStore (Typed API surface, such as Proto) dependencies { implementation "androidx.datastore:datastore:1.1.7" // optional - RxJava2 support implementation "androidx.datastore:datastore-rxjava2:1.1.7" // optional - RxJava3 support implementation "androidx.datastore:datastore-rxjava3:1.1.7" } // Alternatively - use the following artifact without an Android dependency. dependencies { implementation "androidx.datastore:datastore-core:1.1.7" }
Kotlin
// Typed DataStore (Typed API surface, such as Proto) dependencies { implementation("androidx.datastore:datastore:1.1.7") // optional - RxJava2 support implementation("androidx.datastore:datastore-rxjava2:1.1.7") // optional - RxJava3 support implementation("androidx.datastore:datastore-rxjava3:1.1.7") } // Alternatively - use the following artifact without an Android dependency. dependencies { implementation("androidx.datastore:datastore-core:1.1.7") }
Preferences DataStore की मदद से, की-वैल्यू पेयर सेव करना
Preferences DataStore को लागू करने के लिए, DataStore
और Preferences
क्लास का इस्तेमाल किया जाता है. इनकी मदद से, आसान की-वैल्यू पेयर को डिस्क में सेव किया जाता है.
Preferences DataStore बनाना
DataStore<Preferences>
का इंस्टेंस बनाने के लिए, preferencesDataStore
से बनाए गए प्रॉपर्टी डेलिगेट का इस्तेमाल करें. इसे अपनी Kotlin फ़ाइल के टॉप लेवल पर एक बार कॉल करें. इसके बाद, अपने पूरे ऐप्लिकेशन में इस प्रॉपर्टी को ऐक्सेस करें. इससे DataStore
को सिंगलटन के तौर पर बनाए रखना आसान हो जाता है. इसके अलावा, अगर RxJava का इस्तेमाल किया जा रहा है, तो RxPreferenceDataStoreBuilder
का इस्तेमाल करें. name
पैरामीटर का इस्तेमाल करना ज़रूरी है. यह Preferences 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>
इंस्टेंस में सेव करना है. उदाहरण के लिए, किसी पूर्णांक वैल्यू के लिए कुंजी तय करने के लिए, 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 में डेटा सेव करने की अनुमति
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 फ़ाइल में पहले से तय किया गया स्कीमा होना ज़रूरी है. यह स्कीमा, उन ऑब्जेक्ट के टाइप के बारे में बताता है जिन्हें Proto DataStore में सेव किया जाता है. प्रोटो स्कीमा तय करने के बारे में ज़्यादा जानने के लिए, प्रोटोबफ़ भाषा की गाइड देखें.
syntax = "proto3";
option java_package = "com.example.application.proto";
option java_multiple_files = true;
message Settings {
int32 example_counter = 1;
}
Proto DataStore बनाना
टाइप किए गए ऑब्जेक्ट सेव करने के लिए, Proto DataStore बनाने के दो चरण होते हैं:
- ऐसी क्लास तय करें जो
Serializer<T>
को लागू करती है. यहांSerializer<T>
, प्रोटो फ़ाइल में तय किया गया टाइप है.T
यह सीरियलाइज़र क्लास, DataStore को आपके डेटा टाइप को पढ़ने और लिखने का तरीका बताती है. पक्का करें कि आपने सीरियलाइज़र के लिए डिफ़ॉल्ट वैल्यू शामिल की हो, ताकि अगर अब तक कोई फ़ाइल नहीं बनाई गई है, तो उसका इस्तेमाल किया जा सके. dataStore
से बनाए गए प्रॉपर्टी डेलिगेट का इस्तेमाल करके,DataStore<T>
का इंस्टेंस बनाएं. यहां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 कार्रवाइयां करने से, एएनआर या यूआई जंक की समस्या हो सकती है. इन समस्याओं को कम करने के लिए, 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 को अपडेट करती है
<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) } } }
ऐप्लिकेशन इन बदलावों को इकट्ठा करेगा और अपने यूज़र इंटरफ़ेस (यूआई) को अपडेट करेगा
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): Settings =
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()
में 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 के बारे में ज़्यादा जानने के लिए, यहां दिए गए अन्य संसाधन देखें:
सैंपल
ब्लॉग
कोडलैब
आपके लिए सुझाव
- ध्यान दें: JavaScript बंद होने पर लिंक टेक्स्ट दिखता है
- पेज में बंटे डेटा को लोड करना और दिखाना
- LiveData के बारे में खास जानकारी
- लेआउट और बाइंडिंग एक्सप्रेशन