You don't really need to instantiate the Viewmodel in the activity. Just do 2 composables; one as a container for the viewmodel injection and a stateless one. This way you can reuse the composables and are perfectly testables for previews and ui tests.
Thanks for the video ! Would not the view model in this case take the activity scope ! Would not it maybe be better to have 2 composables ? I mean here U have only one screen but with multiple composables with navigations l, I think this viewmodel will live longer than u need ! Correct me if I am wrong ?
Wanted to say exactly the same this. I have a MainScreen where the viewModel injects normally and in it I call the MainScreenContent where I pass the state I needed and MainScreenContent is where the drawing of the UI actually happens and I preview MainScreenContent. This way you also don't break multimodule apps for example.
@@fadiselim633 yes you are wrong. The viewmodel that was created in the composable was a part of nav entry once it is destroyed by clicking back the viewmodel will be destroyed with that composable containing a screen level component...
You can separate screen into two composables : screen and content (outer and bottom inner) and have both : viewmodel in the screen and state and working preview in content…
I was working on a sample project in Compose today evening. I was thinking of how i can de-clutter my constructor.and i got that youtube notifcation from your channel like a magic. Thanks so much man!
The state advice is really great! It just hit me with another idea tho. You can make an extention function above your screen composable, where you'd instantiate the view model. Then you'd just call this extension in the main activity navhost
Initializing the view model in the nav graph violates the single responsibility principle. The recommended pattern is the have a stateful screen composable that wraps a stateless screen composable.
This is very interesting I personally prefer to create a stateless container composable but what I hate about it is when you need to pass many states 😵💫
So what is the proper solution in this case? Pass multiple states and multiple callbacks? Also some states may be needed to other composables down in the hierarchy, will you pass them all? It seems that passing viewModel is much easier. Even if it doesn't allow you to build preview. May be we should think about mocking viewModel easily without passing all dependencies to it.
Something we've looked into is making a ***Route class which contains a ***Route function where we initialize the Viewmodel and any interfaces we need to allow the viewmodel to react to the screen states. We can also initialise the nav graph function in the same class, meaning it can be kept apart in its own feature nicely
Hi Philip, great video as always. Let's say your screen contains composables items that appear dynamically for example inside lazyColumn. Now each composable contains editable field with state which is passed to composable inside lazyColumn. How would you update state for each item individually? An obvious issue with this is approach is that if you change state at one item, the state will be updated for every item inside list.
Great video as always Philipp, thank you ! What i'm wondering is that are you refactoring all your old projects when you learn something new like that ?
Depends what you mean with old projects. I don't really have time to work on hobby projects, but in my freelance projects I do so if I have the time for that change. Sometimes other things have higher priority though
Hi Phillip, In your previous videos on building full-fledged apps, such as "Clean Architecture Cryptocurrency App", "CRUD Note App Using KMM" and others, you have passed hiltViewModel() right to the screens. What has changed during this time in compose/kotlin or was it bad behavior from the beginning?
It is always lovely to see your content, like lumos on our brains leaving it alight with more info. Anyway, have always been curious about the effect of state reducers. They use the copy function to create a new state which is passed to the screen itself. Does that not affect the performance of compose as the whole screen is always recomposed whenever there is even a minute state change?
So, in general, it's the idea that you should only pass as parameters the most minimal amount of data that the component needs to know. As a React developer myself, it's something that I've had to learn and use for a while now
We actually fixed this using ViewModels as interfaces (not ViewModels, but Components from Decompose to be exact). We have real implementations as well as mocks
I have been using MVI architecture in xml project, I can tell that there is no performance issue with that approach, even though I copy whole state after a single change. the view is not updating it's component for example text View if old and new value matches. In compose the other part of view is not even updating, this is the reason why MVI fits really well in compose, I am not sure tbh if MVI can be used in xml project, even if I like it really much, because it's really testable, understandable, there are not so many, livedata/stateFlows inside viewModel which are not repetitive, and you have effect for navigation, toast, and such kind of operations, basically 1 state and 1 event.
Hi Phillip, Creating this approach is beneficial for UI testing and for isolating screens. However, don't you think that instantiating the view model in the activity creates an instance of the view model that remains until the app is destroyed? How can we ensure that whenever the screen is decomposed, the associated view model is also freed?
I've encountered this problem before, although in a different use case. Singleton view models are really bad unless your users have hundreds of megabytes of spare RAM on their devices, and I've solved this problem in a StackOverflow post. Idk if Philipp allows links in his comments, so you'll have to search the question by name: "Compose - get the same instance of ViewModel inside and outside of Navigation Graph"
you can scope the viewmodel to a specific activity/fragment/nav destination by passing the owner to the viewModelStoreOwner parameter of the viewModel() function, for example to have your viewmodel cleared out when its associated nav destination is popped off the back stack, simply pass the navBackStackEntry to the viewModelStoreOwner argument, this binding process is done automatically when you instantiate the viewModel inside your screen level composable. which is what we usually do.
Just repeat the video until you get the answer. He instanciated the viewmodel in composable. When the composable is destroyed by using backpress or some other reason, the viewmodel will also be destroyed.
Hello, how do you manage the composable if it needs more data? I mean suppose my screen needs to get data from DB, so it has to collect it from a flow object, and it needs multiple data from kotlin data store. Is this possible with a single state object?
Other question about this. And in that case, how am I listening in the composable to set possible changes with the collectasstatewithlifecycle? Thank you so much!
There is still problem when i have to pass Channel flow events and ScaffoldState as parameters. How to resolve that? The only solution is to make that parameters nullable?
This is a classic example of why classes are not preferred over simple functions and data structures, viewmodels are a garbage can where you combine state and processing of data.
Could you please show how the MainState class looks like? I'm new to kotlin so I would like to see the whole structure of custom states since I don't reallt get it now :P
Hay , is initialize the Viewmodel on the mainActivity keep its Lifecycle Connected to the matched Screen in the Navgraph , in order that compose will match the life cycle to the screen in the navgraph and not to the main activity which alwase in the backstack?
And if you need a value from a web service, how could you read it without pass the view model? the normal observer from the activity, seems not too convenient, let me know!. Thanks
But what if we have multiple child coomposables that use viewModel and we are using compositionProvider to pass ViewModel through the composable hierarchy . In this case, we will still have to use viewModel in a child composable.
Not sure what is the benefit of onEvent plus sealed class instead of interface implemented by the view model with the methods. If you pass the view model in the interface parameter you don't need any of the on event switch logic. Another benefit with interface is that with composables that only need some functions then you can pass these directly.
I guess because large interfaces create much more boilerplate if you instantiate them, since you have a whole function for every single case instead of just a when branch, so they pollute your NavHost and you can't just pass viewmodel::onEvent
Hey Philipp what do you think about passing viewmodel through local composition provider to use it throughout the screen and get the providers value on a nested composable through default params. Any thoughts?
Please you can build an application with responsive design depending on the size of the screen, implementing portrait and landscape mode and implementing multiple window(portrait but half screen), I need that guide 😭
So two questions. You originally promoted DestinationsNavigator by Ramcosta. How would that work with this architecture. Also, this looks great on the first activity which is called from the main activity, but how do you create the next one? Surely that viewmodel would need to be created on the main page?
I think you will need to create some layer between the screen and activity. When it gets the viewModel, it maps viewModel data to the screen state and puts it to the screen component. It divides navigation and fixes the previous problem.
@@PhilippLackner what about return values? say, viewmodel , has func1(): String, func2(): Int, how would you define the onEventHandler: (HomeScreenEvent) -> String that you pass to the composable? and then you have fun3() that has no return value, what does that return? I guess, you could have a return type Any? so, in your composable you would pass onEventHandler: (HomeScreenEvent) -> Any?
Which android studio version are you using? Because I am using latest version of flamingo on M1 Pro, and I have enabled Live Edit feature but still i have to rebuild every time I change anything in the composable. Otherwise it gives me that rendering problem.
Using a master state class defeats the purpose of the scoped/optimized recomposition feature of Jetpack Compose. Only use something like this if individual fields can't change on your screen independently. Otherwise use separate states for each field so that your screen doesn't fully recompose when a single field changes.
Jetpack compose is smart enough to determine what changed and what didn't, as long as you apply state hoisting best practices and pass composables only what they need, you don't need to worry about composables being recomposed with the same state. Just use a master state class and don't worry about performance issues, compose will handle the rest.
Lol I think you first need to understand how exactly Composition and recomposition works and then jump into coding. The Android team themselves said Compose is smart enough to re-render only that part of the UI whose state changes. So it is better to do state hoisting and at screen level you get all the states and then breaks the state and pass into low level components by passing required data instead of passing a big state object...
Because then you have the same problem with the preview and UI tests I mentioned. Your screen composable just shouldn't know about the viewmodel it uses if you want the preview to work
@@ficc666 nonono, I sorry, I’m not a native speaker, I don’t have a native sense of what my words could be offensive but I was learning programmig also a time ago. Stupid questions doesn’t exist, only exists stupids that doesn’t ask questions.
@@PhilippLackner are you sure? When I call state.update { it.copy (value1) } assuming original state is (value1, value2). Even though I only updated value1, composable who is observing value2 will recompose as well.
You don't really need to instantiate the Viewmodel in the activity. Just do 2 composables; one as a container for the viewmodel injection and a stateless one. This way you can reuse the composables and are perfectly testables for previews and ui tests.
Sure that works as well 🤌🏼
I agree this approach is better, because in this case only MainScreen knows which ViewModel it depends on.
Thanks for the video ! Would not the view model in this case take the activity scope ! Would not it maybe be better to have 2 composables ? I mean here U have only one screen but with multiple composables with navigations l, I think this viewmodel will live longer than u need ! Correct me if I am wrong ?
Wanted to say exactly the same this. I have a MainScreen where the viewModel injects normally and in it I call the MainScreenContent where I pass the state I needed and MainScreenContent is where the drawing of the UI actually happens and I preview MainScreenContent. This way you also don't break multimodule apps for example.
@@fadiselim633 yes you are wrong.
The viewmodel that was created in the composable was a part of nav entry once it is destroyed by clicking back the viewmodel will be destroyed with that composable containing a screen level component...
You can separate screen into two composables : screen and content (outer and bottom inner) and have both : viewmodel in the screen and state and working preview in content…
Wie gewohnt klasse erklärt und zum richtigen Zeitpunkt. Danke für deinen super content, Philipp!
Gerne!
I was working on a sample project in Compose today evening. I was thinking of how i can de-clutter my constructor.and i got that youtube notifcation from your channel like a magic. Thanks so much man!
Wow. After this video, I'll have to do a little update on my project kk. thanks philip
The state advice is really great! It just hit me with another idea tho. You can make an extention function above your screen composable, where you'd instantiate the view model. Then you'd just call this extension in the main activity navhost
Initializing the view model in the nav graph violates the single responsibility principle. The recommended pattern is the have a stateful screen composable that wraps a stateless screen composable.
Recommended by whom? Share some links if you have some 🙏
This is very interesting I personally prefer to create a stateless container composable but what I hate about it is when you need to pass many states 😵💫
So what is the proper solution in this case? Pass multiple states and multiple callbacks? Also some states may be needed to other composables down in the hierarchy, will you pass them all? It seems that passing viewModel is much easier. Even if it doesn't allow you to build preview. May be we should think about mocking viewModel easily without passing all dependencies to it.
Something we've looked into is making a ***Route class which contains a ***Route function where we initialize the Viewmodel and any interfaces we need to allow the viewmodel to react to the screen states. We can also initialise the nav graph function in the same class, meaning it can be kept apart in its own feature nicely
Hi Philip, great video as always.
Let's say your screen contains composables items that appear dynamically for example inside lazyColumn. Now each composable contains editable field with state which is passed to composable inside lazyColumn. How would you update state for each item individually? An obvious issue with this is approach is that if you change state at one item, the state will be updated for every item inside list.
thank youuuuuuuuuu you are the best my friend , you just solve my biggest problem
This is exactly what you do in one of your courses, I am confused because I thought it was a good practise.
Great video as always Philipp, thank you ! What i'm wondering is that are you refactoring all your old projects when you learn something new like that ?
Depends what you mean with old projects. I don't really have time to work on hobby projects, but in my freelance projects I do so if I have the time for that change. Sometimes other things have higher priority though
thanks, I've been thinking about it recently
Hi Phillip,
In your previous videos on building full-fledged apps, such as "Clean Architecture Cryptocurrency App", "CRUD Note App Using KMM" and others, you have passed hiltViewModel() right to the screens. What has changed during this time in compose/kotlin or was it bad behavior from the beginning?
It is always lovely to see your content, like lumos on our brains leaving it alight with more info. Anyway, have always been curious about the effect of state reducers. They use the copy function to create a new state which is passed to the screen itself. Does that not affect the performance of compose as the whole screen is always recomposed whenever there is even a minute state change?
So, in general, it's the idea that you should only pass as parameters the most minimal amount of data that the component needs to know. As a React developer myself, it's something that I've had to learn and use for a while now
Great content.
You could have also show quickly how the MainEvent and MainState classes look like.
Lol that was not the purpose of the video...
It has to do with the MVI architecture...
Great. What if we want to listen to flow channel events as well.
We actually fixed this using ViewModels as interfaces (not ViewModels, but Components from Decompose to be exact). We have real implementations as well as mocks
I have been using MVI architecture in xml project, I can tell that there is no performance issue with that approach, even though I copy whole state after a single change. the view is not updating it's component for example text View if old and new value matches. In compose the other part of view is not even updating, this is the reason why MVI fits really well in compose, I am not sure tbh if MVI can be used in xml project, even if I like it really much, because it's really testable, understandable, there are not so many, livedata/stateFlows inside viewModel which are not repetitive, and you have effect for navigation, toast, and such kind of operations, basically 1 state and 1 event.
extremely helpful
Hi Phillip,
Creating this approach is beneficial for UI testing and for isolating screens. However, don't you think that instantiating the view model in the activity creates an instance of the view model that remains until the app is destroyed?
How can we ensure that whenever the screen is decomposed, the associated view model is also freed?
I've encountered this problem before, although in a different use case. Singleton view models are really bad unless your users have hundreds of megabytes of spare RAM on their devices, and I've solved this problem in a StackOverflow post. Idk if Philipp allows links in his comments, so you'll have to search the question by name: "Compose - get the same instance of ViewModel inside and outside of Navigation Graph"
you can scope the viewmodel to a specific activity/fragment/nav destination by passing the owner to the viewModelStoreOwner parameter of the viewModel() function, for example to have your viewmodel cleared out when its associated nav destination is popped off the back stack, simply pass the navBackStackEntry to the viewModelStoreOwner argument, this binding process is done automatically when you instantiate the viewModel inside your screen level composable. which is what we usually do.
@@joeyeager5941 thanks brother
@@CalamityDeadshot tq
Just repeat the video until you get the answer. He instanciated the viewmodel in composable. When the composable is destroyed by using backpress or some other reason, the viewmodel will also be destroyed.
I hope you will make a video how to use Circuit to do state management
Hello, how do you manage the composable if it needs more data? I mean suppose my screen needs to get data from DB, so it has to collect it from a flow object, and it needs multiple data from kotlin data store. Is this possible with a single state object?
Other question about this.
And in that case, how am I listening in the composable to set possible changes with the collectasstatewithlifecycle? Thank you so much!
Happy Coding 😊
There is still problem when i have to pass Channel flow events and ScaffoldState as parameters. How to resolve that? The only solution is to make that parameters nullable?
I hope to make a video about the state class
what about the NavController in the Composable Screen constructor as almost all on the screen need a navController
Wow, there seems to be a lot of pitfalls involved with using Compose
Brilliant, make more short videos
This is a classic example of why classes are not preferred over simple functions and data structures, viewmodels are a garbage can where you combine state and processing of data.
Lame
Very Good video.
Now a request. Could u teach us how to create Widgets with Jetpack Compose and how to make Notifications for our apps, please ? :D
Could you please show how the MainState class looks like? I'm new to kotlin so I would like to see the whole structure of custom states since I don't reallt get it now :P
How about setting the hiltview model as default in the main screen and creating another main screen in the form of overloading?
Same thing.
You can do both things...
There is so late for this video.. :) I have such kind of questions about year ago.. stackoverflow resolved it 😊
Do you have any app example of how it is done? Really struggling with this compose state passing.
Hay , is initialize the Viewmodel on the mainActivity keep its Lifecycle Connected to the matched Screen in the Navgraph , in order that compose will match the life cycle to the screen in the navgraph and not to the main activity which alwase in the backstack?
How to get channel event with this approach?
I converted the CRUD notes app in your videos to this - but how do I use the navController from the viewModel?
And if you need a value from a web service, how could you read it without pass the view model? the normal observer from the activity, seems not too convenient, let me know!. Thanks
where the MainState come from ?
Hi Philipp , are you still using Compose Destinations lib ?
It's cool, but how to handle onLaunch events? Like navigation or show error?
Could you share the repo with the code that you shown in this video please?
what about sharedflow if we need to make one time action on same approach
But what if we have multiple child coomposables that use viewModel and we are using compositionProvider to pass ViewModel through the composable hierarchy . In this case, we will still have to use viewModel in a child composable.
Hey philip , what if i pass the viewmodels via composition locals ? Would you recomment that ?
Not sure what is the benefit of onEvent plus sealed class instead of interface implemented by the view model with the methods. If you pass the view model in the interface parameter you don't need any of the on event switch logic. Another benefit with interface is that with composables that only need some functions then you can pass these directly.
I guess because large interfaces create much more boilerplate if you instantiate them, since you have a whole function for every single case instead of just a when branch, so they pollute your NavHost and you can't just pass viewmodel::onEvent
Hey Philipp what do you think about passing viewmodel through local composition provider to use it throughout the screen and get the providers value on a nested composable through default params. Any thoughts?
Breaks the preview and has the same issue I mentioned
I have a doubt that, Assume I have 10 compose screens in the activity. Do I need instantiate 10 viewmodels in the activity according to this pattern?
Please you can build an application with responsive design depending on the size of the screen, implementing portrait and landscape mode and implementing multiple window(portrait but half screen), I need that guide 😭
Lol why are you crying?
@@deepakbisht4957 lol, it is not literal. I was not crying, in my country use emojis ironically
Does anyone know where repository is? 😞
dependency can be injected by hilt..
Bro! NOT is more permited recording screen ir THE Android device? I am confuse
So two questions. You originally promoted DestinationsNavigator by Ramcosta. How would that work with this architecture.
Also, this looks great on the first activity which is called from the main activity, but how do you create the next one? Surely that viewmodel would need to be created on the main page?
I think you will need to create some layer between the screen and activity. When it gets the viewModel, it maps viewModel data to the screen state and puts it to the screen component. It divides navigation and fixes the previous problem.
I guess one issue with this is that you end up with 20 callbacks passed to the composable.
No, just one
@@PhilippLackner what about return values? say, viewmodel , has func1(): String, func2(): Int, how would you define the onEventHandler: (HomeScreenEvent) -> String that you pass to the composable? and then you have fun3() that has no return value, what does that return? I guess, you could have a return type Any? so, in your composable you would pass
onEventHandler: (HomeScreenEvent) -> Any?
Which android studio version are you using? Because I am using latest version of flamingo on M1 Pro, and I have enabled Live Edit feature but still i have to rebuild every time I change anything in the composable. Otherwise it gives me that rendering problem.
I'm using electric eel, but preview tooling is still just trash atm
Thanks for this great tip. Can you suggest how can we pass navController when we need it in the main screen. It breaks my preview
Don't pass it and use callbacks instead
Please, can you tell me what processor you have?
M2 Max
Nice video ever 🥰
What prize sir ?
Just kidding 😂
Give the prise to next person please i am ok 😉
Using a master state class defeats the purpose of the scoped/optimized recomposition feature of Jetpack Compose. Only use something like this if individual fields can't change on your screen independently. Otherwise use separate states for each field so that your screen doesn't fully recompose when a single field changes.
Jetpack compose is smart enough to determine what changed and what didn't, as long as you apply state hoisting best practices and pass composables only what they need, you don't need to worry about composables being recomposed with the same state. Just use a master state class and don't worry about performance issues, compose will handle the rest.
@@joeyeager5941 Not correct. You have to use individual states inside the view model or your master class. Unlike the solution in the video.
@@st4849 Sorry, I didn't understand. What part of my answer that is incorrect?
Lol I think you first need to understand how exactly Composition and recomposition works and then jump into coding.
The Android team themselves said Compose is smart enough to re-render only that part of the UI whose state changes.
So it is better to do state hoisting and at screen level you get all the states and then breaks the state and pass into low level components by passing required data instead of passing a big state object...
Compose detects which fields have changed and which didn't and will only recompose what needs to be recomposed. Try it out ;)
okey, but now I can't send navigation params. There is any workaround?
I don't see why you can't pass them
Why you don’t init your viewmodel with hiltviewodel inside an composable fun? Then you don‘t need to set an parameter as state.
Because then you have the same problem with the preview and UI tests I mentioned. Your screen composable just shouldn't know about the viewmodel it uses if you want the preview to work
@@PhilippLackner I see. Thanks a lot!
what about the navigator?
Use DI for navigator or just place it in nav graph only. Don't pass it into screen level components...
Hi Philipp,
You mentioned MVI at the beginning, but does it matter whether it is MVI or MVVM? Thanks!
He just used to say “MVI” because in the application implementation uses a ViewModel, so MVI and MVVM are comparable equals in this case
@@darwinspace I assumed it would be a stupid question, but thanks :)
@@ficc666 nonono, I sorry, I’m not a native speaker, I don’t have a native sense of what my words could be offensive but I was learning programmig also a time ago.
Stupid questions doesn’t exist, only exists stupids that doesn’t ask questions.
@@darwinspace Nah, you didn't write anything like that. It was rather a comment on my question rather than on your answer. 😎
Amazing
Unfortunatelly isn't possible with Koin.
Why?
Great video. Though all of the children composable will recompose again on every state change. Sorry, but that doesn't seem the best approach.
No they won't, compose is smart enough to detect which values changed, even when part of a data class :)
@@PhilippLackner are you sure? When I call state.update { it.copy (value1) } assuming original state is (value1, value2). Even though I only updated value1, composable who is observing value2 will recompose as well.