The Top 3 State Management Mistakes On Android

Поділитися
Вставка
  • Опубліковано 13 січ 2024
  • In this video I will show you the Top 3 State Management Mistakes On Android.
    💻 Let me be your mentor and become an industry-ready Android developer in 10 weeks:
    pl-coding.com/drop-table-ment...
    ⭐ Courses with real-life practices
    ⭐ Save countless hours of time
    ⭐ 100% money back guarantee for 30 days
    ⭐ Become a professional Android developer now:
    pl-coding.com/premium-courses/
    Get my FREE PDF about 20 things you should never do in Jetpack Compose:
    pl-coding.com/jetpack-compose...
    Regular programming advice on my Instagram page: / _philipplackner_
    Join my Discord server:
    / discord
  • Наука та технологія

КОМЕНТАРІ • 84

  • @96Cpr
    @96Cpr 4 місяці тому +35

    In the part you talk about the _state.update (between 5:00 and 6:00) it's better to also use it.counter instead of state.value.counter.

  • @robchr
    @robchr 4 місяці тому +20

    Your saved SavedStateHandle implementation has the same race condition bug as the previous example.

    • @Crimdog
      @Crimdog 4 місяці тому +8

      yea came to ask that, seems like that suffers from the same mistake as example 1? Or is there something I'm missing?

    • @hups6648
      @hups6648 3 місяці тому

      Yep what do you think about that Philipp?

    • @iori57
      @iori57 Місяць тому

      For solving this I did the following but I am not sure if it works in all conditions, but should fix the race condition, let me know your comments:
      I created these extension methods for SavedStateHandle:
      fun SavedStateHandle.getUiStateFlow(initialValue: T): StateFlow = this.getStateFlow("uiState", initialValue)
      fun SavedStateHandle.updateUiState(uiState: StateFlow, function: (T) -> T) {
      synchronized(uiState) {
      val prevValue = uiState.value
      val nextValue = function(prevValue)
      this["uiState"] = nextValue
      }
      }
      now in my viewModel, I can do this to get the uiState:
      val uiState = savedStateHandle.getUiStateFlow(
      MyUiState( title = "test")
      )
      and if I want to update the state:
      savedStateHandle.updateUiState(uiState) {
      it.copy(title = "updated")
      }

  • @eugenepopovich2264
    @eugenepopovich2264 4 місяці тому +13

    Thanks for the video. One note. There will not be any race condition in the first case demo. That's because both coroutines run in the main thread and there are no any suspending calls between read and write of the state. However, in case of the alternative dispatcher, such as IO or some suspending call between read and write there will be the race condition.

  • @abdallahsafieddine1632
    @abdallahsafieddine1632 4 місяці тому

    Pretty much basics and official recommendations.. good job

  • @mircodev
    @mircodev 4 місяці тому

    Thx for the video. Tip 2 was very helpful for me.

  • @khanzadakashif8248
    @khanzadakashif8248 4 місяці тому +1

    Third one actually helped me understand a problem we've been facing for quite a long time.

  • @UsmonWasTaken
    @UsmonWasTaken 4 місяці тому +16

    SavedStateHandle API is good enough to work with, but it would be great if it's type-safe. On top of it, you have to use a synchronized block or Mutex to make it thread-safe

    • @ngolian
      @ngolian 4 місяці тому +8

      I noticed the sync issue too. In fixing mistake 2 he reintroduced mistake 1!

    • @pablovaldes6022
      @pablovaldes6022 4 місяці тому +2

      To me the Android saveStateHandle API and the whole ceremony created around process death is unavailable and just not necessary. Use sharedPreferences or a database and everything should be ok.

    • @deepakbisht4957
      @deepakbisht4957 4 місяці тому

      ​@@pablovaldes6022well you don't need persistent storage for everything.
      SaveStateHandle and persistent storage has different usecases. You simply can't blindly store everything...

  • @DiabloZq
    @DiabloZq 4 місяці тому

    Thanks you! Interesting way, before i did it with mutex, but now i see that more direct way.

  • @khanzadakashif8248
    @khanzadakashif8248 4 місяці тому +2

    For those trying to recreate first one, create a textfield, update it's value via viewmodel, use copy method and then try typing quickly.... I learned it the hard way... 😅

  • @yuriifeshchak7124
    @yuriifeshchak7124 22 дні тому

    Thanks a lot Philipp for helpful tips. Your videos improve my Android Dev skills and my English as well.

  • @Adam-ey7wy
    @Adam-ey7wy 4 місяці тому +1

    Bro, u r the GOAT of Android :) great tips as always! Here is my video request: How to handle google ads/ custom ads in our apps properly

  • @noebenjaminreynosoaguirre1565
    @noebenjaminreynosoaguirre1565 4 місяці тому

    thank you philipp, i didnt know about the options shown by the right click on the logcat window xd

  • @user-xz2lc8ni5l
    @user-xz2lc8ni5l 4 місяці тому

    Very important concepts thank you, people very often encounter in this scenarios in real world projects.

  • @DenzilFerreira
    @DenzilFerreira 4 місяці тому +16

    One thing to consider is that if you save a token in persistent storage, you need to make sure it’s in a secure fashion (encrypted for example). Depending on the token, they may have a shorter or longer lifetime. It becomes an easy attack vector. If the default is null, we could simply ask the user to re-login?

    • @erayagdogan3389
      @erayagdogan3389 4 місяці тому

      There's EncryptedSharedPreference and there's also SQLCipher for database encryption and it can work with Room.

    • @fardalakter4395
      @fardalakter4395 4 місяці тому +1

      Do you want to relogin everytime you kill the apps ? That would be annoying

    • @deepakbisht4957
      @deepakbisht4957 4 місяці тому

      ​@@fardalakter4395no!
      Create two tokens.
      One is an access token for a short life span and another one is a refresh token that has a long time span.
      When your access token expires then don't logout, just hit another api that has a refresh token in the header and it will return a new access token and refresh the token and hit that API again with the new access token...
      Point to note that
      Access token is used for authentication and authorisation of all the APIs
      Refresh token is used only to get new access token and new refresh token...

    • @DenzilFerreira
      @DenzilFerreira 4 місяці тому

      @@fardalakter4395 could be a fingerprint, pin to re-authenticate? That’s what normally secure apps do.

    • @olivermetz4809
      @olivermetz4809 4 місяці тому

      Often you can refresh an expired access token without having to login again using *drumroll*... a refresh token. I'd generally be very careful what to persist.

  • @fakhrymubarak665
    @fakhrymubarak665 4 місяці тому

    thanks for the knowledge bro!

  • @LucasAlfare
    @LucasAlfare 4 місяці тому +4

    And what about the multithreading problem on the second topic using the approache of the third topic?

  • @rsumanradhakrishnan3747
    @rsumanradhakrishnan3747 4 місяці тому +1

    Hey philip, please make video related to KMP

  • @GN9K71
    @GN9K71 4 місяці тому

    Let me ask: running two separate coroutines from the viewmodel scope will run the frim the context of the main thread by default, right? In this case how a race conditon will occur?

  • @unknownBoy85lover
    @unknownBoy85lover 4 місяці тому +1

    Thank you sir ❤

  • @amineharbaoui7408
    @amineharbaoui7408 3 місяці тому

    is any one know what Philipp did you move the bloc inside the viewModelScope using the keyboard ?

  • @jvp8447
    @jvp8447 4 місяці тому +1

    You are awesome!

  • @pelealexandru
    @pelealexandru 4 місяці тому

    hey Philip, in the first example both coroutines are running on Main thread and since none of them perform a suspend operation, they will always run one after the other and there will never be a race condition in this particular example. am i wrong somewhere? i am pretty new to coroutines so please let me know if i am wrong. thanks!

  • @HritikGupta722
    @HritikGupta722 4 місяці тому

    Thank You !

  • @ngapps
    @ngapps 3 місяці тому

    Hi, what about such kind of update, is it better to change as example _contactUiState.value = ContactUiState.Loading to _contactUiState.update { ContactUiState.Loading }? Similar situation as with copy, but probably happens once? Thanks

  • @mustafaammar551
    @mustafaammar551 4 місяці тому

    Thank You BRO

  • @probot7588
    @probot7588 4 місяці тому

    Can you please make a video on mvvm with firebase and pagination 3

  • @ccmonkey1106
    @ccmonkey1106 4 місяці тому +4

    This is a very interesting video. I'll try to apply these solutions in my code base.
    I have a personal request, Philipp. Can you make an updated video related to preferences datastore? Shared preferences is sorta deprecated right and it would be nice to have a new guide on how to apply preferences datastore nowadays

    • @PhilippLackner
      @PhilippLackner  4 місяці тому +1

      Shared preferences isn't deprecated :)

    • @ubersticks
      @ubersticks 4 місяці тому +1

      @@PhilippLackner this guy says it is deprecated: *"Preferences DataStore in 10min (SharedPreferences deprecated)"* by Philipp Lackner, Nov 27 ,2020 🙂

    • @fardalakter4395
      @fardalakter4395 4 місяці тому +2

      ​@@ubersticks😂

    • @ccmonkey1106
      @ccmonkey1106 4 місяці тому +1

      @@PhilippLackner Thanks for the answer. I'll do my research to avoid questions like that. But if that said, I'm wondering if there's any advantage of learning preferences datastore over shared preferences...

    • @deepakbisht4957
      @deepakbisht4957 4 місяці тому

      ​@@ccmonkey1106for simple lightweight solution shared preference is enough but for complex data and better performance especially for larger dataset data store is better as it works asynchronously so it is good for IO operations

  • @EmmanuelAgyapong-jn6ue
    @EmmanuelAgyapong-jn6ue 3 місяці тому

    Good question, where can I find resources where I can learn Android in more depth? I kind am proficient (making apps), but I realized that I lack the fundamentals.

  • @user-wk1vz9wv6j
    @user-wk1vz9wv6j 4 місяці тому +2

    Hi Phillip, I have a question Should we have to use mutableStateOf or mutableStateFlow for state managing?

    • @vengateshm2122
      @vengateshm2122 4 місяці тому +1

      mutableStateOf can be used inside composable.
      mutableStateFlow can be used in view model.
      But technically you can use both inside view mode.

  • @inuyasha11p
    @inuyasha11p 4 місяці тому +1

    Hi Philipp. I wanted to start learning kotlin and I saw that ur playlist was from 4 years ago. Is it still possible to learn from there or is it too outdated?

  • @matteoZattera
    @matteoZattera 4 місяці тому

    Hi Philipp, can you make a video on how to make an app with an OnBoarding screen using Datastore to save onBoardingCompleted?

    • @vengateshm2122
      @vengateshm2122 4 місяці тому

      //
      class AppDataStore(context: Context) {
      private val dataStore: DataStore by context.dataStore(name = "app_settings")
      private val ONBOARDING_COMPLETED_KEY = preferencesKey("onboarding_completed")
      val onboardingCompleted: Flow = dataStore.data
      .catch { exception ->
      if (exception is IOException) {
      emit(emptyPreferences())
      } else {
      throw exception
      }
      }
      .map { preferences ->
      preferences[ONBOARDING_COMPLETED_KEY] ?: false
      }
      suspend fun setOnboardingCompleted(completed: Boolean) {
      dataStore.edit { preferences ->
      preferences[ONBOARDING_COMPLETED_KEY] = completed
      }
      }
      }
      //
      class MyApp : Application() {
      lateinit var appDataStore: AppDataStore
      private set
      override fun onCreate() {
      super.onCreate()
      appDataStore = AppDataStore(this)
      }
      }
      //
      @Composable
      fun OnboardingScreen(appDataStore: AppDataStore) {
      val onboardingCompleted by appDataStore.onboardingCompleted.collectAsState()
      if (onboardingCompleted) {
      // Show your main app screen
      // ...
      } else {
      // Show onboarding screen
      Button(onClick = {
      lifecycleScope.launch {
      appDataStore.setOnboardingCompleted(true)
      }
      }) {
      Text("Finish Onboarding")
      }
      }
      }
      //
      class MainActivity : AppCompatActivity() {
      private val appDataStore by lazy { MyApp.instance.appDataStore }
      override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      setContent {
      MyAppTheme {
      AppMainScreen(appDataStore = appDataStore)
      }
      }
      }
      }
      @Composable
      fun AppMainScreen(appDataStore: AppDataStore) {
      val onboardingCompleted by appDataStore.onboardingCompleted
      .collectAsStateWithLifecycle(lifecycle = LocalLifecycleOwner.current.lifecycle)
      if (onboardingCompleted) {
      // Show your main app screen
      // ...
      } else {
      // Show onboarding screen
      OnboardingScreen(appDataStore = appDataStore)
      }
      }

  • @sepideh1085
    @sepideh1085 4 місяці тому

    i always wonder about defining all the states in a data class and copy the data class when we want to update it . doesn't it causes memory leak issues ? isn't it better to define each state as a separate stateflow and set each of their value whenever we want the state to be updated!? instead of copy the whole data class just to change one of its property each time!

  • @user-ku4hz2wz2i
    @user-ku4hz2wz2i 4 місяці тому

    Thank you

  • @BelokonRoman
    @BelokonRoman 4 місяці тому

    Third case can lead to an issue even without process death because the sessionTOken variable is not thread safe(memory visibility issue)

  • @salmakd3586
    @salmakd3586 4 місяці тому +1

    Quick question: can we pass arguments from one destination to another using parcealble data classes also?

    • @abiodunmoses2638
      @abiodunmoses2638 4 місяці тому

      Not without doing it the hacky way. Why not just save whatever you are trying to pass into a db?

  • @pablovaldes6022
    @pablovaldes6022 4 місяці тому +2

    I keep saying that savedStateHandle and whatever process API Google created to recover from process death is unscalable and not necessary.
    You forgot to mention there is a limit in the amount of data you can save and the fact that it needs to be serializable makes it hard to deal with big state classes. Better off using regular preferences or a database. Also you should have mentioned that in modern devices that is very unlikely to happen.
    That API is one of Google's worst API designs ever created.

  • @ozzy4654
    @ozzy4654 4 місяці тому

    Looking at your mentorship program for Feb. There still spots open?

  • @PSK005
    @PSK005 4 місяці тому

    7:50 recently i read some articles. Saved state handle can store 1mb size of data. It's true???

    • @raheemadamboev
      @raheemadamboev 4 місяці тому +1

      I think it was 2 mb (I could be wrong tho). But yes it has limited capacity.

    • @raheemadamboev
      @raheemadamboev 4 місяці тому +1

      It throws ParcelTooLarge exception when it gets full

  • @hashemmousavi2451
    @hashemmousavi2451 4 місяці тому +2

    Both Coroutines were run on the Main thread. How is it possible to be in a race condition?

    • @christianricardo4058
      @christianricardo4058 4 місяці тому +1

      It's explained.
      He said both coroutines may be in the same thread.
      Boths coroutines first read the initial value before it is updated

    • @fardalakter4395
      @fardalakter4395 4 місяці тому

      ​@@christianricardo4058then it's synchronized because it's executed line by line

    • @lemondog252
      @lemondog252 4 місяці тому

      Phillipp could you make another video explain this more?

    • @hashemmousavi2451
      @hashemmousavi2451 4 місяці тому +1

      @@christianricardo4058 I know l, but it's impossible to have race condition on the same thread.

    • @vengateshm2122
      @vengateshm2122 4 місяці тому +1

      Try changing the dispatchers and update the counter.

  • @foryoutube5118
    @foryoutube5118 4 місяці тому +1

    Green -> Red Thumbnail

  • @paininmydroid4526
    @paininmydroid4526 3 місяці тому

    A counter app in Android? Flutter is ruining the world. 🤣

  • @FerreolYvesPoint-mi3oq
    @FerreolYvesPoint-mi3oq 4 місяці тому

    in reality, there could be many more errors