How to Validate Forms with Clean Architecture (You're Doing it Wrong)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey guys and welcome back to a new video today i'm going to show you how you can properly validate your forms using clean architectural guidelines so we will use use cases here to do that because that's business logic i will show you how you can use a view model in this context and how you can send all these validation events to the ui and from the ui to the view model because that's something you need in pretty much any app that deals somehow with data so we will build a very simple registration form here where we can enter an email a password we need to repeat it and we need to accept some terms and conditions and then we can click on submit so if we just click submit here you will see we will get a bunch of errors that's what i will show you here email can be empty of course let's change that to something that's not empty we click submit it tells us that's not a valid email that's true so let's enter a valid email if we then click submit again that hour will go away however we still need to fix the other ones like let's choose one two three four five six seven eight for the password because it needs to be at least eight characters long we click submit um then it tells us okay the password actually needs to contain at least a digit and the letter we just entered digits so let's add an h or so and we of course need to repeat the password here so one two three four five six seven eight and an h we then click submit and it still tells us please accept the terms and conditions we do this click submit and then we get our toast registration successful where you would just navigate to a different screen or so or do whatever you like when it should be when it is successful jumping into an empty composed project in android studio let's actually start by creating a review model first of all i added this dependency here view model compose make sure to add the same one because otherwise you don't have access to view models in compose other than that this is a fully empty project you can just create an android studio let's go to our main package new kotlin class and i will call it main view model and that should of course be scoped to the specific screen you have here since we only have one screen i will scope it here directly to the main activity let's select class and make that inherit from a viewmodel just as usual okay so what do we now want to do here is the first step what i like to do is i like to create a wrapper class for our actual screen state for the ui state that we want to show so everything every single value that can change over time like the values of the text fields um oops like the single error messages or so those belong in the state so i will actually go here to my root package and because as i said we want to stick to clean architectural guidelines that means we divide each feature in presentation domain and data layers well what do we deal with here for validation we of course on the one hand deal with presentation so what's visible in the ui so we deal with our text fields some with our view models which is also presentation but on the other hand we also deal with the domain layer which contains our actual business rules and business logic which in the end is for example validating some stuff and for that stuff for validating an email for validating um a password for each of these we will create a use case uh what that exactly is if you don't know that i will get to it when we create that let's actually start by simply creating a new package here in our root package called presentation let's move in our main activity and main view model in there click refactor and then go to a root package again create another package called domain dot use cases or just use case as you like and that's where our use cases will be located we don't deal with any data related stuff here so we don't need a data layer usually you have that of course but here we don't so let's start in our presentation layer where we want to create a new kotlin class called validation state or let's say a registration form state because that's what it really describes so we select data class and in here we need to think about the the different things that can change on our screen like for example the value of the email the user entered but also something like the email error message which is a nullable string here so if that's null we just assume okay there is no error and else is just the error message and by the way the email is empty by default then we have the same for the password which is a string empty by default we have that for the repeated password repeated password which is a nullable one again we have that um actually we need to have an a password error here as well password error nullable string actually the repeated password is not nullable that's of course just the value we entered for the repeated password text field and then we have a repeated password error which is a nullable string as i said and we have a boolean actually whether we accepted the terms are not and that's of course false by default so the checkbox remains unchecked and since we also want to show if the user did not accept the terms we also want to have um an accepted or let's just call it terms error which is a nullable string again so we just initialized these values here with their default values how it should look like on a very empty screen you could say so when the user opens that screen that screen then this is the state that should reflect how it will look like at that specific time back in main view model let's create our state here because the job of the main view model especially in clean architecture is to make use of use cases so the use cases will contain the business logic like the validation logic here for example whether the password is longer than eight characters or not whether the email is a valid email that belongs in the views cases and the viewmodel will then make use of these use cases and map the results to compose state so it will call the validate email use case when it checks okay was that validation actually successful if yes then i will change the state we have here correspondingly so that it is correctly displayed in the ui with that error message or that the error message is hidden if there is no error stuff like that is a job of the view model so we can use we can make use of the delegate here our state by mutable state off and we import this here twice actually pressing out plus enter and we initialize an empty registration form state cool then the next step is let's actually create our use cases we could do different things here we could also set up our ui or so there is no real fixed order we need to follow but let's start or let's continue by i'm going into our use case package and start by creating a class called validate email press enter here so and let's now talk a little bit about what a use case actually is and what's the purpose behind that um so use case is a very integral part of clean architecture and it's kind of a middle layer between your view model and your data layer so the use cases would then maybe access a repository that accesses your database or it they would directly access some kind of data source and the reason why we have these use cases in clean architecture is that they are actually an excellent way to follow the single responsibility principle so that's the s of the solid principles which just states that each class function should really just do one thing should you really just have one responsibility or some people say one reason to change so if we have a class and we it could happen that we changed this because our email validation logic changes or because our password validation logic changes then that class doesn't have a real responsibility because there are two different reasons it could change and we want to solve this problem by making use cases so by having a class for really every single every single bit of business logic we have in our project so typically each use case consists of just one function which executes that use case so which validates the email in this case let's write that function execute and open the body here some people like to make this an operator function to override the invoke operator which i also often did in my previous videos but just to show you a different way you can also just call this execute it really doesn't matter you just need to make it obvious that this executes the use case okay so this function should now validate the email on the one hand we for example want to check whether an email is blank is empty so what we can do is if actually the email we pass here the email we want to validate if that's blank we somehow want to tell um the client who executes our use case hey that email is actually blank here's the error message please show that in the ui and the client would be the view model in this case so how do we do that and the reason uh the the way i will fix this or the way i will solve this is by creating a result class so we will simply return a result class here that will tell our viewmodel whether this validation was successful or if there was an error and if so which error there was and since this type of validation result will be the same for all of our different use cases let's go to use case and make that a class here if this type of result class would really differ per use case then what i would do is i would create that as a child class here as a subclass instead of the validate email use case so data class result and then have all your fields that are individual to the email validation here but since it's the same for all of you all of our use cases let's go to our use case package and create our validation result make it a data class and on the one hand we want to check whether that validation was successful or not and if it was not successful what is actually the error message that's a nullable string and that's already it so we can jump back to validated email return such a validation result and if that email was blank we return a validation result in this case it was of course not successful and we want to pass an error message that says okay the email can't be blank and that's how we really tell the viewmodel then whatever happened um of course in a real project i was using string resources if you want to learn how you can use string resources in a use case without actually needing the contacts then i will link a little video up here i will show you exactly that but for time reasons i won't do that here in this video so next step what else do we want to validate well the user could have entered an email that's actually not a valid email but that's also not blank so we also wanted to check that and there is a very nice way to check that which is patterns from android util email address matcher we pass our email and we check whether that matches if that does not match we know that's not a valid email and in a true clean architecture project you would probably abstract this out somehow so you don't have these android specific implementation details in a use case but that would really be too much here for this simple tutorial um so you would kind of wrap that into an interface like an email matcher or so and then you have an implementation of that which uses this pattern's email address blah blah blah but here let's not do that and the error message would be that's not a valid email and of course you can add a lot more validation here as you like i will leave it at that and just return a successful validation result in this case and we don't need to pass an error message and that's our very first use case to validate an email so it's a very very readable way so we just see okay if it's blank it fails if the pattern just doesn't if the pattern doesn't match it failed and else okay else it's successful and you can easily extend this can super easily test this function and this class now only has a single reason to change that is that our email validation changes okay let's jump to the next use case and that is to validate a password for that i will simply go to my email use case copy paste it oops copy paste it yes and we call it validate password so this time the parameter changes to password of course it will still return a validation result and here i will just check whether the password that length is less than eight super simple in real project i would make that add a constant so you can change it easier here we will say the password needs to um at least needs to consist of at least eight characters like that what else can go wrong well with the password of course it could be that we want to check if they're like like at least a digit at least a letter or so so we could say val contains letters and digits and we could check that for example using the any function which uh goes through all the characters and checks whether this condition that we pass here is true for any so for at least one character so at least one character should be a digit um actually it is digit and password that any um and one character of course needs to be at least one character needs to be a letter and if that is not the case so if it does not contain letters and digits so it might only contain digits or only letters we want to return that result the password needs to contain at least one letter and digit and else we again return the successful result next up we're gonna go to validate password copy paste and we wanna call it validate repeated password so really for every single step we have our own validation use case this time we also want to pass the actual password and the repeated one since we need to compare these of course and check whether these are the same or not so if the password is not equal the repeated password that was not successful and we say the passwords don't match and that's really everything we want to check you for the repeated one remove the import and do one final use case which is to validate the terms and conditions which is super uh super simple here because we just want to check whether the boolean is true but if you truly want to follow clean architecture that's business logic if you have that if statement and you want to put that in use case because imagine sometime in future you decide okay maybe the user does not need to accept the terms in a specific case or so and then you just need to change the logic in the use case and not in all classes that use the use case so let's go here copy paste validate terms here we can pass like whether the terms were accepted or not and remove that stuff here we just want to check if the terms were not accepted we say please accept the terms and else we return true cool now we have all of our use cases and the way we use this in the view model is we inject them in the constructor and when i say inject uh yeah i usually mean by using something like dagger here the dependency injection framework but since that would make this year too complex i will simply instantiate these here all in the constructor because these classes don't need any parameters don't take this as as a best practice usually you want to use dependency injection so let's say validate email is a new valid email instance private valve validate password and yeah private eval validate repeated password private val validate terms like that and now our viewmodel can easily validate um the fields with these use cases and even if there are other screens which also might need to validate an email maybe the login screen then they then they can just use that use case again and you don't have to duplicate code duplicate validation logic and that's what really makes use cases cool and helps your projects to really scale on the other side usually for smaller projects they are way too overkill i recently made a video here about why i think best practices can sometimes be dangerous um so in case you're interested in that way i think that uh you can also it's not too long ago you will find it on my channel but there i talk about yeah why use cases for example sometimes are way too overkill but here i just want to show you how you would do it in a really big project cool what's the next step the next step is that our viewmodel actually makes use of these use cases and properly maps the results to our state and for that i like to create a c-class an event c class where each event corresponds to a single thing the user can do on the screen so a single thing could be entering something in the email field it could be entering something in the password field it could be checking the terms checkbox it could be clicking the submit button all that all that things that kind of involve user interaction so in the presentation package i will create a new class we call that registration form event make it a sealed class and in here we specify these events on the one hand email changed passing the new email like this we can then duplicate that some more times we have password changed passing the password repeated password changed let's put that on a new line repeated password and we have um accept terms val is accepted cool and we actually have one more that's an object just submit so when we click the submit button because that of course doesn't need any parameters since we just click on a button so back in menu model we will now have a function on event which will receive such events from our actual screen and here we can very easily distinguish between these different events on the one hand we could have an email changed event we could have a password changed event we could have a repeated password changed event we could have an accept terms event or we could have a submit event and in these four cases it's very easy because we just want to map our state if we entered an email we know we want to simply set the state to state at copy and we just want to change the email to whatever we got from the event and sometimes i get the question why we use this copy and that's just making an independent copy of the state so we can't directly um change the fields of our state so we can't say state email is event email because email is a val and it also should be a val so you should always rely on immutable fields so the way to change the state is we need to change the whole state object so we created a copy of this and all we do is we change the email of that copy and here for the password case we change the password of that copy here we change the repeated password and here we say accepted terms is event is accepted um what's that did i make that a string um yes i did of course that's a boolean okay and then we have one last case left that's submit which is a little bit more complex because we of course need to uh make use of all of our use cases so let's call that submit data a function i will create we can press alt enter and say create function submit data and here let's just get our different results by using our use cases so email result this email a valid email execute passing our state email we have password result valid password execute passing state password repeated password result is equal to validate repeated password execute you need to pass it both the password and the repeated one and we have the terms result which is validate terms execute state accepted terms cool and now we want to check whether there is actually an error or if that was successful so has error is i'll create a list here passing all these different results so password result repeated password result and terms result and if any of these results um actually has an error message that's not now so if any has an error then we know there is an error for at least one field we could probably also check if any is not successful that's probably a little bit more readable then we want to check that if we have an error we want to map our state again this time we say email error is email result.era password error is password result error message repeated password result error message and terms error is terms result.error message and after that we want to return and here if we don't have an error we want to launch a curtain actually in view model scope because now it's actually the other way around till now we only send events from our ui to our view model here now we want to send an event from the viewmodel to the ui because every time i need to tell the ui hey now the validation was actually successful so what we will do is we will create a channel private val um and that will be what we call a validation event channel let's say so the purpose of a channel is to do exactly that what i just described it is to send events into this channel and the ui can then collect these changes by simply collecting from a flow i like to also create a c-class for this here in this specific case we only have successful events i will still show you to do it like how you would do it in really a really big project or in a normal project which is called validation event we don't have that no we don't and here we have a success event like that so now we can have that channel of type validation events and since we don't want to expose this channel to the ui since we don't want the ui to send something into this we want to expose an immutable flow so val validation events is validation event channel receive as flow um you will sometimes see people do this with a shared flow a shuttle should be used if there are multiple observers so multiple screens observing this thing the same flow here we only have one observer which is our screen then you should use a channel i'm going down here this is now executed if the validation is successful so we can say validation event channel send and we want to send a success event that's now everything for our view model and the next and last step is to implement our ui to send these events to this view model here and then we can already try it out so let's go to main activity in the surface i'll create a column which i will assign a modifier of fill mag size give it a little bit of padding like 32 dp and import dp we want to say okay the vertical arrangement is center so we center all of our stuff horizontal arrangement will not be so necessary because we just make all the stuff fill the whole width so let's actually start by getting an instance of our review model now we'll do that here so valve viewmodel is a viewmodel we use this composable function and that will be an instance of main view model then we want to have a very variable here of our state we model that state and i want to have a context because i want to show a toast when we actually successfully validate all fields local context that current let's actually start with that successful case so how do we now observe this event channel i talked about we will use a launch defect block and for the key we pass the context we just make sure that as soon as the context changes this launch defect block will will be relaunched kind of which i think is never the case but i like this more than just passing through for the key which would never execute this launch defect block again so let's go here and have our view model validation events and we want to collect these so this will now be fired every time we send something into this channel which in this case is only the case when we successfully validate the fields so we can now check when that event is actually a success event we want to show toast toast make text passing the context passing a text registration successful and passing a length and saying that show just like that that's already how we can listen to these events we send here into this channel so every time we call send and in the ui this collect block will be called with that corresponding event we send into this so in here in this column we want to have some text fields i'll just choose the material once here putting this on a separate line and the value of this one is of course email when we change this email text field we want to now use our review model on event and we send the email changed event passing the new email so it and what else do we want well we want to have an is error so whether the text field should show like should be shown in red whether there is an error or not which is state dot email error not equal to now that we know there is an error then what else we want to have a modifier a modifier fill max width we want to have a placeholder which will be a text composable that just says email when i have a keyboard type keyboard options actually keyboard options um and here we can pass a keyboard type which for this one is keyboard type email so just chose the the most helpful fields on the keyboard for entering an email and is there something else we would like to pass here i don't think so so we can simply keep it like that and below this we want to have a text actually only if the state email error is not equal to now so if there is an error we want to display in our text and this text will be equal to the state email error and the color of that will be in our error color material theme colors error oops and then we can simply put a spacer below that passing a height of 16 dp and now we can just do a bunch of copy pasting and copy this whole stuff do it for the password field so stated password we send the password changed event the error is when we have a password error being not equal to now placeholder will be password keyboard type will be password and here actually one thing changes and that is you want to add a visual visual transformation um of a password visual transformation that just makes sure that you see these password dots and now the clear text password which is of course something you usually want with password text fields the error message for the password should show if the state password error is not equal to null and here as well password error rest is fine let's copy this stuff again pasting it down below for the repeated password so text field repeated password repeat the password changed repeated password error repeat password that's fine for the keyboard tab that's also fine repeated password error repeated password error and now for the terms we can simply paste the text field we want to instead have a row um yeah it'll just fill the whole width so fill max width the row will consist of um a checkbox the checkbox is checked if the state accepted terms is true and when that changes when we tap on the checkbox you want to save your model on event accept terms and we pass it so whether we accepted it or not um i think that's it after that we would like to have a little bit of horizontal spacing let's say atp and then we have a text that just describes um except terms it's like usually what do something like accept terms conditions or so or yes i have read the terms and conditions i'll just keep it simple here and below that we again have an if condition if the state terms error is not equal to now we want to display another error text where the text is equal to state terms error and else not else the color i mean is the arakada and then the only thing that's missing below this terms error is our actual submit button and here on click when we click that we send the submit event to the view model i'll make sure that's actually aligned to the end alignment.end and the content of that button is a simple text um where we say ok or register whatever you like when i think about it we should also add um these alignment modifiers to our single error texts i guess yes let's do that actually not for the terms one but for the other ones um here repeated password error password error and here the email error so that they just stick to the end of the column i think that should be it let's try that out and let's first of all click on submit then all these fields give us errors except for the repeated password one because these passwords actually match like empty is equal to empty but let's enter something an invalid email like a password that consists only of numbers let's also repeat that and now let's accept the terms i can see that's not a valid email password needs to contain at least one letter in digit please accept the terms let's check that that error goes away let's add a letter here that goes away and we enter a valid email and registration successful so it seems to work just fine and now thanks to making these single use case the single validation functions use cases we can super easily test these just let me just write a very simple sample test case we just go here to valid email alt enter create test select junit four have a setup function click okay that's a unit test doesn't need to it doesn't need any um oh it actually does um so that's one thing one reason why i would write an abstraction here actually for this pattern's email address because you see that comes from android and if you want to test this you now need um you know need an instrument test which is slower so that's actually one good reason why you should write an abstraction for this in a clean architecture project let's do this for the validate password use case instead then um here we remove the import create test setup click ok select the test source set and here we can then just have our instance or test instance about identity variable a validate password for example is a validate password use case and in setup we initialized that um of course that's not a course about testing uh now but i won't go into detail but i'll just show you how easy that now is um that's a new valid password use case you should just make sure that you use brand new objects for every single test case so that's called before every single test case in this class and then we could have a test case here no siri uh i don't want to test you we instead say function oops we can say okay um what could we test here take a look in the use case i want to test if this is actually uh if the password or let's say we enter a password that's only only consists of letters and we actually get an error so password is letter only returns error that's what we expect so we can say okay a eval result is validate password.execute i want to test this with um a b c d e f g h which i think is eight characters so that's fine and then we want to say okay we assert that result successful is actually false i think with junit we say sort equals and never use this assertion framework so we're going to assert whether the result successful is false if we now run this test case we see instantiating tests and then you can see it passes if we now go to validate password and we change this here for example someone actually messes this up and removes this letter block we rerun our test then it should actually um it still passes uh yeah of course uh because i removed the wrong block so right now it would check okay whether it does not contain digits then it's an error so if we remove this instead and we run the test then okay it actually catches the error it expected to be true but it was false so yeah working unit test just has a very quick demonstration how you can now do that with the simple use cases i hope that was uh helpful um and i will link the relevant videos here regarding best practices you can just click here if you want to learn why best practices such as use cases can sometimes be dangerous and other than that i wish you an amazing day an amazing week and i hope i see you back in the next video again bye-bye
Info
Channel: Philipp Lackner
Views: 41,781
Rating: undefined out of 5
Keywords: android, tutorial, philip, philipp, filipp, filip, fillip, fillipp, phillipp, phillip, lackener, leckener, leckner, lackner, kotlin, mobile, clean architecture, use cases, use case
Id: zu8lQSVw4vk
Channel Id: undefined
Length: 41min 7sec (2467 seconds)
Published: Wed Apr 27 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.