The Top 5 Practices That Make My Android Architecture More Scalable

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey guys and welcome back to a new video this video I will give you my top five practices that really help me to make my Android architecture more scalable more scalable in this regard means that it's easier to work on this code base in general so it's easier to extend it with more features it's easier to change existing features it's easier to just understand what your code does so in case new people join the team these five practices I will share here will really improve all of that and I also really tried to make these practices as universally applicable as possible ible at least if we think Beyond Simple apps that maybe consist of a list and a detail screen so let's dive right into it number one dependency injection I recently made a video about dependency injection on cotland multiplatform where I really also talked about what dependency injection really is at its core but that's really a design pattern that I would always apply to just quickly show you what I mean here I prepared a little file we have some sort of repository abstraction repository interface we have the implementation and then we might want to use this in our view model for example or any other class what I often see people do is that they would initialize the repository like this which would not be dependency injection so the repository would be the dependency in this case but the idea behind this pattern is that we inject these dependencies from the outside so we pass them to our login view model rather than initializing them right away in our view model so instead of this we want to pass them here in our view models Constructor so we have a repository we make it dependent on the abstraction by the way because the big advantage of this design pattern of the pency injection now is that it's completely up to the class that initialized this view model what specific type of repository it wants to use so for a production code we might want to use the auth repository implementation which we can then very well pass but we might also want to maybe write a local unit test for our login view model and unit tests should in the end be isolated that means we want to eliminate all external circumstan external sources where the test could fail but rather be sure that if the test fails the issue is really in the class we test in isolation so inside of this view model and that works with so-called test doubles so that's just a a different implementation of this class that is optimized for testing that just simulates the functionality of the real implementation for example but also for your production code it's really helpful to stick to a dependency injection since you might have one implementation that uses specific HTP client Library a and you might want to swap that out in future with a different HTTP client Library so in that case you could just write an additional implementation of that use the new library and then just pass it to your classes that depend on this without actually needing to change the classes themselves and lastly if you just initialize your dependencies directly inside your classes like I did before so with your uh repository in here repository is equal to author repository implementation then this will really also not scale at all because your dependencies will just be sprinkled all over your project in a bigger project cuz there's no structure at all and no central place where you you create and initialize these because one could be initialized in the view model or another dependency could be initialized in the repository you might have multiple instances for a single dependency that you actually use globally across the project so this will really become messy and whether you now just use manual dependency injection with normal Constructors here as I showed you or you use some kind of di framework like dagger or coin that is secondary and up to your preference getting to practice number two and that is testing in my recent Workshop about testing I showed this meme because it's so true why do developers think they have no time to write test cases but they have time to fix bugs they have time to go through endless feedback loops with their team and just time to discuss bugs and issues in hourong meetings if you have a well tested code base it just gives you so much power about it because you just need to be so less afraid of breaking something so just in case you're not familiar with what I mean with testing that's in the end that we write code that verifies our real apps code and as soon as we break something in that real code then we can still run our test cases that that verify that with a single click and they will tell us when something broke and you don't have that option if you don't have a test suite and even if you have a suite of a lot of tests that doesn't mean that you will completely get rid of manual testing that you will need to go through your app and verify things but I would say at least 60 to 80% of the testing efforts can be delegated to a machine so to your test suite and that will not only lead to your code base having much less bucks because you are much more likely to find bucks if you run your test cases very frequently but also that overall release time will be faster since you will just drastically reduce the amount of feedback loopes needed in your team so where you ask Thea might go to your QA assistant or quality assurance those people who usually test our apps and you might be like okay here is the finished app please test that the QA assistant tells you oh no there's actually an issue hands the app back to you you fix the issue hand the app back to him and there's always that delay between you waiting for the test results and the QA assistant waiting for you to implement the fixes and as I said you won't get rid of that when having a test Suite but you will drastically reduce the amount of these feedback loops that's needed and that's also the reason why from my recent Android Essentials bundle where you learn everything about app architecture I now added a free bonus part about testing so in the bonus part you will really learn testing Theory from basic to Advanced up to also writing real world test cases for the running Tracker app that was built on this course and this bonus part is completely free available as an addon right now during this week until Sunday but not afterwards anymore apart from that you can also get 25% discount on all my courses including the new Android Essentials one together with this free testing addon which again is really only available during this week afterward it will be removed Link in this video description next practice number three is modular design and here don't strictly mean modules in the sense of gradal modules so a multi modu architecture Android that can be part of it but it's not all about modular design I personally think the best analogy to modular design and what that actually is is when you play with Lego so if you want to build a Star Wars Millennium Falcon with a Lego and the set would only contain four different stones that make up this Millennium Falcon together and that would suck not only because it wouldn't be fun to assemble that at all but also because you wouldn't be able to assemble anything else out of these four Stones other than the Millennium Falcon and that's in the end what we like about Lego right that we can build anything we want but since there are just so many stones included in that Millennium Falcon set we can see each individ Stone kind of as its own module and the more modularized the set is and the more such modules we have the more we can also build out of these so you might take the stones from a Millennium Falcon and build a house out of it and in software development it's really the same the more modularized our codebase is the more it allows us to reuse certain powers for different purposes not only in the same project you're working on but even in different projects and modular design and software development can really be applied at every single level of detail and I will show you what I mean by that so here I opened the app that I built in my Android itial course you can see it has a multimodule architecture we have different grading modules which allow us to reuse these parts but as I said that's not all about modular design modular design really starts at the very lowest level so let's take a look at this connectivity data module we have here so that's really just meant to be used to implement the communication between the mobile app device with the watch so here we build a running Tracker app where we can track the user's hard rate on a smartwatch device and that will just regularly sync that tracked hard rate with the phone and here if we take a look at the datar related connectivity Behavior we have four packages the ey Discovery mapa messaging and here in the messaging package that for example implements how messages are just exchanged between phone and watch if we open up this we messaging client for example then here the smallest level of a module could just be a single function so here this function for example implements that we connect to a specific node to a specific Smartwatch or real phone device and then allows us to observe all messages that the other partty sent to us this function really doesn't do anything else it really just implements the behavior to connect to a remote node and then observe its messages in this sense is really nothing else than sticking to the single responsibility principle because if we keep functions to have a single responsibility then it's much easier to also reuse that function since the more stuff we would put into the single function the more likely it will become that this function won't be usable at all for other parts of the code base so a very small level of modularization would just be to make your functions of a class modular make each function just do a single thing the next level would be to make the whole class modular in this case that is a weird messaging client and making this class modu means that we want to use this wherever we want to somehow communicate with a Weare device but not more than that so that again keeps this class reusable because if we would also add functionality here to maybe scan for devices then it would already contain too much because another class that used this one might not even need that functionality but we don't need to stop here another form of modularization would be package structure so if we take a look in here we have four packages where each package just corresponds to a single purpose so this one to messaging to exchanging data between watch and phone here we have a package for mapping so where we Define how certain data needs to be mapped to another form of data we have a package for discovery which I just mentioned where we add functionality that just helps us to discover other devices like other SmartWatches around us and we have a DI package that just implements the creation of these classes here in inside for our dependency injection library and if we take a look at each package then it only contains classes related to that functionality the package communicates so each package also now serves as its own module and if we then go even one layer above and take a look at the whole Gradle module at our connectivity data layer then this is now also its own module because it contains all the functionality we might want to use if we want to implement the communication between two devices because that would obviously include first of all scanning for devices and if we successfully connected to device to also observe the messages and to finally map the incoming data to a data type that we can interpret in other modules and now in whatever app we actually want to work on that needs this specific type of functionality we could just go ahead and reuse this data module but within this data module we would also be able to reuse functionality that this module itself defines so by having this device node mapper here for example which is really just a simple mapping function but having this in a separate file it makes this function to device node accessible to to the whole module in this case even globally but we could also make this just available to this module here however if we would take this function and maybe put it here inside this we messaging client then the we messaging client could access that function but not any other classes so I think and I hope you get the idea of what I really mean with modularization we can take a look at each level of detail functions then classes then packages and then finally real gradal modules in each step should just be modularized with the right level of detail coming to practice number four and that is avoiding in memory Global State because what really harms scalability more than just weird errors popping out of nowhere requiring you to fix them before implementing anything else and a very common source of weird errors is having in memory Global State let me show you what I mean by that let's take a look at this session manager class so this could really just be a class that contains some sort of state that is needed globally in the app just like an excess token for example so just a token you need to authenticate with the API you're using and this token is just used in all features since all features might make some sort of HTP request which that token and people might now have the idea to put this token in a state flow so you can automatically observe changes of that token and down here you would have some sort of logic um where you implement how the token now changes in the scope of your app so what's the problem with this approach the problem is that in memory Global State isn't safe at all in an Android environment because Android there's a concept called process death so that means if your app is in the background the system is hungry for memory it might decide to kill your app's process however the user is still able to get back to the previously visited screen by just opening their recent app store and all the backsack will be restored so they will just land on the previously visited screen but because the process was killed before that also means that its whole memory was cleared and things like that like the exess token which are stored in memory will also be reset to their default values so in this case null that means the user could land back on a screen where they only get when they are authenticated but the token would be null so this is one type of error that can happen with such Global State I also want to show you a different one let's take a look at this booking manager meeting manager for example imagine you have some kind of Maybe video calling app or some kind of app where users can have meetings with each other and there's first maybe an onboarding screen a login screen and then you have a feature for the meeting functionality itself where the meeting takes place and in order to just save the current meeting ID which might be relevant for multiple screens part of that meeting feature you might save that in a global booking manager meeting manager class here as a simple string so you can easily access that meeting from across the feature or actually across the codebase that is the point here if you would make this booking manager a Singleton here so it's a global object and Singleton with state is in the end nothing else than a global variable so this current meeting ID would be a global variable here you have no mechanism at all that cares about resetting that meeting ID when you want to join a different meeting so when you might finish the first meeting get back to the some kind of onboarding or um join a meeting screen where you have no active meeting that means you will always need to remember to set this current meeting ID back to null when the meeting was over if that does not happen it's very error prone because you might have already joined a different meeting and this current meeting still refers to the old meeting and you might now argue okay of course we can just do this manually we can reset the meeting at the end but believe me I have dealt with such scenarios before and this is very erpro because you really don't always think about these things if they happen in such Global objects like this booking manager and of course the issue I talked about previously with process death would also apply here but what do we learn out of that how can we now work around these issues first of all if you have state that really needs to be Global and needs to be accessible across the app like a session token then the only safe way or the only safe place to put that state is in local preferences or some kind of persistent storage so instead of just saving this in memory you would have your local preferences set up here with Shar preferences with a data store or something else where you take this token and whenever it changes you insert it into local preferences and whenever you need it you read it from local preferences again because the moment you save something persistently it is pretty much process dep proof for state that you might not even need globally like here which is only available for the specific feature you're in for this meeting feature booking feature here it makes sense to actually put this state in maybe a shared view model that is only shared between the screens of that specific meeting feature because then you can suddenly scope this dependency just to that shared view model and you make sure that the meeting ID will be cleared as soon as the view model will be cleared which will be after a meeting so you don't need to remember resetting that to null and setting it to the current meeting when you go to that screen so in short if if state is needed globally save it persistently if state is only needed for a single feature but multiple screens and then properly scope it just to that feature you will thank me later because this really leads to super hard to debug issues and practice number five and that is a quick one let's talk about package structure consistency or just architectural consistency all in all there is no single approach to package structuring that is Simply the Best there are many ways that work but at the same time there are also many ways that are objectively bad and a very big problem of bad package structures is inconsistent so if we assume you have your project rout directory here you might have different features and if we open this up we might have a dagger module and in here we also have a dagger module but here the dagger module is called dagger and here the dagger package actually is called di even though the purpose of these two packages is the same we call them differently and this just leads to confusion especially when new people join the team because if they find a DI package in feature B they would typically also expect there to be a DI package in feature a and the more inconsistent your package structure is the harder it will really be to spot specific classes that you might be looking for so the easy check as always if someone else would create a new file a new class in your code and they don't immediately know which package or module that new class should be in then your package structure is very likely not consistent so instead make sure to rename these packages here to have the same name whether that's now dagger or di is up to you I would prefer di since it doesn't couple your project to use dagger at least not in the sense of a package naming but most importantly should be consistent trust me these details really matter more than you think so I hope you enjoyed this as I said if you want to learn how you can build this specific architecture so one that really scales together with a limited bonus testing course that is available just for this week then check the link down below and save the 25% discount on all my courses other than that thanks so much for watching I will see you back in the next video have an amazing rest of your week bye-bye [Music]
Info
Channel: Philipp Lackner
Views: 7,712
Rating: undefined out of 5
Keywords:
Id: kKMFZ6iLX7s
Channel Id: undefined
Length: 17min 5sec (1025 seconds)
Published: Wed Jul 10 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.