DataStore: چیست، مبانی ذخیره‌سازی داده و جایگزین SharedPreferences

نویسنده: IT Sectr منتشر شده: 2026-03-12 زمان مطالعه: 9 دقیقه

DataStore — مؤلفه‌ای از کتابخانه Jetpack است که برای ذخیره‌سازی حجم‌های کوچک داده در برنامه‌های Android طراحی شده است. برخلاف SharedPreferences، به صورت ناهمگام کار می‌کند و一致性 داده‌ها را در دسترسی همزمان تضمین می‌کند. طبق داده‌های Google, 2024، DataStore از Kotlin Coroutines و Flow استفاده می‌کند که آن را برای نخ اصلی ایمن و برای معماری‌های واکنشی مناسب می‌سازد.

نکات کلیدی

  • DataStore — جایگزین SharedPreferences با API ناهمگام و انواع داده از طریق Protocol Buffers
  • Preferences DataStore — Key-Store ساده با خواندن از طریق Flow و تراکنش‌ها
  • Proto DataStore — مخزن تایپ‌شده با مهاجرت‌های خودکار schema
  • SharedPreferences — API همگام که نخ UI را در حجم‌های زیاد مسدود می‌کند
  • مهاجرت از طریق رابط SharedPreferencesMigration بدون از دست دادن داده انجام می‌شود

DataStore چیست؟

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

در هسته DataStore SingleProcessDataStore قرار دارد — پیاده‌سازی که در چارچوب یک فرآیند کار می‌کند. این از ذخیره‌سازی فایل با قفل‌های سطح فایل استفاده می‌کند: هنگام نوشتن داده، فایل قفل می‌شود که از خرابی در دسترسی همزمان جلوگیری می‌کند.

DataStore خودکار خطاهای غیرسریال‌سازی را مدیریت می‌کند: اگر فایل خراب شده باشد، مقدار پیش‌فرض را برمی‌گرداند و فایل را بازنویسی می‌کند. این رفتار از طریق corruptionHandler که می‌توان در ایجاد DataStore تنظیم کرد، پیکربندی می‌شود.

مشکلات SharedPreferences که DataStore حل می‌کند

SharedPreferences از سه مشکل اساسی رنج می‌برد: خواندن همگام از دیسک در نخ اصلی، عدم تضمین اتمیک بودن در نوشتن‌های همزمان و عدم امکان ردیابی واکنشی تغییرات. DataStore هر سه را حل می‌کند: Flow برای مشاهده، قفل فایل برای اتمیک بودن و API ناهمگام برای ایمنی نخ‌ها.

DataStore در Android چگونه کار می‌کند؟

DataStore داده‌ها را در فایل‌های حافظه داخلی دستگاه ذخیره می‌کند. Preferences DataStore از فرمت فایل مشابه SharedPreferences استفاده می‌کند اما با فراداده‌های اضافی برای بررسی صحت. Proto DataStore از فرمت باینری Protocol Buffers استفاده می‌کند که اندازه فایل را کاهش داده و سریال‌سازی را تسریع می‌کند.

هنگام خواندن داده، DataStore کل فایل را یک بار در حافظه بارگذاری می‌کند و پس از آن مشترکین وضعیت فعلی را از طریق Flow دریافت می‌کنند. تغییرات به طور خودکار به تمام مشترکین فعال منتقل می‌شوند — برخلاف SharedPreferences نیازی به ثبت دستی listenerها نیست.

اصل کار Preferences DataStore

Preferences DataStore از مکانیزم سریال‌سازی داخلی مبتنی بر Map استفاده می‌کند. هر ورودی یک جفت رشته و نوع اولیه (Int, Boolean, Float, Long, String, Set) است. داده‌ها در فایل XML مشابه SharedPreferences ذخیره می‌شوند اما با نوشتن اتمیک از طریق قفل فایل.

مثال ایجاد Preferences DataStore: افزونه preferencesDataStore روی Context یک singleton با نام فایل ایجاد می‌کند. در فراخوانی‌های مکرر همان نمونه برگردانده می‌شود — این کار تکثیر فایل‌ها و سردرگمی با نمونه‌های مختلف مخزن را از بین می‌برد.

اصل کار Proto DataStore

Proto DataStore نیاز به تعریف schema داده از طریق فایل .proto و کامپایل با افزونه protobuf دارد. کلاس تولیدشده Java به عنوان تنها نقطه ورود برای تمام فیلدها استفاده می‌شود — این کار اشتباهات تایپی در کلیدها که برای SharedPreferences معمول است را از بین می‌برد.

Schema Proto DataStore یک بار تعریف می‌شود و افزودن فیلدهای جدید را بدون از دست دادن داده‌های قدیمی پشتیبانی می‌کند. اگر در نسخه جدید برنامه فیلدی با مقدار پیش‌فرض اضافه شود، فایل قدیمی به درستی غیرسریال‌سازی می‌شود — سازگاری با عقب در پروتکل تعبیه شده است.

Preferences DataStore و Proto DataStore: مقایسه

انتخاب بین Preferences DataStore و Proto DataStore به پیچیدگی داده و نیازمندی‌های تایپ‌سازی بستگی دارد. هر دو گزینه ناهمگام و تراکنشی هستند اما در سطح type-safety و عملکرد سریال‌سازی تفاوت دارند.

ویژگیPreferences DataStoreProto DataStore
تایپ‌سازیضعیف (کلید-مقدار)قوی (کلاس تولیدشده)
سریال‌سازیXML (داخلی)Protocol Buffers (protobuf)
اندازه فایلبزرگ (XML قابل خواندن)کوچک (باینری)
پیچیدگیکم (بدون .proto)متوسط (نیاز به .proto)
مهاجرت schemaبدون schemaخودکار (proto)
سازگاریSharedPreferences (از طریق مهاجرت)فقط Proto DataStore

چه زمانی Preferences DataStore را انتخاب کنیم

Preferences DataStore برای تنظیمات ساده مناسب است: پرچم‌های فعال‌سازی ویژگی‌ها، رشته توکن احراز هویت، تعداد راه‌اندازی برنامه. اگر داده کم است (تا 10–15 کلید) و نیاز به schema دقیق ندارد — Preferences DataStore حداقل آستانه ورود را بدون اتصال افزونه protobuf فراهم می‌کند.

چه زمانی Proto DataStore را انتخاب کنیم

Proto DataStore زمانی توجیه‌پذیر است که ساختار داده پیچیده است یا ممکن است بین نسخه‌های برنامه تغییر کند. مثلاً تنظیمات پروفایل کاربر یا پیکربندی تست A/B با 20+ فیلد. Protobuf تایپ‌سازی قوی و مهاجرت‌های خودکار را فراهم می‌کند که خطاهای زمان اجرا به دلیل ناهماهنگی کلیدها را از بین می‌برد.

چگونه از SharedPreferences به DataStore مهاجرت کنیم

Google مکانیزم مهاجرت داخلی را از طریق کلاس SharedPreferencesMigration فراهم می‌کند. مهاجرت یک بار در اولین راه‌اندازی پس از به‌روزرسانی برنامه انجام می‌شود: DataStore داده‌ها را از SharedPreferences می‌خواند، در قالب خود می‌نویسد و مهاجرت را به عنوان تکمیل‌شده علامت‌گذاری می‌کند.

مهاجرت از تبدیل‌های سفارشی پشتیبانی می‌کند: اگر در SharedPreferences کلیدها با کلیدهای مورد نظر DataStore مطابقت ندارند، می‌توان تابع تبدیل را از طریق SharedPreferencesMigration تعریف کرد. این امکان تغییر نام کلیدها و تغییر انواع داده را در فرآیند مهاجرت فراهم می‌کند.

مهاجرت گام به گام

گام اول: DataStore را به build.gradle اضافه کنید و نمونه DataStore را با مهاجرت ایجاد کنید: SharedPreferencesMigration نام فایل SharedPreferences و مجموعه کلیدهایی که باید منتقل شوند را دریافت می‌کند. گام دوم: تمام کدی که از طریق SharedPreferences کار می‌کند را حذف کرده و با فراخوانی‌های DataStore جایگزین کنید. گام سوم — مهاجرت را تست کنید: در اولین راه‌اندازی داده‌ها باید در DataStore ظاهر شوند و فایل SharedPreferences قدیمی دیگر استفاده نشود.

kotlin
val Context.dataStore by preferencesDataStore(
    name = "settings",
    produceMigrations = { context ->
        listOf(
            SharedPreferencesMigration(context, "old_prefs")
        )
    }
)

نمونه‌های استفاده از DataStore در کد

DataStore به راحتی در پروژه موجود ادغام می‌شود. در زیر نمونه‌های عملی برای Preferences DataStore و Proto DataStore آورده شده است — هر دو خواندن، نوشتن و مشاهده واکنشی داده را نشان می‌دهند.

Preferences DataStore: خواندن و نوشتن تنظیمات

در این مثال Preferences DataStore سه تنظیم را ذخیره می‌کند: تم تیره، نام کاربری و تعداد راه‌اندازی‌ها. خواندن از طریق افزونه .data که Flow برمی‌گرداند انجام می‌شود. نوشتن — از طریق تابع suspend .edit که اتمیک بودن تغییرات را تضمین می‌کند.

kotlin
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: schema و استفاده

Proto DataStore نیاز به تعریف فایل .proto دارد. پس از کامپایل، کلاس UserSettings ایجاد می‌شود که برای خواندن و نوشتن استفاده می‌شود. مهاجرت‌های نسخه‌های schema در همان فایل .proto توصیف شده و به طور خودکار اعمال می‌شوند.

kotlin
// 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 نیست.

kotlin
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 چه برتری‌هایی بر SharedPreferences دارد؟

DataStore به صورت ناهمگام کار می‌کند (نخ UI را مسدود نمی‌کند)، دسترسی همزمان را از طریق تراکنش‌ها پشتیبانی می‌کند و امکان اشتراک واکنشی در تغییرات از طریق Flow را فراهم می‌کند. SharedPreferences — API همگام با خطر ANR در حجم‌های زیاد داده و بدون پشتیبانی داخلی از واکنش‌گرایی.

آیا می‌توان از DataStore با Java استفاده کرد؟

DataStore به Kotlin نوشته شده و نیاز به Kotlin Coroutines دارد. استفاده از آن از Java ممکن است اما ناخوشایند است: باید wrapperهایی با CompletableFuture ایجاد کنید یا به صورت دستی coroutineها را مدیریت کنید. برای پروژه‌های Java، Google توصیه می‌کند SharedPreferences را حفظ کنید یا Kotlin را به ماژول اضافه کنید.

آیا DataStore برای ذخیره حجم‌های زیاد داده مناسب است؟

DataStore کل فایل را هنگام خواندن در حافظه بارگذاری می‌کند بنابراین برای ذخیره لیست‌ها یا اشیاء بزرگ مناسب نیست. برای چنین سناریوهایی از Room یا SQLite استفاده کنید. DataStore برای تنظیمات و داده‌های ساختاریافته کوچک — تا صدها کیلوبایت — بهینه شده است.

چگونه خطای فایل خراب DataStore را مدیریت کنیم؟

در ایجاد DataStore می‌توان corruptionHandler را ارسال کرد — تابعی که هنگام خرابی فایل فراخوانی می‌شود. به طور پیش‌فرض DataStore استثنای CorruptionException را抛出 می‌کند. در corruptionHandler می‌توان داده خالی برگرداند و پس از آن DataStore فایل را با وضعیت صحیح بازنویسی می‌کند.

آیا Proto DataStore نیاز به فایل .proto اجباری دارد؟

بله، Proto DataStore نیاز به تعریف schema در فایل .proto و اتصال افزونه protobuf-gradle-plugin دارد. اگر پروژه کوچک است و داده ساده است، استفاده از Preferences DataStore آسان‌تر است — نیازی به پیکربندی اضافی build ندارد.

خلاصه

  • DataStore — جایگزین مدرن SharedPreferences که با Kotlin Coroutines و Flow کار می‌کند
  • Preferences DataStore — کلید-مقدار ساده بدون schema، مناسب برای تنظیمات
  • Proto DataStore — مخزن تایپ‌شده با schema protobuf و مهاجرت خودکار
  • مهاجرت از SharedPreferences به DataStore از طریق SharedPreferencesMigration داخلی است
  • ایمنی نخ‌ها — همه عملیات ناهمگام هستند، مسدود شدن UI منتفی است
  • واکنش‌گرایی — Flow مشترکین را در هر تغییر داده مطلع می‌کند
  • توصیه — در تمام پروژه‌های جدید Android از DataStore استفاده کنید، پروژه‌های موجود را هنگام کار با تنظیمات مهاجرت دهید

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید