Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for DataStore is the new SharedPreferences
Layale Matta
Layale Matta

Posted on • Edited on • Originally published atyalematta.dev

     

DataStore is the new SharedPreferences

You've definitely used SharedPreferences to store small or simple data sets. But SharedPreferences' API has a series ofdownsides and luckily the Jetpack DataStore library aims at addressing those issues.

So if you're currently using SharedPreferences, consider migrating to DataStore instead. And good news, it's now in Beta 🎉

🔍 What is DataStore?

Jetpack DataStore is a data storage solution that provides two different implementations: Preferences DataStore and Proto DataStore.

Preferences DataStore stores and accesses data using keys.

Proto DataStore stores data as instances of a custom data type and requires creating a schema using protocol buffers.

DataStore uses Kotlin coroutines and Flow to store data asynchronously, consistently, and transactionally unlike SharedPreferences.

🤿 Let's dive

In this article, we will focus onPreferences DataStore.

In this simpleproject, we are implementing theRemember Me functionality of a Login screen. We are currently using SharedPreferences to store this value and redirect the user to the Welcome screen once it's checked. We will migrate the code to use DataStore.

To get your hands on the code, consider checking thisGitHub repo.
The final code is located in thepreferences_datastore branch.

🛑 SharedPreferences Limitations

The biggest downsides of SharedPreferences include:

  • Lack of safety from runtime exceptions
  • Lack of a fully asynchronous API
  • Lack of main thread safety
  • No type safety

Luckily Jetpack DataStore addresses those issues. Since it's powered by Flow, DataStore saves the preferences in a file and performs all data operations on Dispatchers.IO under the hood. And your app won't be freezing while storing data.

🏁 Let's get started...

First, add the Preference DataStore dependency in the build.gradle file:

implementation"androidx.datastore:datastore-preferences:1.0.0-beta01"
Enter fullscreen modeExit fullscreen mode

We have also added the Lifecycle dependencies for using ViewModel:

// architecture componentsimplementation"androidx.core:core-ktx:$coreVersion"implementation"androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion"implementation"androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"implementation"androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion"
Enter fullscreen modeExit fullscreen mode

🗃️ DataStore Repository

Inside a new package calledrepository, create the Kotlin classDataStoreRepository.kt. In this class we are going to store all the logic necessary for writing and reading DataStore preferences. We will pass to it a dataStore of typeDataStore<Preferences> as a parameter.

classDataStoreRepository(privatevaldataStore:DataStore<Preferences>){...}
Enter fullscreen modeExit fullscreen mode

Let's create a data class calledUserPreferecences. It will contain the two values we're going to save.

data classUserPreferences(valusername:String,valremember:Boolean)
Enter fullscreen modeExit fullscreen mode

Unlike SharedPreferences, in DataStore we cannot add akey simply as a String. Instead we have to create aPreferences.Key<String> object or simply by using the extension functionstringPreferencesKey as follows:

classDataStoreRepository(privatevaldataStore:DataStore<Preferences>){privateobjectPreferencesKeys{valUSERNAME=stringPreferencesKey("username")valREMEMBER=booleanPreferencesKey("remember")}}
Enter fullscreen modeExit fullscreen mode

📝 Write to DataStore

In order to save to DataStore, we use thedataStore.edit method using the keys we created above.

suspendfunsaveToDataStore(username:String,remember:Boolean){dataStore.edit{preference->preference[USERNAME]=usernamepreference[REMEMBER]=remember}}
Enter fullscreen modeExit fullscreen mode

You may have noticed that we're using a suspend function here. This is becausedataStore.edit uses Coroutines. This function accepts atransform block of code that allows us to transactionally update the state in DataStore. It can also throw an IOException if an error was encountered while reading or writing to disk.

📋 Read from DataStore

To read our data, we will retrieve it usingdataStore.data as aFlow<UserPreferences>.
Later, we are going to convert this Flow emitted value to LiveData in our ViewModel.

valreadFromDataStore:Flow<UserPreferences>=dataStore.data.catch{exception->if(exceptionisIOException){Log.d("DataStoreRepository",exception.message.toString())emit(emptyPreferences())}else{throwexception}}.map{preference->valusername=preference[USERNAME]?:""valremember=preference[REMEMBER]?:falseUserPreferences(username,remember)}
Enter fullscreen modeExit fullscreen mode

Make sure to handle the IOExceptions, that are thrown when an error occurs while reading data. Do this by usingcatch() beforemap() and emittingemptyPreferences().

🆑 Clear DataStore

To clear data, we can either clear the preferences all together or clear a specific preference by its key.

suspendfunclearDataStore(){dataStore.edit{preferences->preferences.clear()}}suspendfunremoveUsername(){dataStore.edit{preference->preference.remove(USERNAME)}}
Enter fullscreen modeExit fullscreen mode

🤙🏼 Call it from the ViewModel

In anotherviewmodel package, create theLoginViewModel class.

classLoginViewModel(privatevaldataStoreRepository:DataStoreRepository):ViewModel(){valuserPreferences=dataStoreRepository.readFromDataStore.asLiveData()funsaveUserPreferences(username:String,remember:Boolean){viewModelScope.launch(Dispatchers.IO){dataStoreRepository.saveToDataStore(username,remember)}}funclearUserPreferences(){viewModelScope.launch(Dispatchers.IO){dataStoreRepository.clearDataStore()}}}classLoginViewModelFactory(privatevaldataStoreRepository:DataStoreRepository):ViewModelProvider.Factory{overridefun<T:ViewModel>create(modelClass:Class<T>):T{if(modelClass.isAssignableFrom(LoginViewModel::class.java)){@Suppress("UNCHECKED_CAST")returnLoginViewModel(dataStoreRepository)asT}throwIllegalArgumentException("Unknown ViewModel class")}}
Enter fullscreen modeExit fullscreen mode

We're retrievinguserPreferences and converting the Flow into LiveData in order to observe it in our Activity. And sincesaveToDataStore andclearDataStore are suspended functions, we need to run them from inside a coroutine scope, which is the viewmodel scope in this case.

LoginViewModelFactory is aViewModelProvider.Factory that is responsible to create our instance ofLoginViewModel later in our Activity. We will pass to it theDataStoreRepository which is need inLoginViewModel's constructor.

Create DataStore 🗄️

privateconstvalUSER_PREFERENCES_NAME="user_preferences"valContext.dataStorebypreferencesDataStore(name=USER_PREFERENCES_NAME)
Enter fullscreen modeExit fullscreen mode

📦 Migrate from SharedPreferences

If we are migrating our existing data from the SharedPreferences, when creating our DataStore, we should add a migration based on the SharedPreferences name. DataStore will be able to migrate from SharedPreferences to DataStore automatically, for us.

privateconstvalUSER_PREFERENCES_NAME="user_preferences"privatevalContext.dataStorebypreferencesDataStore(name=USER_PREFERENCES_NAME,produceMigrations={context->listOf(SharedPreferencesMigration(context,USER_PREFERENCES_NAME))})
Enter fullscreen modeExit fullscreen mode

🔬 Observe it in the Activity

In our activity, we first observe our userPreferences as liveData from our ViewModel.

classLoginActivity:AppCompatActivity(){privatelateinitvarbinding:ActivityLoginBindingprivatelateinitvarviewModel:LoginViewModelprivatevarrememberMe=falseprivatelateinitvarusername:StringoverridefunonCreate(savedInstanceState:Bundle?){super.onCreate(savedInstanceState)binding=ActivityLoginBinding.inflate(layoutInflater)valview=binding.rootsetContentView(view)viewModel=ViewModelProvider(this,LoginViewModelFactory(DataStoreRepository(dataStore))).get(LoginViewModel::class.java)viewModel.userPreferences.observe(this,{userPreferences->rememberMe=userPreferences.rememberusername=userPreferences.usernameif(rememberMe){startActivity(Intent(this,WelcomeActivity::class.java))}})binding.login.setOnClickListener{if(binding.remember.isChecked){valname=binding.username.text.toString()viewModel.saveUserPreferences(name,true)}startActivity(Intent(this,WelcomeActivity::class.java))}binding.remember.setOnCheckedChangeListener{compoundButton:CompoundButton,b:Boolean->if(!compoundButton.isChecked){viewModel.clearUserPreferences()}}}}
Enter fullscreen modeExit fullscreen mode

WheneverRemember Me is observed as checked, we redirect the user to the Welcome screen. Whenever we click the login button, if our checkbox is checked we update our userPreferences, otherwise we clear our saved user preferences.

For the simplicity of our application, we will use the same ViewModel in ourWelcomeActivity as well. We observe theusername and display it whenever it is not empty. And once we log out we clear our saved userPreferences.

classWelcomeActivity:AppCompatActivity(){privatelateinitvarbinding:ActivityWelcomeBindingprivatelateinitvarviewModel:LoginViewModeloverridefunonCreate(savedInstanceState:Bundle?){super.onCreate(savedInstanceState)binding=ActivityWelcomeBinding.inflate(layoutInflater)valview=binding.rootsetContentView(view)viewModel=ViewModelProvider(this,LoginViewModelFactory(DataStoreRepository(dataStore))).get(LoginViewModel::class.java)viewModel.userPreferences.observe(this,{userPreferences->valusername=userPreferences.usernameif(username.isNotEmpty()){binding.welcome.text=String.format(getString(R.string.welcome_user),username)}})binding.logout.setOnClickListener{viewModel.clearUserPreferences()startActivity(Intent(this,LoginActivity::class.java))}}}
Enter fullscreen modeExit fullscreen mode

💡 Key Takeaways

Now that we migrated to Preferences DataStore let's recap!

DataStore:

  • is a replacement for SharedPreferences
  • has a fully asynchronous API using Kotlin coroutines and Flow
  • guarantees data consistency
  • handles data migration
  • handles data corruption

⏭ Up next

Join me in the next post to learn how to useProto DataStore.

If this post was of any help to you, or if you think it requires further explanation, I'd love to know! Drop me a DM on Twitter@yalematta ✌🏼

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Android Developer. I consider the community one of the best parts of my job.
  • Joined

More fromLayale Matta

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp