بخشی از DataStore در Android Jetpack .
Jetpack DataStore یک راهکار ذخیرهسازی داده است که به شما امکان میدهد جفتهای کلید-مقدار یا اشیاء تایپشده را با بافرهای پروتکل ذخیره کنید. DataStore از کوروتینها و Flow کاتلین برای ذخیره دادهها به صورت غیرهمزمان، سازگار و تراکنشی استفاده میکند.
اگر از SharedPreferences برای ذخیره دادهها استفاده میکنید، به جای آن، مهاجرت به DataStore را در نظر بگیرید.
API فروشگاه داده
رابط DataStore API زیر را ارائه میدهد:
جریانی که میتواند برای خواندن دادهها از DataStore استفاده شود
val data: Flow<T>تابعی برای بهروزرسانی دادهها در DataStore
suspend updateData(transform: suspend (t) -> T)
پیکربندیهای فروشگاه داده
اگر میخواهید دادهها را با استفاده از کلیدها ذخیره و به آنها دسترسی داشته باشید، از پیادهسازی Preferences DataStore استفاده کنید که به طرحواره از پیش تعریفشدهای نیاز ندارد و ایمنی نوع را ارائه نمیدهد. این پیادهسازی یک API شبیه به SharedPreferences دارد اما معایب مربوط به shared preferences را ندارد.
DataStore به شما امکان میدهد کلاسهای سفارشی را ماندگار کنید. برای انجام این کار، باید یک طرحواره برای دادهها تعریف کنید و یک Serializer برای تبدیل آن به یک قالب ماندگار ارائه دهید. میتوانید از Protocol Buffers، JSON یا هر استراتژی سریالسازی دیگری استفاده کنید.
راهاندازی
برای استفاده از Jetpack DataStore در برنامه خود، بسته به نوع پیادهسازی که میخواهید استفاده کنید، موارد زیر را به فایل Gradle خود اضافه کنید:
فروشگاه داده تنظیمات
خطوط زیر را به بخش وابستگیهای فایل gradle خود اضافه کنید:
گرووی
dependencies { // Preferences DataStore (SharedPreferences like APIs) implementation "androidx.datastore:datastore-preferences:1.2.1" // Alternatively - without an Android dependency. implementation "androidx.datastore:datastore-preferences-core:1.2.1" }
کاتلین
dependencies { // Preferences DataStore (SharedPreferences like APIs) implementation("androidx.datastore:datastore-preferences:1.2.1") // Alternatively - without an Android dependency. implementation("androidx.datastore:datastore-preferences-core:1.2.1") }
برای افزودن پشتیبانی اختیاری از RxJava، وابستگیهای زیر را اضافه کنید:
گرووی
dependencies { // optional - RxJava2 support implementation "androidx.datastore:datastore-preferences-rxjava2:1.2.1" // optional - RxJava3 support implementation "androidx.datastore:datastore-preferences-rxjava3:1.2.1" }
کاتلین
dependencies { // optional - RxJava2 support implementation("androidx.datastore:datastore-preferences-rxjava2:1.2.1") // optional - RxJava3 support implementation("androidx.datastore:datastore-preferences-rxjava3:1.2.1") }
فروشگاه داده
خطوط زیر را به بخش وابستگیهای فایل gradle خود اضافه کنید:
گرووی
dependencies { // Typed DataStore for custom data objects (for example, using Proto or JSON). implementation "androidx.datastore:datastore:1.2.1" // Alternatively - without an Android dependency. implementation "androidx.datastore:datastore-core:1.2.1" }
کاتلین
dependencies { // Typed DataStore for custom data objects (for example, using Proto or JSON). implementation("androidx.datastore:datastore:1.2.1") // Alternatively - without an Android dependency. implementation("androidx.datastore:datastore-core:1.2.1") }
وابستگیهای اختیاری زیر را برای پشتیبانی از RxJava اضافه کنید:
گرووی
dependencies { // optional - RxJava2 support implementation "androidx.datastore:datastore-rxjava2:1.2.1" // optional - RxJava3 support implementation "androidx.datastore:datastore-rxjava3:1.2.1" }
کاتلین
dependencies { // optional - RxJava2 support implementation("androidx.datastore:datastore-rxjava2:1.2.1") // optional - RxJava3 support implementation("androidx.datastore:datastore-rxjava3:1.2.1") }
برای سریالسازی محتوا، وابستگیهایی را برای Protocol Buffers یا سریالسازی JSON اضافه کنید.
سریالسازی JSON
برای استفاده از سریالسازی JSON، موارد زیر را به فایل Gradle خود اضافه کنید:
گرووی
plugins { id("org.jetbrains.kotlin.plugin.serialization") version "2.2.20" } dependencies { implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0" }
کاتلین
plugins { id("org.jetbrains.kotlin.plugin.serialization") version "2.2.20" } dependencies { implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0") }
سریالسازی پروتوباف
برای استفاده از سریالسازی Protobuf، موارد زیر را به فایل Gradle خود اضافه کنید:
گرووی
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") } } } }
کاتلین
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، همیشه قوانین زیر را در نظر داشته باشید:
هرگز بیش از یک نمونه
DataStoreبرای یک فایل مشخص در یک فرآیند ایجاد نکنید. انجام این کار میتواند تمام قابلیتهای DataStore را از کار بیندازد. اگر چندین DataStore برای یک فایل مشخص در یک فرآیند فعال باشند، DataStore هنگام خواندن یا بهروزرسانی دادهها،IllegalStateExceptionرا صادر میکند.نوع عمومی
DataStore<T>باید تغییرناپذیر باشد. تغییر نوع استفاده شده در DataStore، ثباتی را که DataStore ارائه میدهد، نامعتبر میکند و باعث ایجاد اشکالات جدی و دشوار میشود. توصیه میکنیم از بافرهای پروتکل استفاده کنید که به تضمین تغییرناپذیری، یک API واضح و سریالسازی کارآمد کمک میکنند.برای یک فایل، از
SingleProcessDataStoreوMultiProcessDataStoreبه طور همزمان استفاده نکنید . اگر قصد دارید از طریق بیش از یک فرآیند بهDataStoreدسترسی داشته باشید، بایدMultiProcessDataStoreاستفاده کنید.
تعریف داده
فروشگاه داده تنظیمات
کلیدی را تعریف کنید که برای ذخیره دادهها روی دیسک استفاده خواهد شد.
val EXAMPLE_COUNTER = intPreferencesKey("example_counter")
فروشگاه داده JSON
برای ذخیرهساز دادهی JSON، یک حاشیهنویسی @Serialization به دادههایی که میخواهید ذخیره شوند اضافه کنید.
@Serializable
data class Settings(
val exampleCounter: Int
)
کلاسی تعریف کنید که Serializer<T> پیادهسازی کند، که در آن T نوع کلاسی است که حاشیهنویسی قبلی را به آن اضافه کردهاید. مطمئن شوید که یک مقدار پیشفرض برای serializer در نظر گرفتهاید تا در صورتی که هنوز فایلی ایجاد نشده است، از آن استفاده شود.
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 از DataStore و بافرهای پروتکل برای حفظ اشیاء تایپشده روی دیسک استفاده میکند.
Proto DataStore به یک طرحواره از پیش تعریف شده در یک فایل proto در دایرکتوری app/src/main/proto/ نیاز دارد. این طرحواره، نوع اشیایی را که در Proto DataStore خود ذخیره میکنید، تعریف میکند. برای کسب اطلاعات بیشتر در مورد تعریف یک طرحواره proto، به راهنمای زبان 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 نوع تعریف شده در فایل proto است. این کلاس serializer نحوه خواندن و نوشتن نوع داده شما توسط 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 از کلاسهای DataStore و Preferences برای حفظ جفتهای کلید-مقدار در دیسک استفاده میکند. از نماینده ویژگی ایجاد شده توسط preferencesDataStore برای ایجاد نمونهای از DataStore<Preferences> استفاده کنید. آن را یک بار در سطح بالای فایل Kotlin خود فراخوانی کنید. در بقیه برنامه خود از طریق این ویژگی به DataStore دسترسی داشته باشید. این کار باعث میشود DataStore شما به عنوان یک singleton آسانتر نگه داشته شود. پارامتر اجباری name نام Preferences DataStore است.
// At the top level of your kotlin file:
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
فروشگاه داده JSON
از نماینده ویژگی ایجاد شده توسط dataStore برای ایجاد نمونهای از DataStore<T> استفاده کنید، که در آن T کلاس داده قابل سریالسازی است. آن را یک بار در سطح بالای فایل کاتلین خود فراخوانی کنید و از طریق این نماینده ویژگی در بقیه برنامه خود به آن دسترسی داشته باشید. پارامتر fileName به DataStore میگوید که از کدام فایل برای ذخیره دادهها استفاده کند و پارامتر serializer به DataStore نام کلاس serializer که قبلاً تعریف شده است را میگوید.
val Context.dataStore: DataStore<Settings> by dataStore(
fileName = "settings.json",
serializer = SettingsSerializer,
)
فروشگاه داده پروتو
از نماینده ویژگی ایجاد شده توسط dataStore برای ایجاد نمونهای از DataStore<T> استفاده کنید، که در آن T نوع تعریف شده در فایل proto است. آن را یک بار در سطح بالای فایل Kotlin خود فراخوانی کنید و از طریق این نماینده ویژگی در بقیه برنامه خود به آن دسترسی داشته باشید. پارامتر fileName به DataStore میگوید که از کدام فایل برای ذخیره دادهها استفاده کند و پارامتر serializer به DataStore نام کلاس serializer که قبلاً تعریف شده است را میگوید.
val Context.dataStore: DataStore<Settings> by dataStore(
fileName = "settings.pb",
serializer = SettingsSerializer,
)
خواندن از DataStore
شما باید نامی برای فایلی که برای ذخیره دادهها استفاده میشود، مشخص کنید.
فروشگاه داده تنظیمات
از آنجا که Preferences DataStore از یک طرحواره از پیش تعریف شده استفاده نمیکند، شما باید از تابع نوع کلید مربوطه برای تعریف یک کلید برای هر مقداری که نیاز به ذخیره در نمونه DataStore<Preferences> دارید، استفاده کنید. به عنوان مثال، برای تعریف یک کلید برای یک مقدار int، intPreferencesKey استفاده کنید. سپس، از ویژگی DataStore.data برای نمایش مقدار ذخیره شده مناسب با استفاده از یک Flow استفاده کنید.
fun counterFlow(): Flow<Int> = context.dataStore.data.map { preferences ->
preferences[EXAMPLE_COUNTER] ?: 0
}
فروشگاه داده JSON
از DataStore.data برای نمایش یک Flow از ویژگی مناسب از شیء ذخیره شده خود استفاده کنید.
fun counterFlow(): Flow<Int> = context.dataStore.data.map { settings ->
settings.exampleCounter
}
فروشگاه داده پروتو
از DataStore.data برای نمایش یک Flow از ویژگی مناسب از شیء ذخیره شده خود استفاده کنید.
fun counterFlow(): Flow<Int> = context.dataStore.data.map { settings ->
settings.exampleCounter
}
collectAsStateWithLifecycle برای مصرف Flow تولید شده توسط یک ViewModel در یک composable استفاده کنید. این کار با خیال راحت DataStore Flow را به Compose State تبدیل میکند که باعث recomposition میشود.
@Composable
fun SomeScreen(counterFlow: Flow<Int>) {
val counter by counterFlow.collectAsStateWithLifecycle(initialValue = 0)
Text(text = "Example counter: ${counter}")
}
برای اطلاعات بیشتر در مورد collectAsStateWithLifecycle ، به State و Jetpack Compose مراجعه کنید.
نوشتن در DataStore
DataStore یک تابع updateData ارائه میدهد که به صورت تراکنشی یک شیء ذخیره شده را بهروزرسانی میکند. updateData وضعیت فعلی دادهها را به عنوان نمونهای از نوع داده شما در اختیار شما قرار میدهد و دادهها را به صورت تراکنشی در یک عملیات خواندن-نوشتن-تغییر اتمی بهروزرسانی میکند. تمام کد موجود در بلوک updateData به عنوان یک تراکنش واحد در نظر گرفته میشود.
فروشگاه داده تنظیمات
suspend fun incrementCounter() {
context.dataStore.updateData {
it.toMutablePreferences().also { preferences ->
preferences[EXAMPLE_COUNTER] = (preferences[EXAMPLE_COUNTER] ?: 0) + 1
}
}
}
فروشگاه داده JSON
suspend fun incrementCounter() {
context.dataStore.updateData { settings ->
settings.copy(exampleCounter = settings.exampleCounter + 1)
}
}
فروشگاه داده پروتو
suspend fun incrementCounter() {
context.dataStore.updateData { settings ->
settings.copy { exampleCounter = exampleCounter + 1 }
}
}
استفاده از DataStore در یک برنامه Compose
برای استفاده از DataStore در یک برنامه Compose، با نگه داشتن عملیات DataStore در لایه داده خود (مانند یک مخزن) و قرار دادن دادهها در رابط کاربری خود از طریق ViewModel ، دستورالعملهای معماری برنامه اندروید را دنبال کنید.
از خواندن یا نوشتن مستقیم در DataStore درون توابع ترکیبی خود خودداری کنید.
DataStore را از طریق یک ViewModel نمایش دهید. مخزن خود (که DataStore را در بر میگیرد) را به
ViewModelخود منتقل کنید وFlowبهStateFlowتبدیل کنید تا رابط کاربری بتواند به راحتی آن را مشاهده کند، همانطور که در قطعه کد زیر نشان داده شده است:class SettingsViewModel( private val userPreferencesRepository: UserPreferencesRepository ) : ViewModel() { // Expose the DataStore flow as a StateFlow for Compose val userSettings: StateFlow<UserSettings> = userPreferencesRepository.userSettingsFlow .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), initialValue = UserSettings.getDefaultInstance() ) fun updateCounter(newValue: Int) { viewModelScope.launch { userPreferencesRepository.updateCounter(newValue) } } }مشاهده کنید و از composable خود بنویسید. از
collectAsStateWithLifecycleبرای مشاهده ایمنStateFlowدر رابط کاربری خود استفاده کنید و توابعViewModelرا برای مدیریت نوشتن فراخوانی کنید، همانطور که در قطعه کد زیر نشان داده شده است:@Composable fun SettingsScreen( viewModel: SettingsViewModel = viewModel() ) { // Safely collect the state val settings by viewModel.userSettings.collectAsStateWithLifecycle() Column(modifier = Modifier.padding(16.dp)) { Text(text = "Current counter: ${settings.counter}") Spacer(modifier = Modifier.height(8.dp)) Button(onClick = { viewModel.updateCounter(settings.counter + 1) }) { Text("Increment Counter") } } }
استفاده از DataStore در کد چند فرآیندی
شما میتوانید DataStore را طوری پیکربندی کنید که به دادههای یکسان در فرآیندهای مختلف با همان ویژگیهای سازگاری دادهها که از درون یک فرآیند واحد به دست میآید، دسترسی داشته باشد. به طور خاص، DataStore ویژگیهای زیر را ارائه میدهد:
- عملیات خواندن فقط دادههایی را برمیگرداند که روی دیسک ذخیره شدهاند.
- سازگاری خواندن پس از نوشتن.
- نوشتنها سریالی میشوند.
- خواندنها هرگز توسط نوشتنها مسدود نمیشوند.
یک برنامهی نمونه با یک سرویس و یک اکتیویتی را در نظر بگیرید که سرویس در یک فرآیند جداگانه اجرا میشود و به صورت دورهای DataStore را بهروزرسانی میکند.
این مثال از یک پایگاه داده JSON استفاده میکند، اما شما میتوانید از Preferences یا Proto DataStore نیز استفاده کنید.
@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 را با استفاده از MultiProcessDataStoreFactory برای کد برنامه و سرویس بسازید:
val dataStore = MultiProcessDataStoreFactory.create(
serializer = TimeSerializer,
produceFile = {
File("${context.filesDir.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 یک API برای مدیریت خرابی ارائه میدهد که میتواند به شما در بازیابی صحیح در چنین سناریویی کمک کند و از بروز خطا جلوگیری کند. پس از پیکربندی، مدیریت خرابی، فایل خراب را با فایل جدیدی که حاوی مقدار پیشفرض از پیش تعریفشده است، جایگزین میکند.
برای تنظیم این هندلر، هنگام ایجاد نمونه DataStore در by dataStore یا در متد factory DataStoreFactory ، یک corruptionHandler ارائه دهید:
val dataStore: DataStore<Settings> = DataStoreFactory.create(
serializer = SettingsSerializer(),
produceFile = {
File("${context.filesDir.path}/myapp.preferences_pb")
},
corruptionHandler = ReplaceFileCorruptionHandler { Settings(lastUpdate = 0) }
)
ارائه بازخورد
نظرات و ایدههای خود را از طریق این منابع با ما در میان بگذارید:
- ردیاب مشکلات :
- مشکلات را گزارش دهید تا بتوانیم اشکالات را برطرف کنیم.
منابع اضافی
برای کسب اطلاعات بیشتر در مورد Jetpack DataStore، به منابع اضافی زیر مراجعه کنید:
نمونهها
وبلاگها
کدلبز
{% کلمه به کلمه %}برای شما توصیه میشود
- توجه: متن لینک زمانی نمایش داده میشود که جاوا اسکریپت غیرفعال باشد.
- بارگذاری و نمایش دادههای صفحهبندیشده
- مرور کلی LiveData
- طرحبندیها و عبارات اتصال
