Full Guide to Dependency Scopes with Dagger-Hilt - Android Studio Tutorial

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey guys and welcome back to a new video in this video i will talk about dependency scopes using dagger hilt recently actually posted a short on youtube in which i told you that you should always use the minimum required scope when using dagger hill and when reading through the comments i felt like for many people that wasn't fully clear why you should do that and what that actually means so here is a more detailed video in which i will actually tell you why you should always use that minimum required scope and not make all your dependencies a singleton like i see so often in people's repositories so that won't be a typical coding tutorial here you don't need to do anything you can just lean back and learn i will just show you some different things here in my sample project starting in this app module that's a very common thing we have on android just an app yeah application module singleton component and the dependencies we provide in that module will also be singletons so that's actually the scope that means these dependencies will live as long as the application does and there will only be a single instance of that throughout the whole application's lifetime and if we take a look in here in that session timer class that i provide then that is just a very very basic sample timer here we have an is running boolean in the session time variable and then the start timer function in which we simply increase that session time once a second and we also can stop this timer here we just set is running to false so we break out of this loop here nothing very special just for demonstration here and that in and of itself is a good thing to do to actually put this logic into its own class you can call it session timer so you just follow that single responsibility principle that this class has the clear responsibility of actually managing this hammer logic however what's now the issue that we make this a singleton the issue is that making it a singleton on the one hand there are actually two issues on the one hand that means that this instance this session timer instance here will occupy memory as long as your application lives so let's say you are working on a video calling app that is actually the example i want to use here and for that very simple one a simple video calling app you actually have a login screen you have a video calling screen and you might have like a call info screen where you just get some extra information about the call and you need the session timer on the video call screen and on the call info screen but not really on the login screen because the session time isn't really going to be displayed there however if you make this a singleton this will also occupy memory on the login screen but wouldn't it just be better if we could only keep this instance alive if the user is actually on either the video calling screen in an active video call or on the call info screen where they would also probably be active in a call for sure that would be better that is one issue but i think the second issue and the even bigger issue that many people don't even think about is that the current way of doing that of making this a singleton can very easily lead to bugs because what we essentially have here is a stateful singleton stateful singleton simply means here that we have a singleton with state so with a value that can change over time which is this session time and this is running would actually also be considered state and you probably already have heard that you should not use global variables or you should be careful with global variables at least because they can quickly lead to bugs having stateful singletons is essentially using a global variable because these variables live as long as your application does so what specifically can lead to these deadly bugs that i talked about here for that let's actually take a look at our different screens i prepared a navigation graph here just for pure visualization so we have our login screen with our video call screen in our call info screen so here our application will obviously start the user will log in and then they can join a call and actually get information about that call and these two screens we actually need our session timer because here the video call is actually going on however our singleton our stateful singleton is actually living as long as all of these screens so it will also be active in the login fragment so what will happen here is the user will go to the login fragment or login screen doesn't matter if you use compose or xml they will log in they will get to the video call fragment where they can actually join a call and here we will actually also call the start timer function when they actually start the call obviously because then we want to track the time during the call however then if they actually leave the call we would obviously call the stop timer function which would break out of this loop they would go to maybe some kind of other screen and eventually get back to the login fragment where they can also log in for another call or you could also think of some kind of invitation screen or so where you just need to enter some kind of code to join a call um so that is what would happen the thing is if they already joined a call and then left the call and then joined another call what would happen is in this case the timer would simply start from the time where they left the first call because as you noticed i forgot to actually reset the timer here in stop timer what we simply do is reset is running to false which will stop the timer of course but that doesn't reset the session time the session time will still be at the value where we actually left the first call so that means if the first call was for 60 seconds then we left the call join the next call then the session time will start at 60 seconds because we use a singleton here the this value here lives as long as our application does and it doesn't care about if the call actually stopped or not and of course we could do something like set the session time back to zero here that would work of course but there are very many scenarios in real applications that are much more complex than this and where it's not so obvious what kind of states you actually need to reset in which scenarios because you don't always have this obvious stop timer function so in these cases you would need to provide some kind of release function or so that just releases all the dependencies in this stateful singleton which then again is not obvious that you actually need to call this maybe for you it's obvious if you write this code but if someone actually is new to your team for example they won't know that they actually have to call this release function when they navigate out of the caller whatever you're building so now that you hopefully understood the problem let's talk about the solution and the solution is not using the singleton scope here because this dependency is not needed for all screens wouldn't it be much better if we could scope this dependency only to these two screens where it's needed because then that would mean that as soon as we leave the call the dependency would be destroyed and as soon as we start a call that the dependency would be created and that means if it's created nearly that since this object is just a new object it will have the default value so we don't need to worry about previous values that we need to reset these values because as soon as we create a new session timer instance the session time will start at zero and is running will start at false so no matter what kind of complex state you have on this object you can be sure that when you need the object the state will actually be the default one so let's talk about in particular how we can achieve this usually when i talk about the minimum required scope you want to scope your dependencies to a specific view model and that is luckily very easy with daggerhild so we don't want to use singleton here actually we don't even need to specify the scope directly here we can just go to our app module or create some kind of other module replace the singleton component with viewmodel component and replace the singleton scope with a viewmodel scoped and that would now mean that if you inject this dependency into a view model then the dependency will live as long as that specific v-model's life cycle is so if that view model is for example bound to a fragment then that would mean that the dependency lives as long as that fragment is actually on the back stack or if the view model is actually bound to an activity it would live as long as that specific activity is on the backstack so until the user actually presses the back button and kind of pops that activity or fragment from the back stack so we would then have something like this session view model i created here where we just inject such a session timer and since we provide this here in our app module in view model scope and we inject it in a view model this session timer instance will live as long as that specific view model so as soon as this view models on cleared function is called the instance of this session timer will also be destroyed and when we then go back to the screen whether through model is actually bound to it will simply create a new session timer instance which it will then inject into this view model but if you remember and we take a look at our navigation graph we actually need this dependency across two screens so we kind of need to share it so between two fragments in this case how can we do that so there are actually two ways of doing that let's talk about way number one and that is we can scope the viewmodel to the activity where these screens are actually in so if you would have some kind of auth activity for login and registration then the login fragment would be in there and you could have some kind of video call activity where you put in these two screens and then you scope the session view model to this video call activity which hosts these two fragments and then you can be sure that this dependency this session timer actually kind of lives as long as your two fragments in which you need them or actually live as long as the application which hosts these fragments however that doesn't always work very often we actually use something like a single activity architecture that you just have one single activity in your project and if you then scope a dependency to that activity then it's pretty much a singleton because there is only one activity also if you're using jetpack compose like i like to do it then you also likely only have one single activity because then you have the set content function and in that you just put all your code you use composables you don't use things like fragments with jetpack compose at least if you're just purely using jetpack compose in your project so how can you do that if you just have one activity but you still want to be able to scope the dependency to scope the session of your model only to two screens and the solution is a third way of scoping overview models and that is scoping it to the navigation graph let's talk about that on the one hand if we are using xml then we can use something like a nested navigation graph which is this one here um actually this well we just specify the screens that belong together between which we want to share a dependency so in our case here in our very simple example just the video call fragment and the call info fragment so these two screens belong together we want to share our session timer between these and now we want to be able to scope a view model just to these two screens so as soon as the user leaves that kind of navigation graph here we want to destroy the view model and its dependencies what we can then do is here in navigation graph two we can this could be our main navigation graph for example where we have our login screen and then we can use a nested graph so we can just go here to plus and include such a graph here if you actually have that new project and if we use such a nested navigation graph then we can also scope a view model to the lifetime of this graph so as long as the user is actually in one of these graphs screens let's see how that works in my activity and i will of course also show you the compose way of doing that or actually not in main activity by the way since we are using fragments here um let's open this video called fragment here where which basically belongs to this nested graph here so we have a video called fragment called infofragment and we're going to go into both these fragments vehicle fragment and we want to say private valve view model is of course a session view model and we say by nav graph view models and here we simply pass the id of our navigation graph which is our navigation dot nested graph and that way we effectively get this session viewmodel instance that is scoped to this nested graph so if we actually take this line of code and also put it in our call info yeah calling for fragment then this fragment and this fragment will get the same instance of the view model but the login fragment will not get this viewmodels instance so that means we have effectively scoped the viewmodel to our two fragments that are both in the same activity and since we scope the viewmodel to these two fragments we also scope these dependencies that are inside the viewmodel to these two fragments since yeah we simply inject them in the constructor and that was provided in viewmodelscoped so that's how that works with xml let's take a look how that works with jetpack compose for that we need to go to main activity and by the way before i tell you that i want to also show you that we that actually included dependencies to have this nav graphing models so these are the dependencies i used of course dagger healed navigation component just for the nav graph stuff um and i think from one of these fragments i assume from the fragment one you get this nav graph view models dependency or from these extensions and here these dependencies are needed for what comes with compose now let's go to main activity and actually have our nav host and first of all the clear nav controller remember nav controller we have our nav host where we pass this nav controller and instead of the graph we specify a start destination which could be anything here start destination we open this block of code and usually you put you would put in your single screens here as these composable blocks but since we want to scope a dependency to multiple screens that belong together we want to also use kind of a nasty graph here which we can do with navigation so we say navigation and now this nested graph also has its own start destination so just the screen that should show up at first when we navigate to this graph which could be something like nested screen one or let's take our example here that would be our video call screen and the route of that would be um something like video graph for example so that is how we would start to navigate to this graph and then inside of this graph we have the start destination or video call screen so in here we can now put our composable blocks this one will get a route the route will be video screen a video called screen i actually called it and here we get a backstack entry that is interesting for us because with that backstack entry we actually get information about the kind of the life cycle of these different screens and with a backstage entry we can also scope our view model to these to this specific graph if we want to retrieve a viewmodel's instance in compose and we're using taker hild we do it like this so we say view model is equal to hill view model and that also comes from a dependency um i will actually just push this to github and you can copy paste the dependencies for your project we also use session view model and that's it so that's how we retrieve a view model in instance in compose what kind of lifecycle will this viewmodel instance now actually have if we just use the function like this well i think if we use it like this it should be scoped to whatever is the parent of our compose composable here of the setcontent function which would be the activity in this case so all the viewmodels would be scoped to the activity if you're using that in a fragment it would be scoped to the fragment but what we actually want to do is we want to use this backstack entry to give it information about a specific lifecycle so we could pass this backstack entry in here what this would do is it would simply scope the session view model just to this screen just to this specific backstack entry so if you want to scope a viewmodel just to one screen in jetpack compose this is how you would do it however we want to scope the viewmodel to two screens since we want to share the session timer instance between these two screens how do we do that well we will go ahead and say val parent entry is equal to remember import remember and here in parentheses we say backstack entry by doing that by passing backstick enter here we basically accomplish that whatever we put inside of this remember block will be re-executed whenever backstage entry changes so in here we're going to say backstack and actually no nav controller dot get back stack entry and we want to now specify a route of the backstack entry we want to get which backstack entry do we want to get well we want to get the backstage entry of our nested navigation graph which in the end is this video graph so we pass our video graph here and then yeah whenever this backstage entry actually changes we save this parent backstage entry in this variable which you can now pass here for our review we can then copy the screen paste it and have our call info screen here and in the end that's just the same now so now these two view models will essentially be the same instance so that's how this stuff would work with jetpack compose i hope that wasn't too complex here this is definitely a more complex topic and i hope it got clear why this is really an issue it's not just about memory that is one concern but the main concern i see is that you have this stateful dependency that you share across your whole application and then it's often an issue that state doesn't properly get reset i ran into so many bugs because i did that in my earlier days so i thought i will share this with you of course if you're using your dependencies outside of a view model then you can also use different scopes very rarely i actually need to directly provide a dependency in a fragment itself or an activity itself don't actually know when you would do that but if you do that you could use something like a fragment component what's more likely is that you inject a dependency into a service for example then you could use a service component to just scope it to that service but in most cases you either use singletons or view model components here and of course i don't want to raise the impression that you should never use singletons that's absolutely not what i'm trying to say here use singletons but only if you really need the single instance across your whole application's lifetime and especially if these if the singleton is stateless like for example a retrofit instance um that doesn't contain some kind of values that can change then you also eliminated the aspect that you can run into very deadly bugs but the more you actually need your dependency to be shared across multiple screens the more you should take care of actually scoping it the right way so i hope this video helped some of you and actually clarified what i quickly explained in my short a little bit if so definitely let me know that down below and if something is unclear also let me know that below and i will um hopefully find time to reply to your comments apart from that if you want to see some other specific topics please also just let me know that in the comments and if they actually get some upvotes that i can see that many people are actually interested in that then i will of course make a video about that as well thanks so much for watching have an amazing rest of the week and i'll see you back in the next video bye bye you
Info
Channel: Philipp Lackner
Views: 19,856
Rating: undefined out of 5
Keywords: android, tutorial, philip, philipp, filipp, filip, fillip, fillipp, phillipp, phillip, lackener, leckener, leckner, lackner, kotlin, mobile, dagger, hilt, dependency injection, inject, singleton, navigation graph, scope, scoping, singletoncomponent
Id: 465JBv3g6XI
Channel Id: undefined
Length: 20min 40sec (1240 seconds)
Published: Sun Jul 31 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.