The Ultimate Guide to Koin (Dependency Injection with Kotlin) - Android Studio Tutorial

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey guys ando welcome back to a new video a new course about dependency injection this time using coin a lot of people wanted me to make a video course about coin i already have one about dagger hilt which is a very common dependency injection library for android and now it's time to actually make a video about coin as well so you can watch both of these courses and then decide which one you actually like more and i will actually structure this in a way um that you don't have to know anything about the parent injection to follow through this and because of that i actually want to start talking about what dependency injection actually is because most people actually over complicate it it's it's really not complicated if you think about it in the end the penalty injection is really just giving an object its instance variables so let's think about that a little bit giving an object its instance variables so an object is any type of class like an instance of a class um and we pass some variables to that object for example in the constructor that's dependency injection that's really it um and the name dependency injection let's think about that for a bit so we give an object it's instance variables this giving or this passing of variables is called injecting and the variables or objects we pass to another object are called dependencies because the object depends on these objects that we pass i think that makes sense and let's now think about why that's actually helpful because that's really one of the design patterns that i would really use in every project there is no scenario i can think of where i would say no dependency injection using that would be too overkill here i would use it in every project for everything you do because it's so easy to apply and it comes with such big benefits and for that i want to let's just create a little project structure here um that doesn't need to make any any sense i will just create a very common project structure you don't need to follow along with this i just want to show you something um and that is i want to have a main repository actually you can follow along just an empty compose project because we will go on with these classes that i create here let's call that main repository implementation i don't write the abstraction yet like the interface for that let's just have the implementation which will be enough to show you what i want to show you and then a very common thing we do with the repositories is that we need them in view models so let's also create a main view model here in our package called here main view model that inherits from view model nothing special and now if we want to actually access our main repository which is usually the place um where we either access our database where we do network calls stuff like that we of course want to do that from within our view model so we now have two options to do that option one would be we say private val repository is equal to a new main repository implementation and then here wherever we have a function like a do network call like this then we could say repository.do and recall or so and that would work however what is the problem with this approach if we do this because right now we don't apply the principle of dependency injection because we we don't really give this view model its instance variables no the instance variables which is the repository in this case is created directly within the viewmodel here and by doing that we force this mainviewmodel to have a main repository implementation here which is immediately created so there is no way for us to actually tell the v model hey for this specific viewmodel instance i actually want to give it a different type of main repository and let's think about what it means if we actually want to use a test or a viewmodel here then every instance of the viewmodel that we test has such an instance of the main repository implementation as well and if that main repository implementation actually makes our actual network calls it accesses our production database then that's something we don't want to do in the test cases in the test cases we rather want to have a repository that kind of simulates that behavior but we of course don't want to talk to our real api or save stuff in our real database every time we run our test cases that would of course be super bad so let's actually work on improving this approach and for that i will actually now add a little abstraction for the repository so you can understand that better i will just create a main repository interface here and let's just give it a little function function do network call here nothing special just to show you how that would work then in the main repository implementation we make sure to implement this main repository interface and here we can override this do network call function which won't do anything here and then here in the viewer model we now want to apply dependency injection so instead of actually creating the repository in the viewer model itself we remove this line and we pass it in the constructor of the viewmodel so we say repository and here we pass the main repository interface and that way we are much more flexible now with what we pass to this view model and what the viewmodel actually uses because now the viewmodel only relies on the abstraction like on the interface not on the implementation and by doing that we can also create different versions of this main repository which might not use our actual api which might just simulate that network call for test cases and that way we can very easily swap these dependencies out and the view model doesn't uh care what it actually what the repository actually does and that's called constructor injection because we inject these dependencies the dependency would be the repository here in the constructor of our main view model cool but this video is about cohen um cohen is a library to do dependency injection so constructor injection is one way of doing that but the other way is actually using a library that supports that which comes just with a lot more features um i will go through this here don't want to overwhelm you already in the beginning but yeah there are other popular libraries like dagger hilt let's just have a little comparison between dagger hilt and coin because those are two very popular libraries in android for dependency injection well dagger health is actually a compile-time injected library which means the dependencies like all of our objects will already be injected at compile time so as soon as we click run it is already clear which dependency goes where and stuff like that because dagger hilt works a lot with annotations and annotation processing and generating some classes you don't see and then these classes kind of provide the dependencies for for example for the viewmodel cohen however works a little bit differently here coin is not compile-time injected which makes it a little bit slower so it it rather provides these dependencies on demand when we already run the code so when we run it it will figure out which dependency goes where however please don't think that when i say cohen is a little bit slower that this is a valid reason not to use coin please really only consider the fact um which dependency injection library you like more this performance issue is really so insignificant that you won't realize it in your projects so let's now actually set up some further classes here because i want to now show you how to use coin in a kind of real life scenario of course this is just a blank project here and we don't have a real project but i want to show you a typical scenario that shows you why dependency injection is helpful and how you can apply it in that scenario and that is just that we have an api interface that comes from retrofit common library to do network calls then our main repository will use that interface from retrofit to do the api calls our viewmodel will use the repository and then our activity will actually use the view model so that is a typical chain of dependencies that you have on your part in your projects and i will show you how you can accomplish that with cohen and some extra features of coin um which you might also often need in practice and to make sure that you can actually follow along with that make sure to have these nine dependencies here actually um on the one hand we have four dependencies for coin which is coin android um that is just the the base dependencies and the base functionality of coin for android we have coin navigation i'm not sure if we actually need this but that way you can kind of scope dependencies to your navigation graph i don't think we need this here in this project um this one is needed for jetpack compose if you want to use cohen with jetpack compose but even if you're not familiar with compose keep watching this video this has nothing to do with compose and i will also show you how you can uh make use of coding with xml because an activity yeah in this case it's kind of both because activities are used in xml and compose and we don't really have composables here anyways um if you want to test with coin that's something i won't go into here then you need this test implementation dependency and we have retrofit for yeah just showing you one example of the fancy injection and of course the viewmodel stuff so we are able to create viewmodel instances without further ado let's go to our root package and create a new retrofit interface i'll just call it my api it won't do anything here it won't talk to real api i'll just have a single function let's call it call api and we say okay that's a get request just a very common thing you have with retrofit and here you have my endpoint which this api would call okay then of course your repository implementation which would do this network call needs an instance to the retrofit interface and what we already have learned is that we should provide this in the constructor here and not create directly in the main repository itself because that would not follow the principle of dependency injection so instead we save private val api of type my api provided in the constructor that way we could also theoretically swap this out with a different type of a different type of my api and then here it would just call api call api and do whatever kind of response and error handling which we don't want to go into here cool in our main view model we already rely on the abstraction of our main repository which is fully correct however there's of course one problem now and that is how should cohen actually know how it should how our api should look like so our retrofit interface how should it know what the base url is how should it know whether we want to use some kind of converter factory just how it's built it can't know that and that's something we need to tell cohen and that's something we also need to do for dagger hilt and that is called we need to provide a dependency we need to provide an instance of this my api so that coin in the end is able to inject this my api at any place we want and the way we do this is we define a so-called module that is a very common concept when it comes to dependency injection um so let's just create this and then i will explain how that works in the root package we have an app module i will call it like that select file and that will just be a normal cotton variable which we can create with a module and then we have a block here of code so in this module block we now need to tell cohen how we want to create our dependencies and there are different types of how we would like to create our dependencies in different scenarios a very common case is that we use what is called single and when i write that i don't want to talk about the life of a programmer no that actually means singletons so a singleton in the end is just an instance of an object that only exists once during our whole application lifetime so if we for example take a look at our api interface then we don't really need to recreate this instance in the project it's totally enough if you just create this once and then use the same instance all over our project so that's what we want to do in the single block now here we need to return an instance of the dependency we want to provide so an instance of our my api interface we can do this with retrofit.builder we need to provide a base url which yeah it's not intended to work let's just provide google here um so it it's a valid url let's add a converter factory for example if we would like to have that mushy converter factory is what i included in the dependencies and then we can say that build and that create which will return the actual my api interface so that way we now told cohen how we actually create our my api interface and from that point on coin will be able to actually use this exact instance we created at this point in our code and inject it here in our repository in the constructor so that's now coin's job however it now still doesn't know how to create this repository which is actually needed in the main view model so when it tries to inject this main repository here well it will take a look in the module and we'll see okay i actually know how to create an api but i don't really know how to create a repository and that's the next step we need to do so we need to do the same for our repository so again single and here we now create our main repository implementation that we want to provide for the main remodel to be injected into there and well now that main repository actually needs our my api so how do we now take this instance and put it here in the constructor of main repository implementation which that one of course needs and that is where the get function comes into play which we have access to in this in these scopes here and what this will do is it will simply tell coin okay you know what i already provided this dependency that i need here so the uh interface please take a look where you can find it and then take that instance so coin will see okay we no philip actually wants to get this my api interface and it provided it here so it will take this instance and pass it here for instead of this get function and that's really almost everything you need most of the time with cohen there is still another function um which is which works a little bit differently from single and that is called factory what factory will do in comparison to single is it will create a new instance here of this main repository implementation every time that is requested so let's say we have two view models and both request a main repository instance then this factory block will give each viewmodel its own instance however if we use single it will create a singleton so just one instance and it will reuse that for both viewmodels usually you're totally fine with single i will come to another concept which you might need sometimes which works a little different from single as well but for now we're fine with that however there's one more thing we need to take care of here right now um dagger daggerfield cohen will think that we actually provide a main repository implementation which we actually do but in the main view model we try to inject the abstraction the interface so we need to make this um explicitly clear here that we actually want to prove oops that we want to provide this as the abstraction by specifying that as a type otherwise queen will complain later cool so now coin also knows how to create a repository it knows how to create our api interface but one thing it doesn't know and that is how it should create our view model and actually inject that into our main activity where we can finally use that instance and that works a little bit different from single because um creating a view model is not just calling the class name with a constructor no there's actually a factory behind the scenes um which you would normally see and normally you would need to implement but cohen actually does that for us however we still need a different function here which is just view model we want to provide an instance of main view model which we actually don't need to specify here we can just create a main view model you can see now that takes our main repository and because coin already knows how to create this main repository we can simply call get here as well just like we did here and now it also knows how to create our main view model so we can inject that finally in our main activity how do we now actually inject something so now we we have learned how we can provide dependencies so that coe knows how to create these but we haven't learned yet how we can inject these into our classes so if we go to main activity for viewmodels that actually again works a little bit differently than for normal classes that we would like to inject let's start with viewmodels because most of the times you're just going to inject view models and all the dependencies you have go inside of the viewmodel so we will have a private valve viewmodel here and we just say by viewmodel and we use this function and that is an instance of mainviewmodel and that will automatically provide the viewmodel instance we provided here in our app module this one here in the main activity so we can use it there that is the way how you would do it with an xml project if you have a compose project you don't need to do it this way instead you can just go inside of your composable and you say i want to get the viewmodel here and that is equal to get viewmodel this composable function here and that again is a main view model oops main view model and then you could also use that in your composable function so those are basically the two different types how you can inject view models but what if we want to have another dependency here in our main activity that's not a viewmodel let's say we want to have an instance we want to have the instance of our api interface in main activity this is something you in a real project you shouldn't do you shouldn't mix data and presentation layer um but just to show you how you would do that if you would have another dependency could be any type of object and you want that in your main activity then you would simply do it like this private val api is equal to actually get so we again have this get function that would be one way um and we say what do we want to get we want to get our my api by using this get function that's actually like an immediate injection so as soon as our activity is created it will inject that my api and it will also construct this instance here of this my api as soon as our activity is created however what you usually want to use is by inject this one here you say my api that's actually called lazy injection so it will only inject this api interface and it will only construct this here once you actually use this api variable for the first time and that can sometimes be very very useful um if you let's say you need to provide some kind of uh service or so that talks to an api and that service needs a token the token first needs to be fetched so there is a little bit a little bit of delay in a network call so you don't want to inject that service immediately now you actually want to lazily inject it and only use it once the token has been fetched just one example why that's usually helpful and usually there is no downside in using inject so i will remove this again and let's just see if this works let's go to our view model and say okay let's not do the network call here i think that will crash let's just print something and in main activity here we just save your model not do network call which will just print something here that's you also shouldn't execute this directly in a composable but it's just to see that the view model is properly injected and passed here if we launched this and you don't you don't need to see my device here because we would have a blank screen anyways we just want to see our logs so let's open these and hopefully actually this won't work um like it will probably crash because i forgot something something very important and that is we need to set up in our application class actually because we haven't really initialized coin yet good to not forget about that in our root package let's call that my application and select class here and that's an application class because when our app is actually created we want to initialize code so we do this by using start coin and we have a block and we now need to tell coin on the one hand um what our modules are so we could have multiple modules but it of course needs to know that this is our app module which is currently an unused variable so that's something of course that we need to provide here and something else is we want to call something like android context and here we want to pass this application instance so what is that useful for well if you actually need to inject the application context with coin then it of course needs to know where it can get that from and it can get that from this my application instance here so you basically type going here you get my application contact and from there it will be able to inject that in some constructors of your dependencies you you might need that in and if you want to use the logging feature of coin you can also install the android logger here which is optional we then want to go to your manifests and provide the name attribute and pass my application so we register it here so it's actually used now we should be fine to execute this let's see in logcat and here you can see it actually prints something so the the injection is working perfectly fine however there are still two more concepts that i want to show you here um on the one hand and that is the concept of scopes so in our app module we have this single function which again just creates a singleton instance so there will be one instance and that lives as long as our whole application does however very commonly you actually want to kind of scope your dependencies that they don't live as long as your whole application does because you might only need them in an activity so why should they actually be kept in memory if that if the activity is destroyed there is no reason for that and that is why we can actually create a different module here which i will call activity module val activity module is equal to a new module again and what we can do here is we can now have another set of dependencies that we provide and they only live as long as the activity they are injected into so how can we do this we can say scope and here we need to provide the type of the class we want to scope this to so main activity and in here we can then say scoped where we need to provide the dependency we actually want to scope i will do something super simple here i will just inject a simple string hello and that way we scope this hello string just to a single activity to our main activity and once that's destroyed the string will also be garbage collected so we need to go to our application again because we created a new module which we need to tell cohen here and then we go to main activity what we need to do here in the activity we actually want to inject the dependency we scope to this we need to implement an interface called i think it's android android scope component and then you can see we need to implement a function here which is um called scope not a function just a object here a field and well what is this scope here we can simply say by activity scope so if we now actually inject our dependency our string or hello string we can do this using private val hello by inject and that's a string then this hello string is now properly scoped to this main activity if we go here and we say print line hello then hopefully we see this hello in our logcat let's search for that and there it is you can see hello is printed in the console so we now properly scope this to german activity we properly injected it here there are of course some other scopes as well for example another common one is the activity retain scope so that would be kind of the view model scope because it's um this will keep this hello string um even when your activity rotates which destroys it but uh it kind of survives that but the the dependency if we use the activity scope it would be destroyed when the activity is rotated or there's any other kind of configuration change and then recreated and re-injected once the activity is recreated um there are other android components scopes like server scope i think fragment scope yeah so feel free to play around with that one last concept i want to show you is actually the concept of qualifiers because some of you might already think what happens if we actually now have two different strings we would like to inject how does cody know which one it should inject here in this hello variable and the answer is it can know that and the like the solution to this problem is that we use so-called qualifiers so in activity module let's say we have another dependency by that we inject here and now we have two strings that we inject in the same activity and we don't specify which one we want to inject here so cohen will have a conflict here because it doesn't know which one to take and the answer is we can provide a qualifier here as a parameter we can say named and we can simply give this a name and then we can refer to this when we inject it using the name again so again named and this one is by we go to main activity we go inside of inject and here we can again pass this qualifier and this time we want to inject the hello stream if we then relaunch this take a look at logcat and then this time it actually crashed i think did it um okay so that's just an issue with the activity retain scope it wants an app combat activity it's a component activity um seems like that doesn't work with compose but there's also no real reason to use that in compose i guess so let's just remove the scope stuff here to show you how all right so we can't remove this but we can use activity scope that should hopefully work um so logcat did this crash again no here we have our hello string and if we now swap this qualifier out for buy we launch this and then we of course don't find it by searching for hello but you can see here's our buy text so that's how you can um tell the uh not that grilled again how i can tell cohen which dependency should be injected into which object and if you actually also want to learn about dagger hills where a lot of these concepts will now be familiar to you then you don't want to miss this course which i already recorded about that you
Info
Channel: Philipp Lackner
Views: 42,568
Rating: undefined out of 5
Keywords: android, tutorial, philip, philipp, filipp, filip, fillip, fillipp, phillipp, phillip, lackener, leckener, leckner, lackner, kotlin, mobile, koin, dependency injection, jvm, scoped, factory, single, activity scope, compose, jetpack, injct, inject
Id: EathumJlWh8
Channel Id: undefined
Length: 30min 11sec (1811 seconds)
Published: Wed May 04 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.