DataStore — مؤلفهای از کتابخانه Jetpack است که برای ذخیرهسازی حجمهای کوچک داده در برنامههای Android طراحی شده است. برخلاف SharedPreferences، به صورت ناهمگام کار میکند و一致性 دادهها را در دسترسی همزمان تضمین میکند. طبق دادههای Google, 2024، DataStore از Kotlin Coroutines و Flow استفاده میکند که آن را برای نخ اصلی ایمن و برای معماریهای واکنشی مناسب میسازد.
نکات کلیدی
DataStore — راهحلی از Google برای ذخیرهسازی محلی داده در Android است که در سال 2020 به عنوان جایگزینی برای SharedPreferences معرفی شد. این دو حالت را پشتیبانی میکند: Preferences DataStore (جفتهای کلید-مقدار ساده) و Proto DataStore (schema تایپشده بر اساس Protocol Buffers).
مزیت اصلی — ناهمگامی کامل: تمام عملیات خواندن Flow را از Kotlin Coroutines برمیگردانند و نوشتن در زمینه coroutine انجام میشود. این کار مسدود شدن نخ اصلی را که مشکل معمول SharedPreferences هنگام کار با حجمهای زیاد داده بود، از بین میبرد.
DataStore اتمیک بودن عملیات را تضمین میکند: نوشتنهای همزمان به دلیل مدل تراکنشی منجر به از دست دادن داده نمیشوند. اگر دو مؤلفه به طور همزمان یک مقدار را تغییر دهند، DataStore از طریق مکانیزم compare-and-swap به درستی تعارض را مدیریت میکند.
طبق دادههای Google I/O 2023، DataStore در 40% پروژههای جدید Android استفاده میشود و Google مهاجرت از SharedPreferences را در تمام برنامههایی که نیاز به پایداری ذخیرهسازی تنظیمات دارند توصیه میکند.
در هسته DataStore SingleProcessDataStore قرار دارد — پیادهسازی که در چارچوب یک فرآیند کار میکند. این از ذخیرهسازی فایل با قفلهای سطح فایل استفاده میکند: هنگام نوشتن داده، فایل قفل میشود که از خرابی در دسترسی همزمان جلوگیری میکند.
DataStore خودکار خطاهای غیرسریالسازی را مدیریت میکند: اگر فایل خراب شده باشد، مقدار پیشفرض را برمیگرداند و فایل را بازنویسی میکند. این رفتار از طریق corruptionHandler که میتوان در ایجاد DataStore تنظیم کرد، پیکربندی میشود.
SharedPreferences از سه مشکل اساسی رنج میبرد: خواندن همگام از دیسک در نخ اصلی، عدم تضمین اتمیک بودن در نوشتنهای همزمان و عدم امکان ردیابی واکنشی تغییرات. DataStore هر سه را حل میکند: Flow برای مشاهده، قفل فایل برای اتمیک بودن و API ناهمگام برای ایمنی نخها.
DataStore دادهها را در فایلهای حافظه داخلی دستگاه ذخیره میکند. Preferences DataStore از فرمت فایل مشابه SharedPreferences استفاده میکند اما با فرادادههای اضافی برای بررسی صحت. Proto DataStore از فرمت باینری Protocol Buffers استفاده میکند که اندازه فایل را کاهش داده و سریالسازی را تسریع میکند.
هنگام خواندن داده، DataStore کل فایل را یک بار در حافظه بارگذاری میکند و پس از آن مشترکین وضعیت فعلی را از طریق Flow دریافت میکنند. تغییرات به طور خودکار به تمام مشترکین فعال منتقل میشوند — برخلاف SharedPreferences نیازی به ثبت دستی listenerها نیست.
Preferences DataStore از مکانیزم سریالسازی داخلی مبتنی بر Map استفاده میکند. هر ورودی یک جفت رشته و نوع اولیه (Int, Boolean, Float, Long, String, Set) است. دادهها در فایل XML مشابه SharedPreferences ذخیره میشوند اما با نوشتن اتمیک از طریق قفل فایل.
مثال ایجاد Preferences DataStore: افزونه preferencesDataStore روی Context یک singleton با نام فایل ایجاد میکند. در فراخوانیهای مکرر همان نمونه برگردانده میشود — این کار تکثیر فایلها و سردرگمی با نمونههای مختلف مخزن را از بین میبرد.
Proto DataStore نیاز به تعریف schema داده از طریق فایل .proto و کامپایل با افزونه protobuf دارد. کلاس تولیدشده Java به عنوان تنها نقطه ورود برای تمام فیلدها استفاده میشود — این کار اشتباهات تایپی در کلیدها که برای SharedPreferences معمول است را از بین میبرد.
Schema Proto DataStore یک بار تعریف میشود و افزودن فیلدهای جدید را بدون از دست دادن دادههای قدیمی پشتیبانی میکند. اگر در نسخه جدید برنامه فیلدی با مقدار پیشفرض اضافه شود، فایل قدیمی به درستی غیرسریالسازی میشود — سازگاری با عقب در پروتکل تعبیه شده است.
انتخاب بین Preferences DataStore و Proto DataStore به پیچیدگی داده و نیازمندیهای تایپسازی بستگی دارد. هر دو گزینه ناهمگام و تراکنشی هستند اما در سطح type-safety و عملکرد سریالسازی تفاوت دارند.
| ویژگی | Preferences DataStore | Proto DataStore |
|---|---|---|
| تایپسازی | ضعیف (کلید-مقدار) | قوی (کلاس تولیدشده) |
| سریالسازی | XML (داخلی) | Protocol Buffers (protobuf) |
| اندازه فایل | بزرگ (XML قابل خواندن) | کوچک (باینری) |
| پیچیدگی | کم (بدون .proto) | متوسط (نیاز به .proto) |
| مهاجرت schema | بدون schema | خودکار (proto) |
| سازگاری | SharedPreferences (از طریق مهاجرت) | فقط Proto DataStore |
Preferences DataStore برای تنظیمات ساده مناسب است: پرچمهای فعالسازی ویژگیها، رشته توکن احراز هویت، تعداد راهاندازی برنامه. اگر داده کم است (تا 10–15 کلید) و نیاز به schema دقیق ندارد — Preferences DataStore حداقل آستانه ورود را بدون اتصال افزونه protobuf فراهم میکند.
Proto DataStore زمانی توجیهپذیر است که ساختار داده پیچیده است یا ممکن است بین نسخههای برنامه تغییر کند. مثلاً تنظیمات پروفایل کاربر یا پیکربندی تست A/B با 20+ فیلد. Protobuf تایپسازی قوی و مهاجرتهای خودکار را فراهم میکند که خطاهای زمان اجرا به دلیل ناهماهنگی کلیدها را از بین میبرد.
Google مکانیزم مهاجرت داخلی را از طریق کلاس SharedPreferencesMigration فراهم میکند. مهاجرت یک بار در اولین راهاندازی پس از بهروزرسانی برنامه انجام میشود: DataStore دادهها را از SharedPreferences میخواند، در قالب خود مینویسد و مهاجرت را به عنوان تکمیلشده علامتگذاری میکند.
مهاجرت از تبدیلهای سفارشی پشتیبانی میکند: اگر در SharedPreferences کلیدها با کلیدهای مورد نظر DataStore مطابقت ندارند، میتوان تابع تبدیل را از طریق SharedPreferencesMigration تعریف کرد. این امکان تغییر نام کلیدها و تغییر انواع داده را در فرآیند مهاجرت فراهم میکند.
گام اول: DataStore را به build.gradle اضافه کنید و نمونه DataStore را با مهاجرت ایجاد کنید: SharedPreferencesMigration نام فایل SharedPreferences و مجموعه کلیدهایی که باید منتقل شوند را دریافت میکند. گام دوم: تمام کدی که از طریق SharedPreferences کار میکند را حذف کرده و با فراخوانیهای DataStore جایگزین کنید. گام سوم — مهاجرت را تست کنید: در اولین راهاندازی دادهها باید در DataStore ظاهر شوند و فایل SharedPreferences قدیمی دیگر استفاده نشود.
val Context.dataStore by preferencesDataStore(
name = "settings",
produceMigrations = { context ->
listOf(
SharedPreferencesMigration(context, "old_prefs")
)
}
)
DataStore به راحتی در پروژه موجود ادغام میشود. در زیر نمونههای عملی برای Preferences DataStore و Proto DataStore آورده شده است — هر دو خواندن، نوشتن و مشاهده واکنشی داده را نشان میدهند.
در این مثال Preferences DataStore سه تنظیم را ذخیره میکند: تم تیره، نام کاربری و تعداد راهاندازیها. خواندن از طریق افزونه .data که Flow برمیگرداند انجام میشود. نوشتن — از طریق تابع suspend .edit که اتمیک بودن تغییرات را تضمین میکند.
val Context.settingsDataStore by preferencesDataStore(name = "settings")
val isDarkMode: Flow<Boolean> = settingsDataStore.data
.map { preferences ->
preferences[booleanPreferencesKey("dark_mode")] ?: false
}
suspend fun toggleDarkMode() {
settingsDataStore.edit { prefs ->
val current = prefs[booleanPreferencesKey("dark_mode")] ?: false
prefs[booleanPreferencesKey("dark_mode")] = !current
}
}
Proto DataStore نیاز به تعریف فایل .proto دارد. پس از کامپایل، کلاس UserSettings ایجاد میشود که برای خواندن و نوشتن استفاده میشود. مهاجرتهای نسخههای schema در همان فایل .proto توصیف شده و به طور خودکار اعمال میشوند.
// user_preferences.proto
syntax = "proto3";
message UserPreferences {
string display_name = 1;
int32 notification_count = 2;
bool notifications_enabled = 3;
}
// خواندن از DataStore
val userPreferencesFlow: Flow<UserPreferences> =
protoDataStore.data
// نوشتن مقادیر جدید
suspend fun updateDisplayName(name: String) {
protoDataStore.updateData { prefs ->
prefs.toBuilder()
.setDisplayName(name)
.build()
}
}
DataStore با معماری MVVM از طریق ViewModel ادغام میشود. Flow از DataStore از طریق .stateIn جمعآوری شده و در UI استفاده میشود. با هر تغییر داده، UI به طور خودکار بهروزرسانی میشود — نیازی به بهروزرسانی دستی یا LiveData نیست.
class SettingsViewModel(
private val dataStore: DataStore<Preferences>
) : ViewModel() {
val uiState: StateFlow<SettingsUiState> =
dataStore.data
.map { prefs ->
SettingsUiState(
isDarkMode = prefs[booleanPreferencesKey("dark_mode")] ?: false,
counter = prefs[intPreferencesKey("launch_count")] ?: 0
)
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = SettingsUiState()
)
}
سوالات متداول
DataStore به صورت ناهمگام کار میکند (نخ UI را مسدود نمیکند)، دسترسی همزمان را از طریق تراکنشها پشتیبانی میکند و امکان اشتراک واکنشی در تغییرات از طریق Flow را فراهم میکند. SharedPreferences — API همگام با خطر ANR در حجمهای زیاد داده و بدون پشتیبانی داخلی از واکنشگرایی.
DataStore به Kotlin نوشته شده و نیاز به Kotlin Coroutines دارد. استفاده از آن از Java ممکن است اما ناخوشایند است: باید wrapperهایی با CompletableFuture ایجاد کنید یا به صورت دستی coroutineها را مدیریت کنید. برای پروژههای Java، Google توصیه میکند SharedPreferences را حفظ کنید یا Kotlin را به ماژول اضافه کنید.
DataStore کل فایل را هنگام خواندن در حافظه بارگذاری میکند بنابراین برای ذخیره لیستها یا اشیاء بزرگ مناسب نیست. برای چنین سناریوهایی از Room یا SQLite استفاده کنید. DataStore برای تنظیمات و دادههای ساختاریافته کوچک — تا صدها کیلوبایت — بهینه شده است.
در ایجاد DataStore میتوان corruptionHandler را ارسال کرد — تابعی که هنگام خرابی فایل فراخوانی میشود. به طور پیشفرض DataStore استثنای CorruptionException را抛出 میکند. در corruptionHandler میتوان داده خالی برگرداند و پس از آن DataStore فایل را با وضعیت صحیح بازنویسی میکند.
بله، Proto DataStore نیاز به تعریف schema در فایل .proto و اتصال افزونه protobuf-gradle-plugin دارد. اگر پروژه کوچک است و داده ساده است، استفاده از Preferences DataStore آسانتر است — نیازی به پیکربندی اضافی build ندارد.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید