অ্যান্ড্রয়েড জেটপ্যাকের ডেটাস্টোর অংশ।

Jetpack DataStore হল একটি ডেটা স্টোরেজ সলিউশন যা আপনাকে প্রোটোকল বাফার সহ কী-মানের জোড়া বা টাইপ করা বস্তু সংরক্ষণ করতে দেয়। ডেটাস্টোর অ্যাসিঙ্ক্রোনাস, ধারাবাহিকভাবে এবং লেনদেনগতভাবে ডেটা সঞ্চয় করতে Kotlin coroutines এবং Flow ব্যবহার করে।

আপনি যদি বর্তমানে ডেটা সঞ্চয় করতে SharedPreferences ব্যবহার করে থাকেন, তাহলে পরিবর্তে DataStore-এ স্থানান্তরিত করার কথা বিবেচনা করুন।

পছন্দসমূহ DataStore এবং Proto DataStore

DataStore দুটি ভিন্ন বাস্তবায়ন প্রদান করে: পছন্দসমূহ DataStore এবং Proto DataStore।

  • পছন্দসমূহ ডেটাস্টোর সঞ্চয় করে এবং কী ব্যবহার করে ডেটা অ্যাক্সেস করে। এই বাস্তবায়নের জন্য পূর্বনির্ধারিত স্কিমার প্রয়োজন নেই এবং এটি টাইপ নিরাপত্তা প্রদান করে না।
  • প্রোটো ডেটাস্টোর একটি কাস্টম ডেটা টাইপের উদাহরণ হিসাবে ডেটা সঞ্চয় করে। এই বাস্তবায়নের জন্য আপনাকে প্রোটোকল বাফার ব্যবহার করে একটি স্কিমা সংজ্ঞায়িত করতে হবে, কিন্তু এটি টাইপ নিরাপত্তা প্রদান করে।

সঠিকভাবে ডেটাস্টোর ব্যবহার করা

ডেটাস্টোর সঠিকভাবে ব্যবহার করার জন্য সর্বদা নিম্নলিখিত নিয়মগুলি মনে রাখবেন:

  1. একই প্রক্রিয়ায় প্রদত্ত ফাইলের জন্য DataStore একাধিক উদাহরণ তৈরি করবেন না। এটি করার ফলে সমস্ত ডেটাস্টোর কার্যকারিতা ভেঙে যেতে পারে। একই প্রক্রিয়ায় প্রদত্ত ফাইলের জন্য একাধিক ডেটাস্টোর সক্রিয় থাকলে, ডেটা পড়ার বা আপডেট করার সময় ডেটাস্টোর IllegalStateException ফেলে দেবে।

  2. ডেটাস্টোরের জেনেরিক প্রকার অপরিবর্তনীয় হতে হবে। DataStore-এ ব্যবহৃত একটি প্রকারের রূপান্তর ডাটাস্টোর প্রদান করে এবং সম্ভাব্য গুরুতর, ধরা-ছোঁয়ার বাগ তৈরি করে এমন কোনও গ্যারান্টি বাতিল করে। এটি দৃঢ়ভাবে সুপারিশ করা হয় যে আপনি প্রোটোকল বাফারগুলি ব্যবহার করুন যা অপরিবর্তনীয়তার গ্যারান্টি, একটি সাধারণ API এবং দক্ষ সিরিয়ালাইজেশন প্রদান করে।

  3. একই ফাইলের জন্য SingleProcessDataStore এবং MultiProcessDataStore এর ব্যবহার কখনই মিশ্রিত করবেন না । আপনি যদি একাধিক প্রক্রিয়া থেকে DataStore অ্যাক্সেস করতে চান তবে সর্বদা MultiProcessDataStore ব্যবহার করুন।

সেটআপ

আপনার অ্যাপে Jetpack DataStore ব্যবহার করতে, আপনি কোন বাস্তবায়ন ব্যবহার করতে চান তার উপর নির্ভর করে আপনার Gradle ফাইলে নিম্নলিখিত যোগ করুন:

পছন্দসমূহ ডেটাস্টোর

গ্রোভি

    // Preferences DataStore (SharedPreferences like APIs)
    dependencies {
        implementation "androidx.datastore:datastore-preferences:1.1.1"

        // optional - RxJava2 support
        implementation "androidx.datastore:datastore-preferences-rxjava2:1.1.1"

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

    // Alternatively - use the following artifact without an Android dependency.
    dependencies {
        implementation "androidx.datastore:datastore-preferences-core:1.1.1"
    }
    

কোটলিন

    // Preferences DataStore (SharedPreferences like APIs)
    dependencies {
        implementation("androidx.datastore:datastore-preferences:1.1.1")

        // optional - RxJava2 support
        implementation("androidx.datastore:datastore-preferences-rxjava2:1.1.1")

        // optional - RxJava3 support
        implementation("androidx.datastore:datastore-preferences-rxjava3:1.1.1")
    }

    // Alternatively - use the following artifact without an Android dependency.
    dependencies {
        implementation("androidx.datastore:datastore-preferences-core:1.1.1")
    }
    

প্রোটো ডেটাস্টোর

গ্রোভি

    // Typed DataStore (Typed API surface, such as Proto)
    dependencies {
        implementation "androidx.datastore:datastore:1.1.1"

        // optional - RxJava2 support
        implementation "androidx.datastore:datastore-rxjava2:1.1.1"

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

    // Alternatively - use the following artifact without an Android dependency.
    dependencies {
        implementation "androidx.datastore:datastore-core:1.1.1"
    }
    

কোটলিন

    // Typed DataStore (Typed API surface, such as Proto)
    dependencies {
        implementation("androidx.datastore:datastore:1.1.1")

        // optional - RxJava2 support
        implementation("androidx.datastore:datastore-rxjava2:1.1.1")

        // optional - RxJava3 support
        implementation("androidx.datastore:datastore-rxjava3:1.1.1")
    }

    // Alternatively - use the following artifact without an Android dependency.
    dependencies {
        implementation("androidx.datastore:datastore-core:1.1.1")
    }
    

পছন্দের ডেটাস্টোরের সাথে কী-মানের জোড়া সঞ্চয় করুন

পছন্দসই ডেটাস্টোর বাস্তবায়ন DataStore এবং Preferences ক্লাসগুলিকে ডিস্কে সাধারণ কী-মান জোড়া বজায় রাখতে ব্যবহার করে।

একটি পছন্দ ডেটাস্টোর তৈরি করুন

Datastore<Preferences> এর একটি উদাহরণ তৈরি করতে preferencesDataStore দ্বারা তৈরি সম্পত্তি প্রতিনিধি ব্যবহার করুন। আপনার কোটলিন ফাইলের শীর্ষ স্তরে একবার কল করুন এবং আপনার অ্যাপ্লিকেশনের বাকি অংশ জুড়ে এই সম্পত্তির মাধ্যমে এটি অ্যাক্সেস করুন৷ এটি আপনার DataStore সিঙ্গলটন হিসাবে রাখা সহজ করে তোলে। বিকল্পভাবে, আপনি যদি RxJava ব্যবহার করেন তাহলে RxPreferenceDataStoreBuilder ব্যবহার করুন। বাধ্যতামূলক name প্যারামিটার হল পছন্দ ডেটাস্টোরের নাম।

কোটলিন

// At the top level of your kotlin file:
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")

জাভা

RxDataStore<Preferences> dataStore =
  new RxPreferenceDataStoreBuilder(context, /*name=*/ "settings").build();

একটি পছন্দ ডেটাস্টোর থেকে পড়ুন

যেহেতু পছন্দগুলি ডেটাস্টোর একটি পূর্বনির্ধারিত স্কিমা ব্যবহার করে না, তাই আপনাকে DataStore<Preferences> দৃষ্টান্তে সংরক্ষণ করতে হবে এমন প্রতিটি মানের জন্য একটি কী নির্ধারণ করতে আপনাকে অবশ্যই সংশ্লিষ্ট কী প্রকার ফাংশন ব্যবহার করতে হবে। উদাহরণস্বরূপ, একটি int মানের জন্য একটি কী নির্ধারণ করতে, intPreferencesKey() ব্যবহার করুন। তারপর, একটি Flow ব্যবহার করে উপযুক্ত সঞ্চিত মান প্রকাশ করতে DataStore.data বৈশিষ্ট্য ব্যবহার করুন।

কোটলিন

val EXAMPLE_COUNTER = intPreferencesKey("example_counter")
val exampleCounterFlow: Flow<Int> = context.dataStore.data
  .map { preferences ->
    // No type safety.
    preferences[EXAMPLE_COUNTER] ?: 0
}

জাভা

Preferences.Key<Integer> EXAMPLE_COUNTER = PreferencesKeys.int("example_counter");

Flowable<Integer> exampleCounterFlow =
  dataStore.data().map(prefs -> prefs.get(EXAMPLE_COUNTER));

একটি পছন্দসই ডেটাস্টোরে লিখুন

পছন্দসমূহ ডেটাস্টোর একটি edit() ফাংশন প্রদান করে যা লেনদেনগতভাবে DataStore ডেটা আপডেট করে। ফাংশনের transform প্যারামিটার কোডের একটি ব্লক গ্রহণ করে যেখানে আপনি প্রয়োজন অনুযায়ী মান আপডেট করতে পারেন। ট্রান্সফর্ম ব্লকের সমস্ত কোড একটি একক লেনদেন হিসাবে বিবেচিত হয়।

কোটলিন

suspend fun incrementCounter() {
  context.dataStore.edit { settings ->
    val currentCounterValue = settings[EXAMPLE_COUNTER] ?: 0
    settings[EXAMPLE_COUNTER] = currentCounterValue + 1
  }
}

জাভা

Single<Preferences> updateResult =  dataStore.updateDataAsync(prefsIn -> {
  MutablePreferences mutablePreferences = prefsIn.toMutablePreferences();
  Integer currentInt = prefsIn.get(INTEGER_KEY);
  mutablePreferences.set(INTEGER_KEY, currentInt != null ? currentInt + 1 : 1);
  return Single.just(mutablePreferences);
});
// The update is completed once updateResult is completed.

প্রোটো ডেটাস্টোর দিয়ে টাইপ করা বস্তু সংরক্ষণ করুন

প্রোটো ডেটাস্টোর বাস্তবায়ন ডেটাস্টোর এবং প্রোটোকল বাফার ব্যবহার করে টাইপ করা বস্তুগুলিকে ডিস্কে বজায় রাখতে।

একটি স্কিমা সংজ্ঞায়িত করুন

Proto DataStore app/src/main/proto/ ডিরেক্টরিতে একটি প্রোটো ফাইলে একটি পূর্বনির্ধারিত স্কিমা প্রয়োজন। এই স্কিমাটি আপনার প্রোটো ডেটাস্টোরে থাকা বস্তুর ধরন নির্ধারণ করে। একটি প্রোটো স্কিমা সংজ্ঞায়িত করার বিষয়ে আরও জানতে, protobuf ভাষা নির্দেশিকা দেখুন।

syntax = "proto3";

option java_package = "com.example.application";
option java_multiple_files = true;

message Settings {
  int32 example_counter = 1;
}

একটি প্রোটো ডেটাস্টোর তৈরি করুন

আপনার টাইপ করা বস্তু সঞ্চয় করার জন্য একটি প্রোটো ডেটাস্টোর তৈরিতে দুটি ধাপ জড়িত:

  1. একটি ক্লাস সংজ্ঞায়িত করুন যা Serializer<T> প্রয়োগ করে, যেখানে T হল প্রোটো ফাইলে সংজ্ঞায়িত প্রকার। এই সিরিয়ালাইজার ক্লাস ডেটাস্টোরকে বলে যে কীভাবে আপনার ডেটা টাইপ পড়তে এবং লিখতে হয়। নিশ্চিত করুন যে আপনি সিরিয়ালাইজার ব্যবহার করার জন্য একটি ডিফল্ট মান অন্তর্ভুক্ত করেছেন যদি এখনও কোনো ফাইল তৈরি না হয়।
  2. DataStore<T> এর একটি উদাহরণ তৈরি করতে dataStore দ্বারা নির্মিত সম্পত্তি প্রতিনিধি ব্যবহার করুন, যেখানে T হল প্রোটো ফাইলে সংজ্ঞায়িত প্রকার। আপনার kotlin ফাইলের শীর্ষ স্তরে একবার এটি কল করুন এবং আপনার অ্যাপের বাকি অংশ জুড়ে এই সম্পত্তি প্রতিনিধির মাধ্যমে এটি অ্যাক্সেস করুন। filename প্যারামিটার ডেটাস্টোরকে বলে যে ডেটা সঞ্চয় করতে কোন ফাইলটি ব্যবহার করতে হবে এবং serializer প্যারামিটার ডেটাস্টোরকে ধাপ 1 এ সংজ্ঞায়িত সিরিয়ালাইজার শ্রেণীর নাম বলে।

কোটলিন

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) = t.writeTo(output)
}

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

জাভা

private static class SettingsSerializer implements Serializer<Settings> {
  @Override
  public Settings getDefaultValue() {
    Settings.getDefaultInstance();
  }

  @Override
  public Settings readFrom(@NotNull InputStream input) {
    try {
      return Settings.parseFrom(input);
    } catch (exception: InvalidProtocolBufferException) {
      throw CorruptionException(“Cannot read proto.”, exception);
    }
  }

  @Override
  public void writeTo(Settings t, @NotNull OutputStream output) {
    t.writeTo(output);
  }
}

RxDataStore<Byte> dataStore =
    new RxDataStoreBuilder<Byte>(context, /* fileName= */ "settings.pb", new SettingsSerializer()).build();

একটি প্রোটো ডেটাস্টোর থেকে পড়ুন

আপনার সঞ্চিত বস্তু থেকে উপযুক্ত সম্পত্তির একটি Flow প্রকাশ করতে DataStore.data ব্যবহার করুন।

কোটলিন

val exampleCounterFlow: Flow<Int> = context.settingsDataStore.data
  .map { settings ->
    // The exampleCounter property is generated from the proto schema.
    settings.exampleCounter
  }

জাভা

Flowable<Integer> exampleCounterFlow =
  dataStore.data().map(settings -> settings.getExampleCounter());

একটি প্রোটো ডেটাস্টোরে লিখুন

প্রোটো ডেটাস্টোর একটি updateData() ফাংশন প্রদান করে যা লেনদেনভাবে একটি সঞ্চিত বস্তুকে আপডেট করে। updateData() আপনাকে আপনার ডেটা টাইপের উদাহরণ হিসাবে ডেটার বর্তমান অবস্থা দেয় এবং একটি পারমাণবিক রিড-রাইট-মোডিফাই অপারেশনে লেনদেনগতভাবে ডেটা আপডেট করে।

কোটলিন

suspend fun incrementCounter() {
  context.settingsDataStore.updateData { currentSettings ->
    currentSettings.toBuilder()
      .setExampleCounter(currentSettings.exampleCounter + 1)
      .build()
    }
}

জাভা

Single<Settings> updateResult =
  dataStore.updateDataAsync(currentSettings ->
    Single.just(
      currentSettings.toBuilder()
        .setExampleCounter(currentSettings.getExampleCounter() + 1)
        .build()));

সিঙ্ক্রোনাস কোডে ডেটাস্টোর ব্যবহার করুন

ডেটাস্টোরের প্রাথমিক সুবিধাগুলির মধ্যে একটি হল অ্যাসিঙ্ক্রোনাস API, তবে আপনার আশেপাশের কোডকে অ্যাসিঙ্ক্রোনাস হিসাবে পরিবর্তন করা সবসময় সম্ভব নাও হতে পারে। এটি হতে পারে যদি আপনি একটি বিদ্যমান কোডবেসের সাথে কাজ করছেন যা সিঙ্ক্রোনাস ডিস্ক I/O ব্যবহার করে বা আপনার যদি এমন একটি নির্ভরতা থাকে যা একটি অ্যাসিঙ্ক্রোনাস API প্রদান করে না।

সিঙ্ক্রোনাস এবং অ্যাসিঙ্ক্রোনাস কোডের মধ্যে ব্যবধান পূরণ করতে সাহায্য করার জন্য কোটলিন কোরোটিন runBlocking() কোরোটিন নির্মাতা প্রদান করে। ডেটাস্টোর থেকে সিঙ্ক্রোনাসভাবে ডেটা পড়তে আপনি runBlocking() ব্যবহার করতে পারেন। RxJava Flowable এ ব্লক করার পদ্ধতি অফার করে। ডেটাস্টোর ডেটা ফেরত না দেওয়া পর্যন্ত নিম্নলিখিত কোডটি কলিং থ্রেডকে ব্লক করে:

কোটলিন

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

জাভা

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

UI থ্রেডে সিঙ্ক্রোনাস I/O ক্রিয়াকলাপ সম্পাদন করলে ANR বা UI জ্যাঙ্ক হতে পারে। আপনি ডেটাস্টোর থেকে ডেটা অসিঙ্ক্রোনাসভাবে প্রিলোড করে এই সমস্যাগুলি প্রশমিত করতে পারেন:

কোটলিন

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

জাভা

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

এইভাবে, ডেটাস্টোর অ্যাসিঙ্ক্রোনাসভাবে ডেটা পড়ে এবং মেমরিতে ক্যাশ করে। runBlocking() ব্যবহার করে পরবর্তীতে সিঙ্ক্রোনাস রিড দ্রুততর হতে পারে বা প্রাথমিক পঠন সম্পন্ন হলে একটি ডিস্ক I/O অপারেশন সম্পূর্ণভাবে এড়াতে পারে।

মাল্টি-প্রসেস কোডে ডেটাস্টোর ব্যবহার করুন

আপনি একটি একক প্রক্রিয়ার মধ্যে থেকে একই ডেটা সামঞ্জস্যের গ্যারান্টি সহ বিভিন্ন প্রক্রিয়া জুড়ে একই ডেটা অ্যাক্সেস করতে ডেটাস্টোর কনফিগার করতে পারেন। বিশেষ করে, ডেটাস্টোর গ্যারান্টি দেয়:

  • রিডস শুধুমাত্র সেই ডেটা ফেরত দেয় যা ডিস্কে স্থির থাকে।
  • পড়া-পরে-লেখার ধারাবাহিকতা।
  • লেখাগুলো সিরিয়াল করা হয়।
  • পঠনগুলি কখনই লেখার দ্বারা অবরুদ্ধ হয় না।

একটি পরিষেবা এবং একটি কার্যকলাপ সহ একটি নমুনা অ্যাপ্লিকেশন বিবেচনা করুন:

  1. পরিষেবাটি একটি পৃথক প্রক্রিয়ায় চলছে এবং পর্যায়ক্রমে ডেটাস্টোর আপডেট করে

    <service
      android:name=".MyService"
      android:process=":my_process_id" />
    
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
          scope.launch {
              while(isActive) {
                  dataStore.updateData {
                      Settings(lastUpdate = System.currentTimeMillis())
                  }
                  delay(1000)
              }
          }
    }
    
  2. অ্যাপটি সেই পরিবর্তনগুলি সংগ্রহ করবে এবং এর UI আপডেট করবে

    val settings: Settings by dataStore.data.collectAsState()
    Text(
      text = "Last updated: $${settings.timestamp}",
    )
    

বিভিন্ন প্রক্রিয়া জুড়ে ডেটাস্টোর ব্যবহার করতে সক্ষম হওয়ার জন্য, আপনাকে MultiProcessDataStoreFactory ব্যবহার করে ডেটাস্টোর অবজেক্ট তৈরি করতে হবে।

val dataStore: DataStore<Settings> = MultiProcessDataStoreFactory.create(
   serializer = SettingsSerializer(),
   produceFile = {
       File("${context.cacheDir.path}/myapp.preferences_pb")
   }
)

serializer ডেটাস্টোরকে বলে যে কীভাবে আপনার ডেটা টাইপ পড়তে এবং লিখতে হয়। নিশ্চিত করুন যে আপনি সিরিয়ালাইজার ব্যবহার করার জন্য একটি ডিফল্ট মান অন্তর্ভুক্ত করেছেন যদি এখনও কোনো ফাইল তৈরি না হয়। নীচে kotlinx.serialization ব্যবহার করে একটি উদাহরণ বাস্তবায়ন রয়েছে:

@Serializable
data class Settings(
   val lastUpdate: Long
)

@Singleton
class SettingsSerializer @Inject constructor() : Serializer<Settings> {

   override val defaultValue = Settings(lastUpdate = 0)

   override suspend fun readFrom(input: InputStream): Timer =
       try {
           Json.decodeFromString(
               Settings.serializer(), 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(Settings.serializer(), t)
               .encodeToByteArray()
       )
   }
}

আপনার ডেটাস্টোর দৃষ্টান্ত প্রক্রিয়া প্রতি অনন্য তা নিশ্চিত করতে আপনি হিল্ট নির্ভরতা ইনজেকশন ব্যবহার করতে পারেন:

@Provides
@Singleton
fun provideDataStore(@ApplicationContext context: Context): DataStore<Settings> =
   MultiProcessDataStoreFactory.create(...)

মতামত প্রদান করুন

এই সম্পদগুলির মাধ্যমে আমাদের সাথে আপনার প্রতিক্রিয়া এবং ধারণা শেয়ার করুন:

সমস্যা অনুসরণকারী
সমস্যাগুলি রিপোর্ট করুন যাতে আমরা বাগগুলি ঠিক করতে পারি৷

অতিরিক্ত সম্পদ

জেটপ্যাক ডেটাস্টোর সম্পর্কে আরও জানতে, নিম্নলিখিত অতিরিক্ত সংস্থানগুলি দেখুন:

নমুনা

ব্লগ

কোডল্যাব

{% শব্দার্থে %} {% endverbatim %} {% শব্দার্থে %} {% endverbatim %}