blonsky95 / TimeStopperAndroid

0 stars 0 forks source link

DataStore over SharedPreferences #48

Closed blonsky95 closed 3 years ago

blonsky95 commented 3 years ago

https://developer.android.com/topic/libraries/architecture/datastore

blonsky95 commented 3 years ago

Ok so DataStore is in Alpha and it relies on coroutines and Flow - which combined aim to substitute RxJava - permits asynchronous code to work

Here about flow: https://blog.mindorks.com/what-is-flow-in-kotlin-and-how-to-use-it-in-android-project

And here about DataStore basic setup with better context https://medium.com/better-programming/jetpack-datastore-improved-data-storage-system-adec129b6e48

blonsky95 commented 3 years ago

should probably do this to understand coroutines/livedata/flow better : https://codelabs.developers.google.com/codelabs/advanced-kotlin-coroutines/#0

blonsky95 commented 3 years ago

And this for actual Datastore https://developer.android.com/codelabs/android-preferences-datastore#5

blonsky95 commented 3 years ago

just watch philip lackner

blonsky95 commented 3 years ago

https://www.youtube.com/watch?v=ShNhJ3wMpvQ&list=PLQkwcJG4YTCQcFEPuYGuv54nYai_lwil_&ab_channel=PhilippLackner for coroutines

Do the flow thing with DataStore first, then watch all the things from philipp!

blonsky95 commented 3 years ago

Next - call the functions that called through Utils, through coroutines and a lifecycle scope - see here example https://www.youtube.com/watch?v=McnVx7l5awk&ab_channel=PhilippLackner DONE Next- test if the prefs are working - need to make the pref class work because datastore is null in most calls (init at start?) - WORK DONE Next - think how to initialise datastore correctly - DONE (missing DI) Next - clean up and document Next- write/sum up here how to use Datastore for preferences

blonsky95 commented 3 years ago

STILL needs TESTING for all functions

First how the class works:

PreferenceDataStore is a class that requires context to be initiliased - then used to init DataStore However the PrefernceDataStore class is instantiated in every class that requires its usage, but to avoid different instances or simultaneous update/read operations, the class is like a singleton, to achieve this the following code is done:

    companion object {
    //Volatile means that writes to this field are immediately made visible to other threads
    // - hence if there is already an instance in other thread, it would use that one
    @Volatile private var INSTANCE:PreferencesDataStore? = null

    //Synchronized makes sure the code inside the block cant be run from two multiple threads at the same time
    //its like a room is the code, and each time a thread accesses it, it locks the room behind it,
    // so until it finishes its job in the room, no other thread can access
    fun getInstance(context: Context): PreferencesDataStore =
        INSTANCE ?: synchronized(this) {
            INSTANCE ?: createClassInstance(context).also { INSTANCE = it }
        }

    private fun createClassInstance(context: Context) =
        PreferencesDataStore(context)
     }

Like with a RoomDatabase - google architecture

blonsky95 commented 3 years ago

The actual DataStore works as such:

initialization:

private var dataStore: DataStore<Preferences> = context.createDataStore(
    name = "tracktimer_preferences"
)

reading operation:

runs from a coroutine - so its a suspend function, the preference variable contains all the key pairs, so because flow is an array of data that is streamed, and we only have one preference, we use first() and get the reference key we want. Then add the elvis operator to return the default value if preference is not existing

datastore.data returns androidx.datastore.preferences.core.Preferences so getting first of this.

suspend fun isUserSubscribed(): Boolean {
    val preference = dataStore.data.first()
    return preference[PREF_IS_SUBSCRIBED]?:false
}

writing operation: same thing but edit function gets mutablePreferences type from datastore, you then modify the key you want

suspend fun updateIsUserSubscribed(isSubscribed: Boolean) {
    dataStore.edit { prefs ->
        prefs[PREF_IS_SUBSCRIBED] = isSubscribed
    }
}

And then to call from activity

   lifecycleScope.launch {
        mainViewModel.checkIfCanStartTiming(mainActivity)
    }

or from viewmodel

        viewModelScope.launch {
        preferencesDataStore.updateIsTimingFreeActive(true)
        }

Also using this when I am trying to update something from a coroutine that I dont want binded to a lifecycle, for example when I finish in onBoardingActivity it sends an intent to open to mainActivity, but I also want to update the preference of firstTimer, so I run the coroutine to update the value on a globalscope coroutie, that wont die when the OnboardingActivity finishes shortly after the intent. Otherwise use the lifecyclescope or viewmodelscope - this more coroutines related

        GlobalScope.launch {
            //dont want to block ui, even if super short simple task, it should be done in bg
            PreferencesDataStore.updateUserFirstTimer(context, false)
        }
blonsky95 commented 3 years ago

Continue testing but jump to dependency injection after testing