Are One-Time Events an Anti-Pattern? - Why Almost Every Android Dev Does It Wrong!

Поділитися
Вставка
  • Опубліковано 4 лис 2023
  • This video is all about one-time events being sent into Channels or SharedFlows. Is that okay or is it considered an anti-pattern as some articles suggest? I will share my opinion.
    💻 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
    Check Manuel's article about one-time events here:
    / viewmodel-one-off-even...

КОМЕНТАРІ • 103

  • @Marco-dr2on
    @Marco-dr2on 7 місяців тому +37

    It's crazy how often your videos come out when I most need them. Thanks!

  • @abdelrahmankhaled7575
    @abdelrahmankhaled7575 7 місяців тому +29

    I like these type of videos which does not directly teach a new concept but rather opens a discussion on a special topic this made me understand the topic better in deep, thanks for this, and hope for more videos of this type !

  • @AlexGnok
    @AlexGnok 7 місяців тому +9

    We actually have used another workaround for this - the old school Event class with isConsumed property. So the ViewModel sets the state (StateFlow), then the Fragment during consumption first checks if the Event has been consumed already (in which case just ignores it), then if not - does what it needs to do and sets the property isConsumed to true. This combined with some extension functions (like consumeIfHasntBeenAlready and peekContent for testing) makes a flawless job

  • @user-ee6uo4il7x
    @user-ee6uo4il7x 7 місяців тому +13

    Thank you so much for your videos. I’m writing a diploma work in my university using Android development, I have never worked with mobile applications and I’m really love clean architecture, so your videos about MVVM, about Jetpack compose, comparison sharedFlow, channels, QR-codes, cameraX, AI and so on are GREAT. So maybe it’s just simple word “thanks” for you, Philipp, but you should know, that this “thanks” for me is COLOSSAL THANKS! I’m from Russia and our developers on UA-cam don’t talk about all of this as good as you! So don’t stop making this very useful and meaningful videos, you are great)

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

      Thank you! All the best for your diploma 💪🏻

  • @dahlola
    @dahlola 7 місяців тому +1

    Very nice video! I have been battling with how to implement one-time events and ended up following Manuel's advice because I wanted to stay on the safe side.
    But I agree that it introduces a lot of complexity and one junior developer in my team doesn't really understand it. I myself can forget about resetting the state also.
    But now I feel more comfortable using channels and think I will switch to that :)

  • @pelealexandru
    @pelealexandru 6 місяців тому

    thanks Philipp for the deep dive on the topic! i'm working on a greenfield project with compose and i was really not sure how to best handle this kind of viewmodel driven navigation scenario. i fully understand the options now!

  • @nem1st
    @nem1st 7 місяців тому

    I was waiting for this video for so long. Thank you very much!

  • @DenisFisenko
    @DenisFisenko 7 місяців тому +18

    Awesome video, thanks!
    We use state way for one-time events. To unify the handling of such events we've created a common interface like:
    interface UiEvent {
    val data: T
    val onConsumed: () -> Unit
    }.
    Where the `onConsumed` callback is set to the view model's function to set event property in the state to null.
    With some nice extensions, handling of such events on the UI layer is uniformed and clean.

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

      That's awesome, thanks for adding 🙌

    • @Narazgul
      @Narazgul 7 місяців тому +1

      I like this solution as well, in case I'd have to stick with state

    • @riyupapa39
      @riyupapa39 6 місяців тому

      That is also good idea. If the event is not happen continotly like snackbar text or some of notify , MutableState with reset consume also good.

  • @Otabek001uz
    @Otabek001uz 7 місяців тому

    Very nice, it's amazing, good luck Phillip!

  • @TuanBuianonymous
    @TuanBuianonymous 7 місяців тому

    Wow, i have been wait too long for video like this. Thankssssss

  • @amzpakistani3186
    @amzpakistani3186 7 місяців тому +8

    Hey Philipp, excellent work! Can you please make an updated tutorial about ktor and Rest API? I would really appreciate that 🙏

  • @nicoloagnoletti3275
    @nicoloagnoletti3275 7 місяців тому +13

    Crazy to me how hard it is to make junior colleagues understand the difference between state and one-time events
    Great content as always 👍

  • @DayZilya
    @DayZilya 7 місяців тому

    I loved the video. Would love more of such content. Explaining differences of approaches

  • @mohammad_droid
    @mohammad_droid 24 дні тому

    Perfect!
    I was a fan of the sharedFlow school, but you convinced me as usual😂

  • @etasdemir
    @etasdemir 7 місяців тому

    Great video, thanks!
    one thing to add for 7:00 channel's default capacity 0 which used in the video and it creates a rendezvous channel. Publisher suspends itself until there is subscriber or subscriber suspends until there is a publisher.

  • @verbranntenetzhaut
    @verbranntenetzhaut 7 місяців тому +1

    Hey Phillip I subscribed to your channel a while ago because I got really interested in Android content but I stopped watching your videos because they just weren't that great presented compared to reading a blog article etc.
    Now I just want to give some positive feedback because this video is very good, referring to the blog article, preparing an example that demonstrates the different cases and keeping it all readable was very helpful.
    Your insights and POV are very valuable, thank you for the effort and I'll definitely tune in more often 👍

  • @ssSFrankSss
    @ssSFrankSss 6 місяців тому

    This is a thumbs up video from me. After creating some complex apps I am entirely aligned with your position on this topic! Keep up the good work ;-)

  • @leonardomiranda1349
    @leonardomiranda1349 2 місяці тому

    Excellent video, you explained every scenario flawlessly 👌

  • @jankotas9882
    @jankotas9882 6 місяців тому

    I dealt with this dilemma for several weeks last year and I'm glad I ultimately stuck with the same solution that you did. If you have a solid understanding of channels and can handle them correctly, there's no need to use state for single events. 👍

  • @ernestguevara5968
    @ernestguevara5968 7 місяців тому

    I saw the use of channels first in your form validation video and it blown my mind, because first I was using events then unknowingly has the same implementation as manuel, but I didn't like it for the same reason that I need to reset it everything, for me it looked dirty. So when I saw the channels in your form validation video, I replaced it and works and look what I want it to be!

  • @riyupapa39
    @riyupapa39 6 місяців тому

    This is very very very important and powerfull tip!!!!!! Thanks Philipp!!!!

  • @gustavobarbosab
    @gustavobarbosab 6 місяців тому

    I agree with you Philipp... When we are using "Channels", the code is simpler to understand, and thinking about junior developers, working with states in this situation can be hard and generate a lot of bugs. This kind of thought about right and wrong depends on the context.
    Nice video, thank you for sharing your knowledge with us.

  • @Guilo583
    @Guilo583 7 місяців тому

    It is amazing. I always learn new thing when watching your video. Thanks

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

    Great video, this is really a controversial topic and there was no place where it was possible to get an unequivocal answer, thank you very much!

  • @theophilus494
    @theophilus494 7 місяців тому

    wisdom!!! thanks always Philipp

  • @unknownBoy85lover
    @unknownBoy85lover 7 місяців тому

    Thank you it will helpful absolutely great value to your videos and sharing your experiences with us ❤❤❤❤

  • @Nixomia
    @Nixomia 7 місяців тому +2

    Your videos have been incredibly helpful, and I really appreciate you sharing your knowledge. I recently tried to put your Jetpack Compose tutorial into practice while building an app, but I'm having trouble with the login/register navigation and home nested navigation. Specifically, I'm unsure how to manage the scaffold for the login and register screens, and I'm also struggling to customize the FAB for each screen in the home nested navigation. Any guidance you could provide would be greatly appreciated.

  • @deepakkanyan1072
    @deepakkanyan1072 7 місяців тому

    Today only I was working on same functionality, really thanks its really helped

  • @mobiledevpro
    @mobiledevpro 7 місяців тому

    Thanks you shared you thoughts about that . Well-explained👍

  • @samadMahmoodi
    @samadMahmoodi 7 місяців тому +14

    there is no RIGHT way to do ANY thing in android

  • @walkmn
    @walkmn 7 місяців тому +1

    Sounds like we are still in process of finding best mechanism of doing one shot event from viewModel to UI

  •  7 місяців тому

    Very well explained Phillip

  • @1stCyborgOnMars
    @1stCyborgOnMars 7 місяців тому

    Great video. Thanks for helping again

  • @evgeniy2012year
    @evgeniy2012year 7 місяців тому

    great video! Events look like the best option. thanks

  • @dumpfuckery7909
    @dumpfuckery7909 7 місяців тому

    Hi, new to Kotlin coming from Flutter, i have a few question:
    - Is StateFlow is in the same case as SharedFlow in this case (multiple listener but sharedflow can emit same value)
    - I have an app in the making at connect to an Iot device (heart rate monitor) that's pushing data through serial in 150ms interval, i'm currently using Stateflow as the device may push same data. Is it the recommend way?
    Side note: as i'm new to kotlin, loving your coding style so far, it's readable and easy to understand

  • @mariuskohmann8476
    @mariuskohmann8476 6 місяців тому

    Great video! How does SingleLiveData from LiveData lib compare to these solutions using Flow/State?

  • @mesutemrecelenk5447
    @mesutemrecelenk5447 7 місяців тому

    Hi Phlipp. Thanks for this nice tutorial. How to use observeAsEvent function for our all composables?

  • @sriduttan7692
    @sriduttan7692 7 місяців тому

    Hey @PhilippLackner , Awesome video, thanks! I have a Qn, WhileSubscribed function takes stopTimeoutMillis. can't we just set stopTimeoutMillis to 5000ms for ex. this will address all the issues when lifecycle/orientation changes happen. it will wait for the collectors to come back, therefore emissions won't get dropped. (StateFlow)

  • @manticomar1146
    @manticomar1146 5 місяців тому

    I don't care what the other dude from Google says for me. philip is always right. We respect u philip

  • @ahmetozcan2278
    @ahmetozcan2278 7 місяців тому +1

    What about keeping a list of ui events in the state object and update the list when the events are processed ?

  • @mikeshilovski1512
    @mikeshilovski1512 7 місяців тому

    Great videoo, thanks 👍

  • @valentinvolodarskij
    @valentinvolodarskij 7 місяців тому +2

    Thanks for a video on a really interesting topic but I have one question about LaunchedEffect. I have seen in many examples that we have to pass the lifecycle as a key to close the LaunchedEffect if the owner of the lifecycle has changed. But do we really need to pass it as a key? Because according to the documentation the LaunchedEffect will be closed and restarted if one of the keys changes OR the LaunchedEffect leaves the composition. So in general we will only close and restart this effect when the activity has recreated itself (which means that the LaunchedEffect will leave composition even without lifecycle as a key) or I don't understand other cases where the lifecycleOwner is changed
    Thanks in advance to everyone for any clarification on this question! :)

  • @SerhiiSolodilov
    @SerhiiSolodilov 7 місяців тому +3

    The only issue is that rotation isn't the only reason to lose event. If you consider that ViewModel could be recreated too state will be on top of the solutions list.

    • @PhilippLackner
      @PhilippLackner  7 місяців тому

      In which scenario would the ViewModel be recreated, but not the UI?

    • @SerhiiSolodilov
      @SerhiiSolodilov 7 місяців тому +5

      ​@@PhilippLackner UI also wont exist. Example is when the user start operation, switches to another app and Android decides to kill your app. When user get back, ViewModel needs to be restored and show correct UI and having state is more suitable for that.

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

      @@SerhiiSolodilov ah now I see what you mean, yes that is a good point

  • @nikolozlatsabidze2196
    @nikolozlatsabidze2196 7 місяців тому

    what can you suggest for state inside viewModels. data class or sealed class. consider the case when ui is very complex, data classes become very big and complex with lots of parameters. is it the sign to use sealed class or even decompose it using another viewModel. so may be multiple viewModels for that particular screen.

  • @dawidhyzy1425
    @dawidhyzy1425 7 місяців тому

    Would be nice to hear Manuel Vivo response too 🙂

  • @eddieng7136
    @eddieng7136 7 місяців тому

    Would you talk about the new compose Multiplatform wizard ?

  • @redaelmadini
    @redaelmadini 7 місяців тому

    What if we have to send multiple events at one (in a list)? I think the third solution with "state" is most suitable. Plus, in some scenarios we have to later consume this event after an action, i.e undo delete action within a snack bar that we have to keep after rotation.

  • @bitwisedevs469
    @bitwisedevs469 7 місяців тому

    First! Finally this topic! Indeed, resetting state which is being suggested by many can cause more bug when someone forgets it instead of sticking to use channel. Thanks a lot

    • @aldaricJohnes
      @aldaricJohnes 7 місяців тому +1

      channel are really bad for that, the second your app will be killed by the system you will have an inconsistent state between your state and channel, and that's way worse than "someone forgetting"
      Design errors are worse than dev errors

    • @bitwisedevs469
      @bitwisedevs469 7 місяців тому

      Fair enough, actually that is what we are doing currently. However if this is the case then all we need here is to use StateFlow directly so no need to convert Channel to state. Unfortunately manually resetting each state is making it hard to maintain and it adds more complexity to read the system flow. So this approach is where we are heading.

  • @ChrisAthanas
    @ChrisAthanas 7 місяців тому

    Super useful breakdown of a controversial and misunderstood factor of Andriod and pure compose apps

  • @GulshanRahimova
    @GulshanRahimova 5 місяців тому

    Great video

  • @breensrobert
    @breensrobert 7 місяців тому

    this was insightful

  • @stanchostanchev5991
    @stanchostanchev5991 6 місяців тому

    All this sounds very nice, but how these constructions will behave when we set "Don't keep activities" to ON in Developer options, which will make activity to be recreated every time when we bring test app from bg to fg. This will illustrate how the app will behave after the device goes to "doze" mode.

  • @user-qt4ct5eq1x
    @user-qt4ct5eq1x 7 місяців тому

    Nice topic bro

  • @LEEJIHUN
    @LEEJIHUN 7 місяців тому

    필립 센세 항상 감사합니다

  • @omkarpawar1741
    @omkarpawar1741 7 місяців тому +5

    why does android not provide a simple and optimal way to develop apps, instead of focusing on core development of what features our might need, developers have to worry about what to use for development, there are so many permissions to handle that it has become crazy, then with compose state and viewmodel, in viewmodel satetfllow, sharedflow, and the list goes on, and the pace at which these changes come makes all the code developed earlier redundant

    • @PhilippLackner
      @PhilippLackner  7 місяців тому +11

      I agree, but I think it's also incredibly easy to say that and hard to do. Making everything safe for users, easy for developers, easy to use for users, run as fast as possible is very complex and a lot of interests clash together.
      If developers would have a much more limited toolkit which is simpler to use, they'd again complain why they don't have fine grained control. It's just hard to please everyone to the fullest degree.

    • @Naxomiun
      @Naxomiun 7 місяців тому +2

      You dont need to use all these tools. In fact they are pretty useless if you know what are you doing. Most of these things can be replaced by common and 'old' patterns, which didnt have these problems btw

    • @omkarpawar1741
      @omkarpawar1741 7 місяців тому +1

      @@Naxomiun using old tools has the advantage of their reliability but there is always a risk of google deprecating them and bringing out half baked or totally different tools, the way SAF(Storage Access Framework) was handled then the epic moment when deprecations were deprecated and then brought back, now in compose handling a simple variable or an UI state is just a joke, compose state, view model, they could have just brought one viewmodel api and managed the one time events more effectively, some thing which has to baked in the OS itself is left to the developers, like handling denial of permissions with no Propper out of the box solution where developers have to provide their own logic, instead on working on a new feature for their app , imagine developing an app requiring camera from a service using notification where images and video permission are to be handled for api 32 and lower, api 33 and then api 34 and higher. wonder if the various teams developing different apis within google even communicate with each other where different "features" end up being a problem for developers

    • @Naxomiun
      @Naxomiun 7 місяців тому +1

      You will always be able to use bare threads instead of coroutines, service scoped classes instead of viewmodels, callbacks instead of flows or hand dependency injection instead of Hilt. Theres no way no one could ever deprecate those as they are part of the language. In fact, the possibilities of viewmodels/livedata/etc being deprecated are really high if you know about google historic changes in its APIs.

  • @hnim2292
    @hnim2292 7 місяців тому

    how about use StateFlow with Event class?

  • @codingCouncil
    @codingCouncil 7 місяців тому +1

    What about collectAsStateWithLifecycle ? . We can't provide the dispatcher.Main.immediate there can we ?

    • @codingCouncil
      @codingCouncil 6 місяців тому

      anyone wondering how to provide Dispatchers.Main.immediate with collectAsStateWithLifecycle; this is how
      val uiState by viewModel.uiState.collectAsStateWithLifecycle(context = Dispatchers.Main.immediate)

  • @rakam.a8070
    @rakam.a8070 7 місяців тому +1

    What's the difference between collecting state with MutableStateFlow and MutableSharedFlow? I usually use MutableStateFlow but I see other people use MutableSharedFlow as well, which one is recommended?

    • @PhilippLackner
      @PhilippLackner  7 місяців тому +2

      SharedFlow is NOT state, it doesn't cache anything by default. As the name says, only use StateFlow for state

    • @adrian110288
      @adrian110288 7 місяців тому +1

      The difference between the two is mainly in a way they behave upon subscribing to them. I think official docs explain it well, you can then decide which behaviour suits your case better 👍

  • @meetb26
    @meetb26 7 місяців тому

    if Mutable StateFlow and live data use so problem

  • @nowfarhan
    @nowfarhan 7 місяців тому +1

    Can you skip events when you use sharedflow with repeat=1? If you are using it for events, I think this is the easiest solution.

    • @vitalijuskolinko9011
      @vitalijuskolinko9011 6 місяців тому

      replay = 1 is equivalent of StateFlow :) You will be able to go back then.

  • @jeyhey5320
    @jeyhey5320 6 місяців тому

    For somebody who has always used a state-solution, anything else seems odd. Of course you need to handle state reset because it is a state. You can’t get around it no matter what you do. You also need to think about it in the other solutions.

  • @jaysh1175
    @jaysh1175 7 місяців тому +3

    Nice video, but to be honest I kinda fell over the argument against state being: "You always need to think about resetting the state [...] when I think of a larger team where the might be junior developers as well [...]" and then proceed to add the Dispatcher.Main.immediate which is equally 'not obvious' (or even worse) in my opinion

    • @PhilippLackner
      @PhilippLackner  7 місяців тому +9

      Yes, I agree that the immediate dispatcher isn't obvious either, but it's a single rule that has to be established in the team ("when observing events, ALWAYS use this ObserveAsEvents function").
      Resetting state is not so trivial, since it sometimes need to be reset and sometimes it's fine to leave it (such as when the previous backstack is cleared after navigation). Even though, I knew the mechanism behind resetting state, it happened to me more often than not that I forgot it. Especially, because one-time events on Android are almost always handled with channels/SharedFlows in Android projects, using state is a rather uncommon approach which is why I think this concept would be new to 95%+ of junior devs and would require an additional intro

  • @yuuzuX
    @yuuzuX 7 місяців тому

    If using presenter to update the ui?

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

    For some reason, the channel option will just be "collectable" once, as well as emitted once. I was hoping I could have 2 different isolated views react to this channel through the viewModel, but only the first view that collects the event will get the "message". I am switching back to state, since this is proven solution for precisely this scenario.

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

      What you're looking for is a sharedflow as the name says :)

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

      Memory-wise, is there any difference, improvement, best practice to pick sharedflow instead of state in a view controller? Are you saying sharedflow will also emit once, but can be heard by several views and not just one?

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

      @@TheRcfrias correct, it's like a channel that allows multiple subscribers

  • @alexeysimchenko7494
    @alexeysimchenko7494 6 місяців тому

    I definitely prefer a State to use

  • @leonljubicic545
    @leonljubicic545 7 місяців тому

    Incredible.Wonderful.Exceptional.

  • @danieldawson8018
    @danieldawson8018 7 місяців тому

    cmd + f, my friend. ;)

  • @eliezerbrasilian3881
    @eliezerbrasilian3881 7 місяців тому

    Good morning, do you have a video teaching how to mask input? I didn't find any tutorial teaching how to mask phone number, or even currency

  • @MonichGPT
    @MonichGPT 6 місяців тому

    You just need helper container with nullable field

  • @ka61er
    @ka61er 6 місяців тому

    :v i always use the third way

  • @HelloWorld-xt8wp
    @HelloWorld-xt8wp 7 місяців тому +1

    Excellent video. But isn't a coroutine context in viewmodels and in views already Dispatchers.Main.Immediate? I do not understand how this hack works.

  • @stoyan_vuchev
    @stoyan_vuchev 7 місяців тому +1

    I've got a notification about the video exactly when I was about to implement a navigation channel!
    Coincidence? I think NOT! 😂Thank you Philipp for the amazing content, keep up the Great work! 🙌🏼
    Here is a quick snippet of the function to observe such events in a lifecycle manner:
    @Composable
    fun CollectFlowWithLifecycle(
    flow: Flow,
    lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
    onCollect: suspend (T) -> Unit
    ) = LaunchedEffect(flow, lifecycleOwner.lifecycle) {
    lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
    withContext(Dispatchers.Main.immediate) {
    flow.collect(onCollect)
    }
    }
    }