معماری برنامه: لایه داده - DataStore - توسعه دهندگان اندروید

پروژه: /architecture/_project.yaml کتاب: /architecture/_book.yaml کلمات کلیدی: datastore، معماری، api:JetpackDataStore توضیحات: این راهنمای معماری برنامه را در مورد کتابخانه‌های لایه داده بررسی کنید تا در مورد تنظیمات DataStore و Proto DataStore، تنظیمات و موارد دیگر اطلاعات کسب کنید. hide_page_heading: true

بخشی از DataStore در Android Jetpack .

با کاتلین چند پلتفرمی امتحان کنید
کاتلین چند پلتفرمی امکان اشتراک‌گذاری لایه داده با سایر پلتفرم‌ها را فراهم می‌کند. یاد بگیرید چگونه DataStore را در KMP راه‌اندازی و کار کنید.

Jetpack DataStore یک راهکار ذخیره‌سازی داده است که به شما امکان می‌دهد جفت‌های کلید-مقدار یا اشیاء تایپ‌شده را با بافرهای پروتکل ذخیره کنید. DataStore از کوروتین‌ها و Flow کاتلین برای ذخیره داده‌ها به صورت غیرهمزمان، سازگار و تراکنشی استفاده می‌کند.

اگر از SharedPreferences برای ذخیره داده‌ها استفاده می‌کنید، به جای آن، مهاجرت به DataStore را در نظر بگیرید.

API فروشگاه داده

رابط DataStore API زیر را ارائه می‌دهد:

  1. جریانی که می‌تواند برای خواندن داده‌ها از DataStore استفاده شود

    val data: Flow<T>
    
  2. تابعی برای به‌روزرسانی داده‌ها در 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.1.7"

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

کاتلین

    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، وابستگی‌های زیر را اضافه کنید:

گرووی

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

کاتلین

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

فروشگاه داده

خطوط زیر را به بخش وابستگی‌های فایل gradle خود اضافه کنید:

گرووی

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

کاتلین

    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 اضافه کنید:

گرووی

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

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

کاتلین

    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 اضافه کنید.

سریال‌سازی 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، همیشه قوانین زیر را در نظر داشته باشید:

  1. هرگز بیش از یک نمونه DataStore برای یک فایل مشخص در یک فرآیند ایجاد نکنید. انجام این کار می‌تواند تمام قابلیت‌های DataStore را از کار بیندازد. اگر چندین DataStore برای یک فایل مشخص در یک فرآیند فعال باشند، DataStore هنگام خواندن یا به‌روزرسانی داده‌ها، IllegalStateException را صادر می‌کند.

  2. نوع عمومی DataStore<T> باید تغییرناپذیر باشد. تغییر نوع استفاده شده در DataStore، ثباتی را که DataStore ارائه می‌دهد، نامعتبر می‌کند و باعث ایجاد اشکالات جدی و دشوار می‌شود. توصیه می‌کنیم از بافرهای پروتکل استفاده کنید که به تضمین تغییرناپذیری، یک API واضح و سریال‌سازی کارآمد کمک می‌کنند.

  3. برای یک فایل، از 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 راحت‌تر حفظ شود. به عنوان یک جایگزین، اگر از 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<T> استفاده کنید، که در آن T کلاس داده قابل سریال‌سازی است. آن را یک بار در سطح بالای فایل کاتلین خود فراخوانی کنید و از طریق این نماینده ویژگی در بقیه برنامه خود به آن دسترسی داشته باشید. پارامتر fileName به DataStore می‌گوید که از کدام فایل برای ذخیره داده‌ها استفاده کند و پارامتر serializer نام کلاس serializer تعریف شده در مرحله 1 را به DataStore می‌گوید.

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

فروشگاه داده پروتو

از نماینده ویژگی ایجاد شده توسط dataStore برای ایجاد نمونه‌ای از DataStore<T> استفاده کنید، که در آن T نوع تعریف شده در فایل proto است. آن را یک بار در سطح بالای فایل Kotlin خود فراخوانی کنید و از طریق این نماینده ویژگی در بقیه برنامه خود به آن دسترسی داشته باشید. پارامتر fileName به DataStore می‌گوید که از کدام فایل برای ذخیره داده‌ها استفاده کند و پارامتر serializer نام کلاس serializer تعریف شده در مرحله 1 را به DataStore می‌گوید.

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
}

نوشتن در 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 }
    }
}

نمونه نوشتن

می‌توانید این توابع را در یک کلاس قرار دهید و از آن در یک برنامه Compose استفاده کنید.

فروشگاه داده تنظیمات

اکنون می‌توانیم این توابع را در کلاسی به نام 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

اکنون می‌توانیم این توابع را در کلاسی به نام 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")
}

فروشگاه داده پروتو

اکنون می‌توانیم این توابع را در کلاسی به نام 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، API ناهمزمان است، اما ممکن است همیشه تغییر کد اطراف شما به ناهمزمان امکان‌پذیر نباشد. این ممکن است در صورتی اتفاق بیفتد که با یک پایگاه کد موجود کار می‌کنید که از ورودی/خروجی دیسک همگام استفاده می‌کند یا اگر وابستگی دارید که API ناهمزمان ارائه نمی‌دهد.

کوروتین‌های کاتلین، سازنده کوروتین runBlocking() را برای کمک به پر کردن شکاف بین کد همزمان و ناهمزمان ارائه می‌دهند. می‌توانید از runBlocking() برای خواندن داده‌ها از DataStore به صورت همزمان استفاده کنید. RxJava متدهای مسدودکننده را در Flowable ارائه می‌دهد. کد زیر نخ فراخوانی را تا زمانی که DataStore داده‌ها را برگرداند، مسدود می‌کند:

کاتلین

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

جاوا

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

انجام عملیات ورودی/خروجی همزمان در نخ رابط کاربری می‌تواند باعث بروز ANR یا عدم پاسخگویی رابط کاربری شود. می‌توانید با پیش‌بارگذاری ناهمزمان داده‌ها از DataStore، این مشکلات را کاهش دهید:

کاتلین

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

جاوا

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

به این ترتیب، DataStore به صورت غیرهمزمان داده‌ها را می‌خواند و آنها را در حافظه ذخیره می‌کند. خواندن‌های همزمان بعدی با استفاده از runBlocking() ممکن است سریع‌تر باشند یا در صورت تکمیل خواندن اولیه، از عملیات ورودی/خروجی دیسک به طور کلی جلوگیری کنند.

استفاده از DataStore در کد چند فرآیندی

شما می‌توانید DataStore را طوری پیکربندی کنید که به داده‌های یکسان در فرآیندهای مختلف با همان ویژگی‌های سازگاری داده‌ها، مانند دسترسی از درون یک فرآیند واحد، دسترسی داشته باشد. به طور خاص، DataStore موارد زیر را ارائه می‌دهد:

  • عملیات خواندن فقط داده‌هایی را که روی دیسک ذخیره شده‌اند، برمی‌گرداند.
  • سازگاری خواندن پس از نوشتن.
  • نوشتن‌ها سریالی می‌شوند.
  • خواندن‌ها هرگز توسط نوشتن‌ها مسدود نمی‌شوند.

یک برنامه‌ی نمونه با یک سرویس و یک اکتیویتی را در نظر بگیرید که سرویس در یک فرآیند جداگانه اجرا می‌شود و به صورت دوره‌ای DataStore را به‌روزرسانی می‌کند.

این مثال از یک پایگاه داده JSON استفاده می‌کند، اما شما می‌توانید از یک پایگاه داده preferences یا proto نیز استفاده کنید.

@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.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 یک API برای مدیریت خرابی ارائه می‌دهد که می‌تواند به شما در بازیابی صحیح در چنین سناریویی کمک کند و از بروز خطا جلوگیری کند. پس از پیکربندی، مدیریت خرابی، فایل خراب را با فایل جدیدی که حاوی مقدار پیش‌فرض از پیش تعریف‌شده است، جایگزین می‌کند.

برای تنظیم این هندلر، هنگام ایجاد نمونه DataStore در by dataStore() یا در متد factory 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، به منابع اضافی زیر مراجعه کنید:

نمونه‌ها

وبلاگ‌ها

کدلبز

{% کلمه به کلمه %} {% فعل کمکی %} {% کلمه به کلمه %} {% فعل کمکی %}