How to use Dependency Injection in SwiftUI | Advanced Learning #16

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[Music] all right welcome back everyone my name is nick this channel is swiffle thinking where we cover all things swift and swift ui related and as the title said this video is going to cover dependency injection now there's a lot to dependency injection we're just going to cover the basics in this video now dependency injection is a really hot term these days there's a lot of people in software engineering in mobile engineering that love to throw around this term dependency injection and i think a lot of times when people talk about it they kind of over complicate something that's actually relatively simple so in this video we're going to look at the very basics of dependency injection and how we can incorporate it into our apps now before we get into the code i want to just briefly talk to you guys about what even is dependency injection the secret is actually in the name dependency injection is actually injecting your dependencies but what that really means is when we create a struct or a class that has dependencies instead of referencing the dependencies from within the class or within the struct themselves we're going to actually inject the dependencies into the struct through the initializer so if you've been using custom and knits in your structs in your classes you've already been doing a little bit of dependency injection and the main reason we do this is so that we can programmatically change what is injected into the class so we can change our inputs we can customize the init so that the structure of the class maybe performs or acts differently and the big thing i want you guys to start thinking about as we do dependency injection is your app architecture because when you're using dependency injection at some point in your code you're going to create your dependencies and then you're going to inject and pass those dependencies throughout all your views your classes your review models so it becomes really important to figure out when we should actually create those dependencies and what is the flow where we should actually pass those dependencies to all of those structs and classes now that is a lot to think about and i don't want to stress you guys out so let's jump into xcode and just start with some very simple dependency injection all right welcome back everyone so i'm back in our xcode project of course in the last video we covered protocols and in this video we're going to cover dependency injection but in order to really get the most out of dependency injection you need to actually understand protocols so if you don't know what protocols are i highly recommend watching the last video and then coming on back but if you are ready and you know what protocols are let's jump into dependency injection by right clicking the navigator creating a new file it will be a swift ui view and let's call it dependency injection boot camp go ahead and click create and once you're inside click resume on the canvas and we're not going to use the navigator so i'm going to close it on the left and make the text a little bit bigger so again this is part of the advanced course so if you're watching this i assume you are already at least comfortable with swift and swift ui so we have to do some setup here and i'm going to move pretty quickly through it because it's all stuff that i've covered in previous videos but let's start out here by creating a view model for this view so let's create a class let's call it dependency injection view model let's me conform to observable object and open the brackets and our view is going to observe that object so let's initialize it with an at state object private var vm set it equal to a dependency injection view model open close parenthesis now we're going to change this initializer in a second but for now let's just get it here let's get it all working in our view model we're going to hold some data and we don't have that data type set up yet so up here outside that class let's create a model we're going to create a struct let's call it posts model and we'll open the brackets now we don't have the data in this model yet but let's just use that in our view model so our view model is going to hold an ad published var it's going to have a let's call it data array and it will be of type array of posts model and we'll set it equal to a blank array to start inside this view model let's create our init and then let's create another function that says load posts open close parenthesis open the brackets and we're going to call this load posts when we initialize and we're only going to call it here so let's actually make it private all right now this load post function is going to go to whatever data source that we have in our app and get those posts download them and then return them back and update this data array now we could put all the logic for downloading posts directly right inside this view model but for this example again we're going to create a separate class that is going to be our data service this is much more realistic in an actual app so outside this class let's create another class and let's call it production data service and let's open the brackets i'm calling it production data service and i will explain why i'm using the word production a little bit later but for right now this will just be our data service for the app this is going to be the class that's in charge of this is going to be the class that's in charge of fetching all of the data from our data repository our database whatever you want to call it so in this production data service let's create a func that's called get data open close parenthesis open the brackets now we're going to actually download some posts from the internet and to make this simple i'm just going to use a very simple api that i've actually used before in my courses so if you go to jsonplaceholder.typeoccode.com and i will link this below um this is a free website that has free fake apis for testing and prototyping i'm going to scroll down and use the posts this is just a free get api that just has a list of posts so i'm going to take this url up here and this is going to be the url that we're going to download from so we're going to download all this data here let's go back and in our production data service let's let's say let url of type url and let's set it equal to a url open the parentheses with a string and we're going to paste in our string here now this is optional by default and we know it is a valid url so i'm going to explicitly unwrap it if this was production again i probably would not explicitly unwrap but i'm just doing that because that's not what we're focusing on in this video so now that we have our url let's fetch the data from that url so i'm going to use combine and let's import and let's import combine framework and in here let's call a url session dot shared dot data task and we'll look for the one with a publisher data test publisher for url let's pass in our url and i'm gonna move quickly again because we've done combine and that's not what we're focusing on this video so let's map the data that we're going to get back here and let's just open the brackets money sign zero dot data it returns us response and data and we're just going to take the data from that and then we want to decode that data into an array of posts model now if we look back at the post model we can see there that this is the data that we're getting so i'm going to just copy one of these quickly let's go back and set up our posts model and i'm just going to paste this for a second so we can see our post model needs a user id an id both of type int a title and a body both of type string so in here let's create a let user id of type int and we can see that the d is lowercase just be careful needs to be exactly as it is in the in the url we'll say let id if type int let title of type string and let body of type string all right let's get rid of some of these comments and we're going to decode this so let's make this conform to codable this is all stuff i've covered before now that we have this conform and decodable let's decode the data into an array of posts model dot self and let's use a json decoder open close parentheses we'll initialize a new one after we decode let's uh let's receive on and let's jump back onto the main thread with dispatch with dis batchq dot main and then let's erase to any publisher so this will convert our publisher to a nice and simple any publisher so in this example this function getdata is actually just going to return this publisher and then we're going to deal with the result in our view model so this will actually return any publisher with a result that is an array of post model and error all right and now in our view model let me zoom out a little bit here in our view model we want in our load post function we want to actually download these posts we want to call this get data function so the first issue obviously that you've had in your apps is how do we access the production data service from our review model class and if you've been following online tutorials and mostly beginner tutorials you probably are well aware of how to use a singleton a singleton is a design pattern that looks like this we create a static let instance and we set it equal to an instance of production data service this is called a singleton where we initialize a single instance of the class within the class and then this would be the only instance that we use throughout our entire app so we can then access the instance by calling productiondataservice.instance you've probably done this many times so let's start with this we're going to call productiondataservice.instance.getdata and when we get that data let's just sync it into the data array so we'll call that sync and the completion i'm not even going to worry about right now let's just leave it blank the this will be returned posts let's make it weak just to be good coders and let's set self.data array equal to the return posts finally we need to store this subscriber somewhere so let's create our var cancelables and let's set it equal to a set of any cancelable open close parenthesis and we're going to store in cancel walls should all be stuff that we have done many times this is nothing new to you guys and finally let's put the posts on the screen so i'm going to create a let's create a scroll view on the view here let's create a v stack let's create a four each and we're gonna go through the in order to loop through the post model we need to conform to identifiable or hashable but we have an id so let's use identifiable and for each let's use the first completion of vm.dataarray and this will be for each post and then on the screen let's just put a text that says post dot title i don't really care what it looks like let's just get it let's get the data onto the screen i'm going to click play on the simulator here so we get the live preview and if we did everything right it should download all of the posts and we can see that here now again i don't really care what this looks like for this video what i am focused on is dependency injection and we have not done that yet and now let's talk a little bit about dependency injection so the reason i set this up this way is because i wanted to use a singleton for our data servers because dependency injection is basically the solution or an alternative to using the singleton design pattern this singleton pattern which you've probably used a bunch of times if you've been learning how to code because it's in a lot of beginner tutorials it's great for when you're learning out of code but there are a lot of flaws and problems with using singletons so let's briefly talk about some of the problems with singletons so i'm going to put a little comment let's put it up here outside this post model say problems with singletons and the first problem is that singletons are global and that means we can access this instance from anywhere in our app so we could access it from within this class we can access it from within this class we can also access it here from not in any class we can call productiondataservice.instance we can access it from our view we can access it from pretty much anywhere and that by itself is not necessarily a bad thing but when you start making larger apps it's going to get confusing if you have a bunch of global variables additionally if you have this instance and it's being accessed from a bunch of different places in your app at the same time you could run into some really big problems if maybe you're using a multi-threaded environment so you're doing different tasks on different threads and those different threads are trying to access the same instance at the same time you could end up getting a bunch of crashes in your app so long story short we want to avoid global variables as much as possible when developing apps most major production apps probably don't even have global variables so we don't want this to be accessed from anywhere in our app the second problem is that we can't customize the init so when we initialize our production data service we're doing it right here as a singleton right now we're not initializing it with any data but if we wanted to create an init and we say maybe let title of type string and then we can do something if we need to pass in a title in order to initialize this instance we would have to do it here and the problem is this initializer obviously is we have to create this now we can't create this when our app is running so if we needed to maybe change what we're going to initialize this production data service with we wouldn't be able to do that right here right because we have to type it in now the second problem with singleton is that we can't customize their initializer and this gets really important when we start trying to add testing to our app i'm going to explain that a little later and in the next couple of videos so for now let's remove this init and we don't need this that's problem number two and the problem number three is that we can't swap out services so what that means and if you follow me in the last video we can use protocols to swap things in and out of our app but if our app is always referencing the productiondataservice.instance we're always going to end up referencing this exact class and therefore we have to use this exact data service we can't use another data service so the solution to not using singletons is to use dependency injection so dependency injection actually sounds a lot harder than it is but really what it boils down to is instead of initializing your dependencies and in this case our dependency is going to be our data service instead of initializing it here within the data service we want to initialize it pretty much early on in our app almost at the beginning of our app and then inject it into the rest of our app all the views and view models that need a reference to the data service so right now we are initializing it as a singleton but we're not going to do that obviously anymore so i'm going to delete this singleton here and now we just have our class and let's try to figure out how we can inject this into our into our view and our view model so coming down to our view model here in our view model obviously we need a reference to a data service so we can call get data so up here i'm going to create a reference to that let's say let data service and it'll be of type what do we call it production data service so in this so in this view model we can call data service dot get data instead of accessing the singleton so now of course when we are initializing our view model we need to pass in a data service so in the init of our view model let's obviously pass in a data service in here we'll set self.data service equal to data service and this is dependency injection in a nutshell it's literally injecting your dependencies into your class or your struct so here we're taking the data service which is our dependency and we're going to inject it through the init into this view model so that this viewmodel has access to the data service now if we go down to the view we can see that we're getting an error here and that's because when we initialize our view model we need a data service now of course our view does not have a data service so we actually want to inject the data service into the view as well so in here let's create our init open close parenthesis open brackets and just like up here we're just going to inject it in and inside this init let's set up our view model so i'm going to get rid of the initializer here the view model will be of type dependency injection view model and then we're going to and then we're going to set this up within our initializer so we'll set underscore vm and we'll set it equal to a state object with a wrapped value of dependency injection viewmodel with a data service and we're going to get that data service from right here in our knit because we are injecting it in to our view finally we're getting the error down in our previews because when we create this view now we need to inject a data service so i'm going to fix and then we need a production data service so right i'm going to initialize it inside this preview for now let's just create a static let data service and set it equal to a production data service and we're just going to pass that in here so in your actual app when you create a view you're going to pass in a data service just like this or you would pass in whatever dependencies that view is relying on but i do want to point out here that this data service we would actually initial we wouldn't initialize it right before this view instead we would initialize this data service somewhere very early on in our app maybe in the app.swift file maybe maybe in one of the first few screens or view models in our app and then we would take the reference to that data service and pass it along and then it finally inject it into this view so i'm not going to build out the rest of our app here i think you guys can get the point but you basically want to initialize your dependencies early on and then pass them around and inject them into all the views that need them so here we're going to inject the data service into the view and then here we're going to take that data service we're going to then inject it even further into the view model and then the view model which is the class that actually needs the data service can go ahead and use it so going back to our problems here the second problem was that we cannot customize the init but now that we're using dependency injection we can so if in our data service instead of initializing it with a url here maybe we wanted to pass in the url to our production data service this would make sense if maybe we wanted to change the url for certain reasons so we create our init and then we can pass in a url of type url and we can set self.url equal to url so now if we create our production data service wherever we create it we're gonna have to pass in a url and we can customize this init as we need so coming back down to our actual app now wherever we decided to actually initialize our data service we can then initialize it with a url i'm just going to paste in the url that we have here again i would not explicitly unwrap in a production app or just but that is not the focus of this video so if i click try again one more time the first problem is now fixed because we can customize the init in our data service all right so now if i click play again it will still work and build alright so now everything is still working and we are injecting our data service and we are customizing the init this might not seem that powerful right now but when you start building production apps you're going to want to customize your data service maybe you want to change the url maybe you want to change some other values inside that class and the best way to do that is by customizing your knit this is really going to come in handy when we start building tests for our app as well and you'll see why this is actually required in order to really run some good tests alright so let's go back up to our problems and let's look the first problem was that singletons are global so we already fixed this because now the only classes that have access to the data service are the ones that we are actually injecting a data service in so we created another class anywhere in our app it's not going to be able to access the data service unless we pass it in and we inject it into that class so problem number one is already fixed problem number two we cannot customize the init so obviously we fixed that because we are customizing our knit right here that's awesome and now we just have our third problem which is that we can't swap out services and i should have said dependencies here so coming down to our app right now we only have a production data service right obviously it has this function get data and it returns our get data but in your actual app you might have many different variations of a data service so maybe you have one for production maybe you have another one for your qa your quality assurance maybe you have another one for testing maybe you have another one for beta testers and maybe each data service has different logic in that data service because maybe you want to download different posts depending on the environment so for example let's create another class here i'm going to create a class and i'm going to call it mock data service let's open the brackets now i'm calling this one mock because it's very common when you start creating tests in apps that you create a mock version of your dependencies this way the mock first off you can use for testing purposes and secondly you can use it for development purposes because then you can add in some fake data in your mock version and then put the fake data on the screen which might be easier for testing and you don't have to deal with actually downloading real data or affecting the actual database instead you can use a fake version a testing version so in this example i want to swap out our production data service for our mock data service in order to do that instead of passing in a production data service we actually need to pass in a protocol this is why i covered protocols in the video before this one so let's create a protocol and let's call it data service protocol open brackets so anything that conforms to data service protocol must have a function called get data that returns any publisher with a result of an array of post model and error so i'm going to copy this i'm going to paste that into our protocol so now our production data service let's actually move the protocol above this so that it compiles a little better now our production data service needs to conform to data service protocol and our mock data service needs to conform to data service protocol now it doesn't yet so let's fix it add the protocol stubs in our mock data service all we need is a function that returns a publisher that provides an array of post model so in this mock data service i'm going to create basically a fake publisher we're not going to actually download anything we're going to create a fake publisher that will send that will publish an array of post model so up here in our mock data service let's just create some test data so we'll say let test data of type let's just make an array of posts model set it equal to and let's just do let's do two quickly let's create a post model user id one one title one body one and then let's do another one number two two two two in our get data we need to return a publisher so a easy way to create a fake publisher and just return one single value is to call just just is a single in publisher that emits a single output it's called just and i'm going to output the test data so it's an array of post model and then we will call dot erase to any publisher to make it any publisher i'm getting a quick error here because a just publisher can never fail because it just publishes the test data it'll never actually throw an error and we know our protocol here obviously possibly can throw an error so we need to conform to that protocol by making this just at least look like it might fail so in here let's just call that try map and i'm just going to return money sign zero so the actual test data but because we're using try map it thinks that we are trying and possibly failing so now the type that is coming out of this publisher actually has a possible error even though it doesn't have an error anyway we now conform to our data service protocol so let's take that protocol and let's go down into our view here so in our view model instead of passing in a production data service let's pass in a data service protocol anything that conforms to data service protocol here let's also inject anything that conforms to data service protocol coming down to our view we also want to inject anything that conforms to data service protocol so now coming down let's click resume see if this still builds so while we're using our production data service obviously it still builds but now instead of using a production data service we could also use static data service equals let's call mock data service and then we can pass in the data server so if i click resume now it just says one and two obviously it's just fake data but the point here is that because we are injecting a data service we were able to actually inject a protocol and because it's a protocol we can then swap in and out whatever we want to use as the data service so in my actual app if it was going to production we would probably use our production data service but if we were testing or maybe just developing quickly we could then use our mock data service and this becomes super powerful when you get complex apps because the production data service is probably going to use production data so it will download actual users actual posts and a lot of times when you're developing you don't want to actually affect the real data instead you want some fake mock or test data and we got that right here which is awesome the final thing i'm going to wrap up this video with is this mock data service right now we're not initializing it with anything so coming up here right now the mock data service starts with some fake test data but let's actually make this uh an initializer so in here let's create an init open close parenthesis open brackets and let's pass in some data let's make it of type array of posts model and let's make it optional now i'm making it optional because this way we can initialize it with some fake data but we can also initialize it without fake data by passing in nil here and so in here let's set up our test data by calling self.testdata and we'll set it equal to the data that we pass in which obviously is optional so if we don't have that data we'll do two question marks otherwise let's just set it as this test array the reason i'm doing this is because it will make our development faster because now when we go down to our app we can initialize our mock data service with out data so i can put nil and we still get our default test data but we could also pass in an array of post model here it's going to pass an array and we can do our own post model so here let's just do some numbers test test and now we can start testing our app by passing in custom data relatively quickly and you can see already how powerful this is because if we wanted to test how a certain view would look with certain type of data instead of having to download it from the internet and trying to find a post with that data we can then just pass in some test data and see how it looks immediately and that's where i'm going to leave this video this was an intro to dependency injection and in the next couple of videos we're going to jump in to testing our app ui tests and unit tests and when we start doing unit tests this right here is pretty much the exact structure that we need so this becomes super powerful when you're creating tests because you're going to want to pass in data that you can actually test against so obviously being able to customize the data that's going into your view and your view model is super important but we're not going to get into that in this video so i hope you guys enjoyed this video i know it was another long one and this was probably a pretty confusing one but the takeaway that you guys need to have from this video is that dependency injection is literally injecting your dependencies into your views your classes your structs so in your app you would create your dependencies early on so if we were in a production environment we might create this dependency if we were in a test environment we might create we might create this dependency and then we would inject whatever dependency is currently in our app into all of the views the view models the classes the structs that need a reference to that dependency so in this example we created a mock data service we injected it into our view and then from our view we further injected it into our view model here and then our view model finally could actually use that data service so we called dataservice.getdata and this was all possible because our data service conformed to a protocol which is absolutely awesome super powerful and now our app is going to be so much more scalable because we can swap in and out different data services and you can imagine that we can then use this same technique for all of the other services that we have in our app so you could do this for color themes you could do this for maybe different analytics if you want to use real analytics fake analytics pretty much whatever classes you want they just need to conform to protocols and then you need to inject them and the last thing that i just thought of and i want to point out is that when we have one or two dependencies it's really easy to pass them around our app right because we just create a knit we pass it in and it's all done but if we had a whole lot of dependencies in our app so if we had like 10 or 20 or even more dependencies than that it would get really annoying to have to create an init and pass in all the different dependencies every time so what some larger apps will actually do is instead of in instead of passing a single dependency around it'll they'll create a class called like dependencies you can call it whatever you want really and in that depend and in that dependency class is where you would actually initialize all of your dependencies so you would have your initializer here of data service you would initialize and pass that into your dependencies class self.dependent data service equals data service and then you would just pass around the dependencies class around your app so instead of injecting a data service you would actually inject dependencies so then you could have you could pass in dependencies here and i'm not going to go through the rest of it but you get the point so if you have a whole bunch of dependencies it could be an idea to create some sort of parent master class that will hold all of your dependencies just to make it easier to pass around your app but if you're just learning and you're just starting i would recommend just passing around single dependencies for now feels a lot easier and just visual to see all right that's enough of me rambling i hope you guys enjoyed this video if you learned something please don't forget to hit the subscribe button leave a like and comment on this video that's it for this one as always i am nick this is swiffle thinking and i'll see you in the next video [Music]
Info
Channel: Swiftful Thinking
Views: 1,573
Rating: undefined out of 5
Keywords: swiftui dependency injection, swift dependency injection, what is dependency injection, dependency injection swift, who to use dependency injection, what is DI in swift, what is DI swiftui, how to use Dependency injection swiftUI, swiftUI how to use dependencies, SwiftUI what is a singleton, swiftui singleton, why is a singleton bad, why shouldn't we use singleton, dependency injection in swiftui
Id: E3x07blYvdE
Channel Id: undefined
Length: 35min 31sec (2131 seconds)
Published: Fri Oct 22 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.