Should You Use Compose State or StateFlow in Your ViewModels?

แชร์
ฝัง
  • เผยแพร่เมื่อ 21 พ.ย. 2024

ความคิดเห็น • 244

  • @hongyihuang6856
    @hongyihuang6856 6 หลายเดือนก่อน +6

    Only a true master can explain things so clearly. Unlike those fake tech leads we usually see in a company, you are truly a master who knows Android development inside out~

  • @geoff-huang
    @geoff-huang 9 หลายเดือนก่อน +7

    Newer versions of Android Studio don't have the square button to kill process. Instead, you can right click anywhere on the logcat tab and find a "Kill process" option

  • @ubersticks
    @ubersticks 2 ปีที่แล้ว +23

    Thanks Philip! I think this was needed. It was never super clear to me why you changed your general practice for ViewModel state and this explains it well!

    • @Alchemist10241
      @Alchemist10241 ปีที่แล้ว +1

      not only he showed us why we should use state flows in view models but also he showed us how to use them properly and covered most of the edge cases.

  • @mohammedalramlawi691
    @mohammedalramlawi691 2 ปีที่แล้ว +5

    Exactly man, I liked the point where we probably use some intermediate operators with Coroutines flow to simplify the logic.

  • @rafaelparanhos7435
    @rafaelparanhos7435 ปีที่แล้ว +4

    Wonderful video Philip but in the example there are few states. If we were talking about a complex screen that as multiples state wich is the best way to observe and update those changes?

  • @hosseinbanay9716
    @hosseinbanay9716 9 หลายเดือนก่อน

    I love your videos man.
    I wanted to become a mobile developer and decided to learn android instead of iOS because there are no iOS developers that have so many videos about almost every feature and can explain it in a such way that even a non programmer would understand it. Thank you Philipp

  • @radbaver
    @radbaver ปีที่แล้ว +5

    Amazing, thank you Philipp! This should be added as bonus/comment to the Building Industry-Level Apps With Multi-Module Architecture course :)

  • @oleg12395
    @oleg12395 ปีที่แล้ว

    Philipp, you are the best ! I read this themes on google 3 times )) You explained it in 10 minutes !!!! Thank you !!! Very very simple and easy to understand

  • @ebraratabay6836
    @ebraratabay6836 6 หลายเดือนก่อน

    If you weren't exist, I wouldn't know what I will do. Thank you! Your samples are very clear and explanatory. 👍✨🎉

  • @yasserakbbach7342
    @yasserakbbach7342 2 ปีที่แล้ว +20

    You nailed it as always 👌

  • @MichaelMossmanNZ
    @MichaelMossmanNZ 5 หลายเดือนก่อน

    Thank you Philipp for another great vid. I'm watching nearly two years after the fact, but I still found it very useful. It's interesting that you mentioned KMM, because that's the reason I'm learning Jetpack Compose in the first place. Once I get the hang of Compose, I also need to learn the iOS side of things! I'm hoping Swift/Xcode will be Compose-friendly by now =)

  • @RaghavSharma-nt3hr
    @RaghavSharma-nt3hr ปีที่แล้ว +3

    why its always difficult to use list inside observables. i would rather use compose state in ViewModel to reduce my stress levels.

  • @PointyMovie
    @PointyMovie 2 ปีที่แล้ว +3

    Yes, you can keep your view models in the shared module of the KMM project, and that's a good reason to avoid compose dependencies (unless you're going to use compose for ios, which is a bold decision). But. Swift doesn't know how to work with flows :) And you have to deal with some kind of wrappers.

    • @PhilippLackner
      @PhilippLackner  2 ปีที่แล้ว

      But you don't get to use AAC viewmodel then (which you don't have to)

    • @PointyMovie
      @PointyMovie 2 ปีที่แล้ว +2

      @@PhilippLackner Well.
      1) Create an expect abstract class ViewModel() in the common module
      2) android-specific actual abstract class ViewModel can inherit the AAC view model
      3) ios-specific actual abstract class ViewModel should implement viewModelScope - just create one
      4) inherit all your view models in the shared module from the one created on step 1
      ...
      5) profit!

  • @arfinhosain4969
    @arfinhosain4969 2 ปีที่แล้ว +1

    was looking for your explanation for this from the time you made the reel about this. Thanks for this content

  • @yossimaskin4473
    @yossimaskin4473 ปีที่แล้ว +1

    Thanks, Philipp! What would you do when the state is getting complex?
    1. Saving all the data to SaveStateHandle can be a too-heavy operation
    2. Combine is limited to 6 args and is not really meant to be used for a "Normal" case, As far as I know, the purpose is combining states from separate sources
    WDYT?

    • @yossimaskin4473
      @yossimaskin4473 ปีที่แล้ว

      ​@Sackhaarspalter Thanks. That was my conclusion too.
      so we can't apply the method mentioned in this video all the time. We should choose where it is necessary and choose the most lightweight value to save

    • @rafaelparanhos7435
      @rafaelparanhos7435 ปีที่แล้ว

      @yossimaskin4473 let's say we have a screen with 10 states. How you would do it? I have a project where we created a data class with all the atributes that are observable in the screen but when we update a single value in every place that the state is being observable the recompose occurs.
      So, when we have the states individual we assure that recompose only occurs when is needed.

    • @АлександрБауэр-о4й
      @АлександрБауэр-о4й 11 หลายเดือนก่อน

      You can take a source code of combine and write your own combine function with as many arguments as you want

  • @UsellesStudent
    @UsellesStudent ปีที่แล้ว +1

    Best channel about android development, keep it up!

  • @dipeshsaini04
    @dipeshsaini04 2 ปีที่แล้ว +4

    Great video for saving state in SaveStateHandler , also premium content is superb✌✌✌

  • @vit4mint685
    @vit4mint685 6 หลายเดือนก่อน

    Your channel has been so helpful. Been learning Jetpack Compose for a one-off personal use-case, and the quality and enthusiasm you emit in your content has me curious to expand my professional capacity to including App work.
    Best wishes towards your mentorship endeavors. Those that come across your channel are truly lucky.

  • @GilTheCohen
    @GilTheCohen 2 ปีที่แล้ว +4

    I actually extended MutableSnapshotState and mutableStateOf to create a persistent version of it - it automatically saves all values on each change (using SharedPreferences behind the scenes)

    • @ChrisAthanas
      @ChrisAthanas 2 ปีที่แล้ว +2

      Do you have a sample project that you would like to share?

    • @GilTheCohen
      @GilTheCohen 2 ปีที่แล้ว +1

      @@ChrisAthanas Not really. If I make one, I'll share it

    • @ChrisAthanas
      @ChrisAthanas 2 ปีที่แล้ว

      @@GilTheCohen sounds good

    • @deepakbisht4957
      @deepakbisht4957 ปีที่แล้ว

      Not a good approach to use shared preference.

    • @GilTheCohen
      @GilTheCohen ปีที่แล้ว

      @@deepakbisht4957 use any type of repository you would like

  • @firaszuriekat8341
    @firaszuriekat8341 2 ปีที่แล้ว

    Great video! I don't see the flow operators you referred to in the summary.

  • @khiariyoussef3226
    @khiariyoussef3226 2 ปีที่แล้ว +1

    I personally never thought of using compose state in the viewmodel, because that should be considered as part of the UI and the business logic, and is also compose-specific.

    • @PhilippLackner
      @PhilippLackner  2 ปีที่แล้ว +11

      The viewmodel is part of the UI

  • @ubersticks
    @ubersticks 11 หลายเดือนก่อน +1

    It seems extremely common these days to wrap UI states into a data class and the ViewModel exposes this to the UI as a flow. It doesn't look like SavedStateHandle is recommended for a state data class without serializing it or some other additional work.

  • @scottbiggs8894
    @scottbiggs8894 10 หลายเดือนก่อน

    Wow, in order to save a couple lines of typing, you only need to have exhaustive knowledge of the tiny inner details of some very esoteric code. Thank you, kotlin! I hope the junior programmers assigned to debugging my code two years from now will appreciate all the fine points and not just rip out my code and put something much easier to understand in its place. (Actually now that I think about it, in two years all this code will be obsolete as Google will have deprecated jetpack compose and stateflows in favor of the next new shiny thing.)

  • @0xmg
    @0xmg 2 ปีที่แล้ว +5

    Google recommending the experimental api over the stable one. Good old Android

    • @ChrisAthanas
      @ChrisAthanas 2 ปีที่แล้ว +2

      its still the wild west after 10+ years

  • @user-rz1hv
    @user-rz1hv 2 ปีที่แล้ว +2

    How would you save a complex object in savedstatehandle like uistate? Would you break it into several properties?

  • @jithind-feverx2818
    @jithind-feverx2818 ปีที่แล้ว

    Your awesome man, Make video about passing custom parameters (class,array) in compose navigation.

  • @SunAndMoon-zc9vd
    @SunAndMoon-zc9vd 3 หลายเดือนก่อน +1

    It suddenly became very Lovecraftian at 3:02. Cinoi´ - the less known Elder God :D

  • @hamzacanbaz9910
    @hamzacanbaz9910 2 ปีที่แล้ว +1

    your videos are very helpful Philip, thanks! Please keep it up.

  • @嘿嘿嘿-z1v
    @嘿嘿嘿-z1v ปีที่แล้ว +1

    Thanks for the clear explaination. Easy to understand and much more interested in jetpack compose :)

  • @joelcaetano6420
    @joelcaetano6420 2 ปีที่แล้ว +1

    I always want to give more thank one like. You are awesome. Thanks for another great video!

  • @umardev500
    @umardev500 7 หลายเดือนก่อน

    can you do tutorial "how to not re compose after back to screen" or navigatin tab if already openend in backstack

  • @gabrielbronzattimoro7953
    @gabrielbronzattimoro7953 ปีที่แล้ว

    Thanks Philip! Your examples are so clear

  • @tonnie7079
    @tonnie7079 2 ปีที่แล้ว

    Was always wondering which was the best to use between StateFlow and Compose State, Thanks to you now I know better.

  • @sadiqabdulazeez3631
    @sadiqabdulazeez3631 2 ปีที่แล้ว +3

    Can't you also make mutablestate survive process death by using remembersaveable?

    • @ubersticks
      @ubersticks 2 ปีที่แล้ว +1

      I think rememberSaveable {} only works in a Composable, therefore not in ViewModel.... but I could be wrong.

    • @ChrisAthanas
      @ChrisAthanas 2 ปีที่แล้ว +1

      @@ubersticks correct

  • @martynasp.7331
    @martynasp.7331 5 หลายเดือนก่อน +1

    UI State Flow backed by SavedStateHandle has to be the worst thing ever. SavedStateHandle is backed by Bundle, so the limit for data is 500Kb . So the ability to use SavedStateHandle as a source becomes really limited, and I would recommend against it. (Try to store some polygon data in there, imagine public transportation zones in a country/city)

  • @steven_mkn
    @steven_mkn 2 หลายเดือนก่อน

    for the compose state, why not use rememberSavable?

  • @aldredo5543
    @aldredo5543 ปีที่แล้ว +1

    I don't have the "terminal application" button in android studio, where can I find it?

  • @omkarpawar1741
    @omkarpawar1741 2 ปีที่แล้ว +1

    thanks for a great tutorial , your explanations are really helpful in understanding how to test our apps and all the problems we might face saving a lot of time for students like us.

  • @oliver_lauritsen_oa
    @oliver_lauritsen_oa ปีที่แล้ว

    But when should we use a non saved stateflow, and when should we use saved stateflow

  • @pablovaldes6022
    @pablovaldes6022 ปีที่แล้ว

    What about memory leaks. When you use State the Activity doesn't automatically unsubscribe from the State when it is destroyed.

  • @Jerry-of9zj
    @Jerry-of9zj 2 ปีที่แล้ว +2

    7:13 where is the link about the flow operators?

  • @ARIZONAMUSIC
    @ARIZONAMUSIC 2 ปีที่แล้ว +1

    Great accurate explanation. Applauding!

  • @kqvanity
    @kqvanity ปีที่แล้ว

    The official documentation of Jetpack compose uses MutableLiveData for private variables of the ViewModel, whilst you're Using MutableStateFlow. What's the catch?

  • @snow4dv
    @snow4dv 10 หลายเดือนก่อน

    Exactly what i searched for. thx!

  • @maximooze3196
    @maximooze3196 2 ปีที่แล้ว

    Thanks again @Philipp.

  • @NeoZondix
    @NeoZondix 5 หลายเดือนก่อน +1

    Should I use StateFlow or LiveData with Compose?

    • @akashkv41
      @akashkv41 2 หลายเดือนก่อน +1

      stateflow

    • @NeoZondix
      @NeoZondix 2 หลายเดือนก่อน

      @@akashkv41 thanks

  • @seifenspender
    @seifenspender หลายเดือนก่อน

    12:25 Why not include the savedStateHandle["color"] = color line in the private setter for the composeColor? This would solve the issue, right?

  • @TheShadowvaultAwaits
    @TheShadowvaultAwaits 7 หลายเดือนก่อน

    How does the viewmodel get the savedState if you are not passing it from the activity?
    Also how does this work when the state is a data class and not a single value?

  • @mesutemrecelenk5447
    @mesutemrecelenk5447 2 ปีที่แล้ว +1

    Thanks Philipp. Should we go on to use private val _state = mutableStateOf(LoginFormState())
    val state: State = _state
    usage? Or must we use MutableStateFlow instead of above usage moreover?

    • @GG9K71
      @GG9K71 2 ปีที่แล้ว +6

      It depends on where the data comes from, how it's persisted and who listens for it's changes, for example: if there is data coming from repository then StateFlow is better to be used, if the data is used only inside composable functions and is only related to the UI(selected item of radio button and etc..) than it's OK to use mutableStateOf

    • @mesutemrecelenk5447
      @mesutemrecelenk5447 2 ปีที่แล้ว +2

      @@GG9K71 thanks for your response. Actually we can use MutableStateFlow instead of livedata. For getting data from api or db and set them on the compose components we can use this
      private val _state = mutableStateOf(LoginFormState()) val state: State = _state

    • @GG9K71
      @GG9K71 2 ปีที่แล้ว +3

      @@mesutemrecelenk5447 Yes, that's right but if you have multiple collectors of that data, which are not composable functions, than composlabe state cannot be used.

    • @mesutemrecelenk5447
      @mesutemrecelenk5447 2 ปีที่แล้ว +2

      @@GG9K71 again thanks a lot 🙏

  • @sergi.adamchuk
    @sergi.adamchuk ปีที่แล้ว

    What about if the state is a list? And I am updating an item of this list or add/remove items. What is the best practice for this case? Especially if my list is big and I do not want to recreate a new list.

  • @ahmadsamimyadgary8223
    @ahmadsamimyadgary8223 ปีที่แล้ว

    Hi Sir! Do we really need ViewModel in compose if yes what are the advantages?

  • @osisuper98
    @osisuper98 2 ปีที่แล้ว

    Thanks to you I switched already 😅.

  • @denisk3852
    @denisk3852 ปีที่แล้ว

    How does "savedStateHandle" implement to ViewModel ? It is just calling without parameters in MainActivity.

  • @aikin89
    @aikin89 2 ปีที่แล้ว +1

    I was searching for this question for the last week and couldn't find a good answer

    • @PhilippLackner
      @PhilippLackner  2 ปีที่แล้ว +1

      Hope this is now a good answer 😁

  • @AungThiha-xj2bb
    @AungThiha-xj2bb 6 หลายเดือนก่อน

    Thank you Philipp

  • @spl420
    @spl420 10 หลายเดือนก่อน

    7:27 so you say we need something like derived state?

  • @LucaChrist-m5v
    @LucaChrist-m5v 2 หลายเดือนก่อน

    I get an error that the instance of my ViewModel can't get created. How do you do that. You don't provide anything for SavedStateHandle

  • @carlitosdroid
    @carlitosdroid ปีที่แล้ว

    Thanks Philip. However I don't have that `Terminate Application` button in latest version of Android Studio(Electric Eel). Do you have the same problem?

  • @user-rz1hv
    @user-rz1hv 2 ปีที่แล้ว +1

    Philipp do you know how to set one or multiple borders in compose? like we can do in css, because i didn't found anything helpful, you can basically set only all borders

    • @PhilippLackner
      @PhilippLackner  2 ปีที่แล้ว +1

      Not sure if there is an easy way yet, but for sure using Canvas

    • @user-rz1hv
      @user-rz1hv 2 ปีที่แล้ว +1

      @@PhilippLackner It's just insane how this simple thing may not to exist in compose, like it should be, because it fits in compose so naturally, but i think they'll definitely add it. And I checked out BorderStroke class and it seems to be pointless, because you can do it with border method on modifier

    • @PhilippLackner
      @PhilippLackner  2 ปีที่แล้ว +2

      @@user-rz1hv depending on your use case you can also use padding + background modifiers to achieve this:
      Modifier.background(borderColor).padding(start = 1.dp).background(backgroundColor)
      This will create a left border. Doesn't work if the background should be transparent though

  • @baudelioandalon
    @baudelioandalon 2 ปีที่แล้ว

    You're the Best, thanks man

  • @mieszkokozma4239
    @mieszkokozma4239 2 ปีที่แล้ว

    Really nice topic, thanks!
    Regarding reason 2,
    don't you think that keeping the whole state (let's say that it's a complexed state object) in SavedStateHandle as a bundle (has to be parsed) is an overkill compared to keeping just the id and refetching the data needed to recreated the state once a POD occurres?

    • @ChrisAthanas
      @ChrisAthanas 2 ปีที่แล้ว

      That's definitely one way to approach it
      And as you know
      Android has a dozen ways to achieve the same result and it's best to pick one and stick with it

    • @deepakbisht4957
      @deepakbisht4957 ปีที่แล้ว

      Well no one said you have to store the whole app state in savedStatehandle.
      Android documentation itself says it is used to store the minimal information that helps you to achieve the restoration of the whole controller.
      Suppose you are in a dentist screen for an id=5. So in that case you should save only the Id not the whole detail screen object. Using just Id you can get the data from the data from the repository...

    • @ldxyz-s1e
      @ldxyz-s1e ปีที่แล้ว

      @@deepakbisht4957 That depends on how slow data restoration is. If it is slow it is better to save it to state.

    • @deepakbisht4957
      @deepakbisht4957 ปีที่แล้ว

      @@ldxyz-s1e well idk what are you trying to say.
      But the Android document itself says you need to store it as minimal details to restore the whole screen state...

  • @JosefGuillen
    @JosefGuillen 2 หลายเดือนก่อน

    thank you

  • @mustafaammar551
    @mustafaammar551 2 ปีที่แล้ว

    Very cool video
    Thank You Bro

  • @georgemujuru1232
    @georgemujuru1232 2 ปีที่แล้ว +1

    Should you be using view models in Compose?

  • @RaghavSharma-nt3hr
    @RaghavSharma-nt3hr ปีที่แล้ว

    can someone please tell me how to do crud operation on list in StateFlow??

  • @xandrviking1113
    @xandrviking1113 ปีที่แล้ว

    Cool, thanks 👍

  • @karamba6936
    @karamba6936 5 หลายเดือนก่อน

    Is it still actual? I cann't find that button in logcat.

  • @annaberkovitch85
    @annaberkovitch85 6 หลายเดือนก่อน

    Hi, Philipp! I know this is a year-old post, but I have a question about saving complex data using SavedStateHandle:
    In my ViewModel I'm using a MutableStateFlow of a data class. Normally, when I want to update one of the properties, I'm using the update function inside which I'm doing, for instance, "_cprRecordingState.update { it.copy(category = newCategory) }.
    Let's say I use savedStateHandle with a key "recording_state", would it be correct to update the StateFlow as following: "savedStateHandle["recording_state"] = _cprRecordingState.copy(category = newCategory)"?

    • @PhilippLackner
      @PhilippLackner  6 หลายเดือนก่อน +1

      Technically you'd need to update it with savedStateHandle[...].copy(...) and then listen to the saved state handle as flow

    • @annaberkovitch85
      @annaberkovitch85 6 หลายเดือนก่อน

      @PhilippLackner yeah, that would make sense, and it's a hassle 😅 Would probably be better to remove the properties that need to survive the process death and use them as separate StateFlows.
      Thank you very much!

  • @vengateshm2122
    @vengateshm2122 2 ปีที่แล้ว

    Hi update android studio to dolphin version. The new logcat features are awesome and developer friendly.

  • @ahmadab9666
    @ahmadab9666 2 ปีที่แล้ว

    Thanks very useful.

  • @safionweb
    @safionweb 2 ปีที่แล้ว

    Superb!

  • @ChrisAthanas
    @ChrisAthanas 2 ปีที่แล้ว

    @8:20 Phillip uses a "stop button" in the logcat pane. I am using Android Studio Dolphin and I do not have this button. Am I forced to use the ADB commands to kill the app?

    • @ChrisAthanas
      @ChrisAthanas 2 ปีที่แล้ว

      Like this: adb shell am kill com.realityexpander.composestatevsstateflow

    • @PhilippLackner
      @PhilippLackner  2 ปีที่แล้ว

      If you're using AS Dolphin, they moved that button to some device manager tab (or similar)

    • @ChrisAthanas
      @ChrisAthanas 2 ปีที่แล้ว

      @@PhilippLackner thank you
      Looks like there is no option to kill for physical devices in the device manager pane. I can't find out what they did with it, I've been googling like crazy and can't find any mention of it.

    • @ericks6783
      @ericks6783 2 ปีที่แล้ว +1

      @@ChrisAthanas A workaround is to switch to the old Logcat. Go to Settings -> Experimental -> (Uncheck) Enable new Logcat tool window. Then the stop button will appear.

    • @ChrisAthanas
      @ChrisAthanas 2 ปีที่แล้ว +2

      @@ericks6783 yes but then I lose all the other features. Where did the stop button go?

  • @mv2e19
    @mv2e19 2 ปีที่แล้ว

    How is 7:00 better than derivedStateOf()?

    • @PhilippLackner
      @PhilippLackner  2 ปีที่แล้ว +1

      It's not better, it's just the StateFlow equivalent and you keep the state logic in the VM

    • @mv2e19
      @mv2e19 2 ปีที่แล้ว

      @@PhilippLackner gotcha, thanks!

  • @bofu9662
    @bofu9662 2 ปีที่แล้ว

    Thanks a looooooot

  • @jeckonly5853
    @jeckonly5853 2 ปีที่แล้ว

    thanks philipp! I learn a lot from your video!

  • @immortal_lnight
    @immortal_lnight 2 ปีที่แล้ว

    90

  • @immortal_lnight
    @immortal_lnight 2 ปีที่แล้ว

    75

  • @akashkv41
    @akashkv41 2 หลายเดือนก่อน

    if android kills the app, then be it. Its best to not create more complexity

  • @alexismandelias
    @alexismandelias ปีที่แล้ว

    3:35 almost clicked off the video at that point. When you're making content that people depend on for their projects please take care to be as informed as possible to provide proper advice
    Edit: clicked off at 7:30. You should provide more concrete examples of why I should do what you're showing me. The example of `color.map{}.stateIn()` is incomplete in so many ways, I left with more questions than answers

  • @immortal_lnight
    @immortal_lnight 2 ปีที่แล้ว

    60

  • @immortal_lnight
    @immortal_lnight 2 ปีที่แล้ว

    24

  • @immortal_lnight
    @immortal_lnight 2 ปีที่แล้ว

    30

  • @immortal_lnight
    @immortal_lnight 2 ปีที่แล้ว

    96

  • @immortal_lnight
    @immortal_lnight 2 ปีที่แล้ว

    28

  • @patrickrodriguez4683
    @patrickrodriguez4683 2 ปีที่แล้ว +1

    Google say we should not use architecture MVVM in compose. What is the correct way? Thank you very much

    • @ChrisAthanas
      @ChrisAthanas 2 ปีที่แล้ว

      it may be possible to get away from it one day but today is not that day.

    • @ubersticks
      @ubersticks 2 ปีที่แล้ว +1

      Where does Google say that? They recommend "UDF" unidirectional data flow, which MVVM certainly supports...

  • @immortal_lnight
    @immortal_lnight 2 ปีที่แล้ว

    42

  • @immortal_lnight
    @immortal_lnight 2 ปีที่แล้ว

    33

  • @immortal_lnight
    @immortal_lnight 2 ปีที่แล้ว

    20

  • @immortal_lnight
    @immortal_lnight 2 ปีที่แล้ว

    23

  • @immortal_lnight
    @immortal_lnight 2 ปีที่แล้ว

    86

  • @immortal_lnight
    @immortal_lnight 2 ปีที่แล้ว

    85

  • @immortal_lnight
    @immortal_lnight 2 ปีที่แล้ว

    10

  • @immortal_lnight
    @immortal_lnight 2 ปีที่แล้ว

    25

  • @immortal_lnight
    @immortal_lnight 2 ปีที่แล้ว

    5

  • @immortal_lnight
    @immortal_lnight 2 ปีที่แล้ว

    27

  • @immortal_lnight
    @immortal_lnight 2 ปีที่แล้ว

    100

  • @immortal_lnight
    @immortal_lnight 2 ปีที่แล้ว

    63

  • @immortal_lnight
    @immortal_lnight 2 ปีที่แล้ว

    21

  • @immortal_lnight
    @immortal_lnight 2 ปีที่แล้ว

    6

  • @immortal_lnight
    @immortal_lnight 2 ปีที่แล้ว

    8