farzad mansoori
farzad mansoori
خواندن ۶ دقیقه·۳ سال پیش

Jetpack DataStore جایگزین مناسبی برای SharedPreferences




وقتی به راه حلی برای ذخیره اطلاعات در اندروید فکر میکنیم در اکثر مواقع عبارت SharedPreferences به ذهنمان میآید. یک راه حل بسیارساده و سریع برای رفع نیازمان اما اگر به درستی مدیریت نشود ممکن است باعث بروز مشکلات بسیار زیادی در برنامه شود دقیقا هنگامی که برنامه شما از نظر اندازه و پیچیدگی افزایش می یابد، حل آن می تواند بسیار دشوار باشد.

حالا Jetpack DataStore آمده تا یک جایگزین بسیار مناسب به جای SharedPreferences باشد و خیلی از مشکلات ما را برطرف کند.

What is DataStore

ـDataStore یک راه حل جدید و بهبود داده شده برای ذخیره اطلاعات میباشد که با هدف جایگزینی برای SharedPreferences توسعه داده شده است. بر روی coroutin و flow ساخته شده و دارای ۲ روش پیاده سازی میباشد( Proto DataStore وPreferences DataStore) .داده ها به صورت ناهمزمان، پیوسته و به صورت تراکنشی ذخیره می شوند و بر برخی از معایب SharedPreferences غلبه می کنند

What you'll learn

  • ـDataStore چیست و دلیل استفاده ازش چیه ؟
  • چگونگی اضافه نمودن DataStore در پروژه
  • تفاوت بین Preferences DataStore وProto DataStore و مزیت های هر کدام
  • نحوه استفاده از Proto DataStore
  • نحوه مهاجرت از SharedPreferences به Proto DataStore

DataStore

گاهی اوقات شما متوجه نیاز خود به ذخیره اطلاعات کوچک یا ساده میشوید در گذشته ممکن بوده شما از SharedPreferences استفاده کنید اما این API چندین مشکل جدی را دارا هستش.

هدف کتابخانه Jetpack DataStore برطرف نمودن این مشکلات- پیاده سازی راحتتر-امن تر و API ناهمزمان برای ذخیره اطلاعات میباشد.

دارای ۲ نوع مختلف پیاده‌سازی میباشد

  • Preferences DataStore
  • Proto DataStore


ـSharedPreferences (1 دارای API همزمان میباشد که به نظر میرسد برای صدا زده شدن درUI thread امن باشد.

اما در واقع عملیات I/O دیسک را انجام میدهد.علاوه بر این متد ()apply متد ()fsync درUI thread را بلاک میکند.

متدهای معلق مانده ()fsync در هر زمان که هر سرویس start/stop یا هر اکتیوتی start/stop میشود دوباره راه اندازی میشود.

2)ـSharedPreferences خطاهای parsing را به عنوان استثناهای زمان اجرا فرض میکند .

Preferences vs Proto DataStore

  • ـPreference DataStore : شبیه به SharedPreferences میباشد و روش پایه‌ی برای دسترسی به دیتا بر مبنای کلید میباشد(بدون تعریف schema)
  • ـProto DataStore : تعریف schema با استفاده از Protocol buffers استفاده از آن اجازه دسترسی به strongly typed data را میدهد. آنها سریعتر، کوچکتر، ساده تر و کمتر از XML و سایر فرمت های داده مبهم هستند. در حالی که Proto DataStore شما را ملزم به یادگیری یک مکانیسم سریال سازی جدید می کند.

Proto DataStore

یکی از نقاط ضعف SharedPreferences و Preferences DataStore این است که راهی برای تعریف schema یا اطمینان از دسترسی به کلیدها با نوع صحیح وجود ندارد. Proto DataStore این مشکل را با استفاده از Protocol buffers برای تعریف schema برطرف نموده

استفاده از protosDataStore میداند که کدام نوع متغییر ذخیره شده و فقط آنها را تولید میکند و استفاده از کلید را حذف کرده است.

Adding dependencies


plugins { ... id &quotcom.google.protobuf&quot version &quot0.8.17&quot } dependencies { implementation &quotandroidx.datastore:datastore-core:1.0.0&quot implementation &quotcom.google.protobuf:protobuf-javalite:3.18.0&quot implementation &quotandroidx.datastore:datastore-preferences:1.0.0&quot ... } protobuf { protoc { artifact = &quotcom.google.protobuf:protoc:3.14.0&quot } // Generates the java Protobuf-lite code for the Protobufs in this project. See // https://github.com/google/protobuf-gradle-plugin#customizing-protobuf-compilation // for more information. generateProtoTasks { all().each { task -> task.builtins { java { option 'lite' } } } } }

پروژه خود را Rebuild کنید تا فایل UserStore.java ساخته شود.

Defining and using protobuf objects

ـProtocol bufferها یک مکانیزم سریالسازی برای داده‌های ساختار یافته شده هستند.

شما فقط یکبار هر آنچه از دادهای ساختار یافته را میخواهید ایجاد میکنید و کامپایلر کدهای مورد نیاز برای خواندن و نوشتن دادها را ایجاد میکند.

Create the proto file

فایل schema را در مسیر app/src/main/User_store.proto ایجاد میکنیم(پکیج proto و فایلی با پسوند proto.)

اگر فایل مورد نظر قابل رویت نبود میتوانید با تغییر ساختار از Android به Project view آن را ایجاد کنید.

در protobufs ، هر ساختار با استفاده از یک کلمه کلیدی Message تعریف می‌شود و هر یک از اعضای ساختار بر اساس نوع و نام، درون Message تعریف می‌شوند و یک ترتیب مبتنی بر 1 به آن اختصاص می‌یابد.

proto file

syntax = &quotproto3&quot option java_package = &quotcom.codelab.android.datastore&quot option java_multiple_files = true; message UserPreferences {   // filter for showing / hiding completed tasks   bool show_completed = 1; }

این کلاس در زمان کامپایل ساخته میشود.

(به منظور راحتی کار با این زبان میتوانید از قسمت plugin →protocol buffer editor را نصب کنید)

Create the serializer

برای گفتن چگونگی خواندن و نوشتن اطلاعات در فایل Proto ایجاد شده ما نیاز به پیاده‌سازی Serializer داریم.

object UserSerializer : Serializer<UserStore> { override val defaultValue: UserStore = UserStore.getDefaultInstance() override suspend fun readFrom(input: InputStream): UserStore { try { return UserStore.parseFrom(input) } catch (exception: InvalidProtocolBufferException) { throw CorruptionException(&quotCannot read proto.&quot, exception) } } override suspend fun writeTo(t: UserStore, output: OutputStream) = t.writeTo(output) }

Creating the DataStore

برای تغییر اطلاعات و امکان انجام عملیات باید یک شی از کلاس DataStore ایجاد کنید که در ورودی آن ۲ پارامتر را دریافت میکند

  • نام فایلی که DataStore روی آن کار میکند
  • Serializer برای نوع داده مورد استفاده با DataStore
const val USER_DATA_STORE_FILE_NAME = &quotuser_store.pb&quot protected val Context.userDataStore: DataStore<UserStore> by dataStore( USER_DATA_STORE_FILE_NAME , UserSerializer )

Reading data from Proto DataStore

ـProto DataStore یک نمونه ازFlow داده‌ی ذخیره شده را ایجاد میکند

val userStoreFlow : Flow<UserStore> = ruserDataStore.data .map { it }

Handling exceptions while reading data

همانطور که DataStore داده ها را از یک فایل می خواند، IOException ها زمانی که خطایی در هنگام خواندن داده ها رخ می دهد ظاهر میشوند. ما می توانیم با استفاده از تبدیل catch Flow این موارد را مدیریت کنیم و فقط خطا را ثبت کنیم

val userStoreFlow: Flow<UserStore> = dataStore.data .catch { exception -> // dataStore.data throws an IOException when an error is encountered when reading data if (exception is IOException) { Log.e(TAG, &quotError reading sort order preferences.&quot, exception) emit(UserStore.getDefaultInstance()) } else { throw exception } }

Writing data to Proto DataStore

ـDataStore پیشنهاد میکند برای نوشتن از متد های Suspend استفاده کنیم.

fun saveProtoData(value: UserDataModel) { lifecycleScope.launch { requireContext().userDataStore.updateData { it.toBuilder() .setName(value.name) .setId(value.id) .build() } } }

SharedPreferences to Proto DataStore

Migrating from SharedPreferences

برای کمک به مهاجرت، DataStore کلاس SharedPreferencesMigration را تعریف می کند

متد by dataStore که DataStore را ایجاد می‌کند (که در TasksActivity استفاده می‌شود)، یک پارامتر productMigrations را نیز نمایش می‌دهد. در این بلاک ما لیستی از DataMigrations را ایجاد می کنیم که باید برای این نمونه DataStore اجرا شوند.

هنگام پیاده سازی SharedPreferencesMigration بلاک migrate ۲ پارامتر به ما میدهد:

  • SharedPreferencesView : اجازه بازگزدانی اطلاعات از SharedPreferences را به ما میدهد
  • UserStore : اطلاعات درست و صحیح
private val Context.userPreferencesStore: DataStore<UserStore> by dataStore( fileName = USER_DATA_STORE_FILE_NAME, serializer = UserSerializer, produceMigrations = { context -> listOf( SharedPreferencesMigration( context, USER_PREFERENCES_NAME ) { sharedPrefs: SharedPreferencesView, currentData: UserStore-> // Define the mapping from SharedPreferences to UserStore if (currentData.sortOrder == SortOrder.UNSPECIFIED) { currentData.toBuilder().setSortOrder( SortOrder.valueOf( sharedPrefs.getString(SORT_ORDER_KEY, SortOrder.NONE.name)!! ) ).build() } else { currentData } } ) } )

حالا که ما منطق مهاجرت را تعریف کرده‌ایم زمان آن رسیده که DataStore ازش استفاده کند.

برایاین کار، سازنده DataStore را به روز کنید و به پارامتر migrations یک لیست جدید که حاوی نمونه ای از SharedPreferencesMigration است اختصاص دهید.

private val dataStore: DataStore<UserStore> = context.createDataStore( fileName = &quotuser_store.pb&quot, serializer = UserSerializer, migrations = listOf(sharedPrefsMigration) )

Create a Preferences DataStore

یک شی نمونه از PreferencesDataStore ایجاد میکنیم.

ک بار آن را در سطح بالای فایل kotlin خود تماس بگیرید و از طریق این ویژگی در بقیه برنامه خود به آن دسترسی داشته باشید.

protected val Context.userPreferencesStore: DataStore<Preferences> by preferencesDataStore( AppContainer.USER_PREFERENCES_NAME)

از آنجایی که Preferences DataStore از طراحی schema استفاده نمی کند، باید از تابع نوع کلید مربوطه برای تعریف یک کلید برای هر مقداری که باید در نمونه <DataStore<Preferences ذخیره کنید استفاده کنید.

Read from a Preferences DataStore

fun readUserPreferences() { val userFlow: Flow<String> = requireContext().userPreferencesStore.data .map { preferences-> preferences[PreferencesKeys.USER_NAME].toString() } lifecycleScope.launch { userFlow.collect { Toast.makeText(requireContext(), &quotPreferences DataStore: $it&quot, Toast.LENGTH_SHORT) .show() } } }

Write to a Preferences DataStore

ـPreferences DataStore یک تابع ()edit را ارائه می دهد که به صورت تراکنشی داده ها را در یک DataStore به روزمیکند.

پارامتر تابع یک بلاک کد را می پذیرد که در آن می توانید مقادیر را در صورت نیاز به روز کنید. تمام کدهای موجود در بلاک تبدیل به عنوان یک تراکنش واحد در نظر گرفته می شود

fun saveUserPreferences(value: String){ lifecycleScope.launch { requireContext().userPreferencesStore.edit { it[PreferencesKeys.USER_NAME] = value } } }

Use DataStore in synchronous code

یکی از مزایای اصلی DataStore API ناهمزمان بودن آن است، اما ممکن است همیشه امکان تغییر کد اطراف خود به ناهمزمان وجود نداشته باشد. اگر با یک پایگاه کد موجود کار می‌کنید که ازI/O دیسک همزمان استفاده می‌کند یا اگر وابستگی دارید که یک API ناهمزمان ارائه نمی‌دهد، ممکن است این مورد صدق کند.

ـKotlin coroutines متد ()runBlocking را برای کمک به پر کردن شکاف بین کدهای همزمان و ناهمزمان ارائه می کنند. برای خواندن همزمان داده ها از DataStore می توانید از ()runBlocking استفاده کنید.

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


و در آخر میتونید در قسمت data دستگاه فایل های ذخیره رو ببینید.

میتونید کدهای مربوط با این مقاله رو در گیت هاب من مشاهده کنید .


من رو در لینکدین و اینستاگرام دنبال کنید .


Jetpack DataStoreProtocol BufferprotoDataStoreJetpackDataStorePreferencesDataStore
Android Programmer & developer
شاید از این پست‌ها خوشتان بیاید