iOS Dev Mentoring #008 - Test-driven MVVM with RxSwift

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello and Dre hello guys hi Andre hey Mike Mike hey guys so today i OS have mentoring number 8 test-driven mvvm with RX swift and the goal is to have an overview of RX whipped in reactive programming we're going to be designing and testing models via models also testing the reactive state transitions and binding model state with service requests and via motor state with the UI and also handling animations and reloading just partially the table view weather then every time there is a state change we're drawing the whole screen will be trying to reload only what's necessary with animations make sense yeah definitely so tell us a little bit about yourself well my name's Andre I've been working as a software engineer for about five years and currently I'm working on a bank project and in the last couple of weeks we've had a task to make a form to make a screen where a user could fill in banking details to make a payment when he or she can put in the bank account number put in the payment purpose and find the payee using the backend API so and the design was pretty complicated so we came up with a solution of a tableview but it was a pretty much a big big mess because there's a lot of code and this is the reason why I I came to you guys with the question how would be to make it more properly structure it's better less code that's the goal right yeah let's code less mess and more understanding of the way things works for the people who come after us we need to respect people who come after us right exactly in testability as well right an easy way to test doesn't all right so you sent us a project demonstrating this feature yes can you see my screen yeah so here we have the form when you select a field for example the Ibon you're gonna focus on the field and as you type you want to give some suggestions right yep when you press a suggestion it it has some information inside it and as you could see the bank name came a little bit later because there was a request mm-hmm so when we selects a suggestion here it doesn't feel only the Iban field for example you may feel other text fields as well yes and almost all the fields are optional because the backend doesn't guarantee that there's going to be any specific data in it right and the same thing for a flex number we can also search yes yes and when you select also it's gonna feel more than one field whatever the back-end could find yes but the bank name we don't search right yes the bank name we don't search it is requested from the server right on the basis of Iban and text number but we can edit the bank name yes yes we can and the comments the same yeah comment is is optional it just needs to be more well it just needs to be that it doesn't really matter currently if it's one symbol or empty mm-hmm okay so that's the challenge when you select something you hide the other cells correct yes and as you type you add more cells to the table view and when you select you need to reset this state go back to all the fields displayed and update the fields exactly it's it's a simple project that I made because it's like I have no right to rebuild the original project and and the real one there is also an amount cell and a source where you could select if it's an account or a card or a deposit or you could manually plug in the card or account number or something else the source of the payment so it's a little bit in real life is a little bit more complicated than in the project that I sent you guys yeah I imagine you probably also have a button they is enable or not depending on the validations they are yes yes yeah it's a green or gray course yeah it's quite a complex screen right it looks simple for the user but yes very much classic yeah there's a lot going on here so what was the the hardest thing about this implementation what was the thing that you're like not happy about the thing that I'm not happy about is the way we work with with all these sell new models and models because you know when you work with a table usually you you think you you think about an array of some of something and with these specific cells you need to address each cell individually so you need to have a model for a specific cell you need to have a model for a specific cell and you need to know which sections which cells to reload so this mechanism became like very very large probably I would say and you also need to you need to observe all the multi values inside each cell so we need to observe basically everything and sometimes when you fill in four out of five cells you need to make a back-end request to get the bank account or to get financial conditions to show the user if he can break he cannot put in that much money and you also need to change the balance of an account or a card that the user chose that's the part that I didn't put in but I think it's it was pretty complicated to do that the way I think about table feels like this is that every cell should have its own model it may be its own controller depending on the pattern you're following and then you also have a main form controller or form view model that coordinates the cells because you want to centralize that how you control the state because if you have many things changing the state at the same time it's a nightmare when I have a centralized observable for example the UI will observe it and depending on state changes you will withdraw the UI rather than having many different signals many different publishers sending values in different keys and not centralizing them because that's how you get in trouble that's how we got in trouble when you have many things they need to listen coming from different sources and many things can change the state of the tableview you might even have race conditions so it's very complicated it's better to centralize it so it can have a center of your model and you can have a view model per cell to keep this state and as you said like newcomers to the team it's almost impossible to understand what's going on like when you have all these listeners and all these signals left and right like it's it's gonna be very very challenging most probably they cannot work like that you know one other thing that was pretty challenging is that basically there are two ways that a change can come a change can come from the user typing it or a change can come from the user selecting a preset and here you sometimes had a collision when we were observing the text field editing changed and we were also observing some text that was set so sometimes we had recursion and simple exceptions and it ways was pretty tough okay why don't we start this feature from scratch that's driven with pleasure all right so let's create a test and cold is what is it's a payment form right yes so payment form view model tests let's create our first test so I just said you would like to have a centralized way of defining this state because the way you think about a form is an array of fields right yes so we can create the payment for view model as these centralized place that controls the payment form so it knows about the fields inside that form you can chose the state for the form so let's start here with our payment form okay so instance shapes the form of your model and that's our system under test the sut you're familiar with the name right of course excellent and one we need here let's say we need an initial state right so we are testing let's say the initial state and let's say the initial state or this form what is it because it has many states let's think about that you have the state where you have four fields you have this state when you have only one field and you have a state when you have one field in a bunch of suggestions make sense well in my case one field and a bunch of suggestions and the one before was the same state it's just that in the same second section you had something so let's say the state here is that we have four fields i bon text number bank name in Commons okay that's the state of the form the how many fields you have in the form so let's say a state where you have fields and let's say you have a bond text number so need to define what is this state property here we have this state where you have a bunch of fields now let's say it's a field of your model and you also have a case when you have only one field let's say the focus state when you have only one field and a bunch of suggestions for example there could be a suggestion via model for example we have the state where you have a bunch of fields and we also have a separate state when something's focused we have only one field and a bunch of suggestions which is represented by a different view model rather than trying to define a very generic view model that works for any field let's treat them differently things they're different they should be treated differently at the type level so have these two states you can be in and the initial state is a bunch of fields right so let's create here the Ibon is a field view model and let's say the text number is an order filled with your model this can go in an array in the assertion so this is the initial States let's say you have only two fields you can simplify the logic now and let's return just empty so we see a failing test let's make this equatable because this is just data so we can get a Equality for free here so let's make this equatable as well and the state is just a state continuity in the assertion right failing test of course okay so let's pass some fields for a payment form of your model let's inject the fields Iban text number tax number let's see if we can get initialize it for free okay and now we can set Ibon text number and a tie should pass boom we have the initial State okay let's see the next test what is something that can change this state when you focus on a field right yes okay so we can can check that so Iban focus state first of all you should only include the urban field and the initial state includes all foreign fields so let's say if we get a Ibon and we send a focus message to it we expected the state now to be focus I ban yes right but for us to be able to observe this state we need these states to be durable exactly it needs to be unobservable so let's comment out this test let's change this to be on observable now observable of states and the initial one could be just the initial state so let's import our X Swift and now we need a way to extract the value from the observable you can create a spy for them a state spy for example and it needs with an observable server both state and here we can observe subscribe to the observable and every time there's a new state we need to capture that state so we can say self dot values append state so let's keep state in a private VAR values it's an array of state make sense so far does and of course we need to add it to this pose bag so that's great here a private dispose bag alright so now we can get our state as this pie of the SCT state can emit the observable parameter name in the init underscore values that's it that's why we need to pair I will waste a lot of time here alright so now we are back to a working state we can investigate every change that happened in the state of the system under test with this is PI and we know that it had only one state the initial state make sense definitely okay so let's go back to the other tests when we focus on the Iban we want the state to be initially fields and it should transition right the second state because we're keeping all the states here so you can actually check all the transitions in that states by that's pretty handy it should be focus high bond and no suggestions right that's the second state before you type anything you shouldn't have anything correct true so now we need a way of focusing in the field of your model so here we can create a focus and what is the type should be a behavior relay or a publish relay something that for example pressing a button could trigger an event like it should be injected we could use a subject the subjects can fail and complete but focusing never failures and never completes we can try with the behavior relay so behavior relay it's same rx cocoa behavior relay but behavior relay needs of value but focus is only a string right he never holds the current value what could be avoid no could be void yeah but a behavior relay every time you subscribe to it it sends the current value and the current value will be void but it would trigger in the subscriber well we could keep one or we can use the publish relay a publish relay doesn't have a value when you subscribe to a publish relay you only get the next value when there is an event that's the difference so publish relay should be enough to send values into this dream but now we need to we need to implement around equatable okay let's return true because we don't have any values to compare yet let's have this dummy implementation make sense this let's run the tests failed okay so we have a failing test we're calling focus but this state is not changing yet right so we have an initial state and we need a way of transition in this state when some events happen when the Iban focus is called we want to transition to a new state right so we can map this event focus and we need to forward the new state what would be the new state after you focus on the Ibon focus right ban yes and the empty suggestions yes so we mapped that event into the new state we want but now we need to merge so we're going to have a centralized observable that will combine every states change and delivery in a single pipeline the new state so you can merge the observables and now if these event occurs we're going to get a new state in the initial state is the fields make sense I'm a little bit lost here right now so we have two observables here right we have the PI bond focus and we have the initial States and the result of the merger will be observable state yes you can merge a collection of observables into one single observable so anything that fires here will be merged into one observable okay and that's the goal when you're defining States transitions in a reactive way you want and have multiple streams or events that can happen and then you keep combining them urging them concat they're a bunch of operations you can perform to combine merge concat streams into different streams mapping the values flat map for example here we get and focus event and we map it because this focus event is void right and we map that void into the new state and we know that when it's an Iban focus the new state is focus I bun so it's in a centralized place all the state transitions and the test is passing home so initially it was fields and when we call focus except it transitions to a bond with empty list of suggestions now we want to do the same thing when there's a text number so when we focus on the text number we want the transition to be focused text number that's not the test should fail it does and we need to do exactly the same here we get a text number focus event and we map it into a focus text number now passing does that make sense Andre yeah that does so that is to centralize the state transitions so they user interface we listen to one single state observable and react to those changes rather than having to listen to many separate distinct signals this is what simplifies the state transitions now we combine all the things that can change the state of the screen into one observable okay so the test is passing now let's do some refactoring here yep got a couple tests already so we have a bunch of the application here let's create a make a city function private function make a city this should return let's see we need to return the fields and the system under test so this is some other test and let's say fields and fields is gonna be AI bond which is a field of your model and text number which is a field via model we can move this logic here turn ICT and Iban text number so now we can create the city and fields from the make sut function and this is now fields dot text number make sense this and here we don't need a few to me oh we do we do and we want we want the Iban and tax number to come from the fields so perhaps we can have a combination of all the fields yeah because if we add new fields in the future we're gonna break a bunch of tests right right exactly if we add new fields and we want to add the bank name at the comment we will break a bunch of tests and we don't want that so okay we can create here let's say we have the fields Ibon tax number and we can maybe have a hole which combines all of them which is a collection yeah okay so we need to pass now collection my bun text number so here we can just say fields thought oh so we want all the fields initially yeah and we don't need the array as well because this is the array right all is the area okay I like that refactoring yeah very neat and here it's thought I bond use the text number so now the tests passing eliminated sound application that's good anything else you see there think that's it so far Andre it looks very good make su tu looks pretty scary but the rest looks better yeah suddenly scary when you declare it what do you use it this awesome because let's say now we want to see what happens when you focus on Bank name what is the state's transition shouldn't change the state right yeah it shouldn't so doesn't change state so let's say now we want to add a bank name we can just create here over bank name you can just add a bank name here field your model and pass it through to the view model field of your model now this is Bank name so that's how we can add new fields without breaking all tests the tests have this abstraction all fields and of course we need to add the bank here because we added a new field okay it's not passing here we don't want a stage change correct yeah yeah what happens when I select the bank name nothing yeah that's true nothing happens okay nothing else should be all all right without the behavior we want now can it the same thing for the next one what happens when I select comment nothing happens so nothing happens right now but maybe there will be a requirement in the future that some requests might be fired you know yeah there may be some side-effects from doing that okay actually there is there is a requirement once you complete typing a comment if everything else is filled in you need to fire a request right so as soon as the whole form is valid yeah but you but you can change it as well right we need to pass now the comment here in the initial state so that's it we add a two new fields without breaking the previous tests and that's let's the go yeah and in the future we could have like a button of your model for example you know if you need a button to actually submit the form mmm it would create something else like a buttom of your model okay so we know that there are no side effects when you focus on the comment that's good what is the next thing that should happen when I start typing the I bun it should request something from a service from a back-end the suggestions from the backend right is that right yep okay so I bon let me copy this one so let's say this I bun now it has a text relay where we can send new states like a query let me add here so this is a behavior relay right because you need to keep the state the current state the text what is the initial value empty yeah okay so we have an empty state and when we send a new value here we want this to be transition to fields with some suggestions that comes from our backyard make sense yeah okay so we need some kind of suggestion yeah which is an array of for the no suggestion as a model so let me create one here let's actually stash eight some of them and say two to the essence we need a way of injecting these ejections right as you said this might come from the back end right we should have some sort of mechanism for requesting the suggestions okay so let's think about that with your service right a separate object that will perform this request we don't want the view model communicating directly with URL session or our limo fire or any networking library I agree yeah yeah so we can create a protocol suggestions service and it can have a method perform requests it's a suggestion requests we need say query write a string yep mm-hmm he needs to return no observable of suggestions that's it does look right Andre yeah yeah it does okay well probably not not maybe not an observable maybe a single because it should fire once yeah that's it so we have our service and we need to inject this service now here right in our view model as well let's say we have a service as a property suggestions service this is gonna break our make SUT right so we need to pass a service here well we can create a service stub and stop it we stop the suggestions as well so it's gonna work okay so let's create some pretty fine values here this table is the query and suggestions so suggestion suggestion cool and perform request should return if the request query matches the stub query we return they stopped suggestion suggestions or we return empty yeah you either use the stuff query and get the stub suggestions or you get nothing empty collection the stubborn 149 we need a value there instead of just the string type a query that's it so now we can provide this stub here yeah okay so we don't break the other tests because we have a default value and there is a type of service yeah okay back here now we need to create our stop service pass it in here and we need to accept the service stub query we make sure that we go back this service is stub suggestions so I don't need these suggestions here I think it's expecting view models in the state not the suggestions right hmm okay good catch yeah so we could make this service return suggestion view models but I think that's a leaky abstraction because the service is in a higher level yeah so the service should return simple models right like the suggestion here true same well said we should know about few models yeah so we need a way of mapping that so maybe we can start these you can create a suggesting view model from a suggestion let's say suggestion and it doesn't have any values yet because that's how I like to define my types and my values I only create things when I need them so I let the type system and their tests guide me what do I need now what do I need now and only do the minimum amount of work at every stage so we can create now of your models from suggestions which means we can let's say map this into view models yeah fantastic just in to rename this context changed state no tags change provides suggestions based on text yeah it's not a query based on a text okay let's run this test hopefully should fail because we're not doing this yet and it does fail okay so we need to add a new event here to our state and this is Ibon dot text so when there is a change in the Obon text we need to map that then we get here the query and we need to perform a service request is that correct yep so when there's a new tax change in the Iban we want to make a sugestions request so it's service dot perform and here we create a request with the query but this returns unobservable while we returns a single so can use a flat mount here otherwise are going to have an observable of observables but what we want is an observable of states and can you catch it as observable yes okay cool but these returns an array of the result of that is an array of suggestions but we want suggestions view models so we managed so we need to do suggestions dot map suggestion via model well it's a focus I bond with suggestion right that's the new state yep just not iPhone it's done okay that's not a test that should do it nope we broke everything now imagine doing this without tests ouch okay the problem is that remember that I said that a behavioral relay it meets a value as soon as you subscribe to it it emits the current value so as soon as you subscribe to it it fires the current value and we don't want that only when it changes we can skip one we can skip one yes skip one okay that should do it boom awesome now with all tests one simple signal you forget yet there one transformation you forgot to add into this stream will be firing these requests when not needed for example so without test is very dangerous to use reactive programming style it's very hard to keep track of everything that is going on if you don't have a way of making sure that this state transitions work according right or you have a little lot of time to test well you see like one small change like adding and skip changes the whole game you change the whole thing and it's so easy to address and not run all the manual tests all over again it's just as keep what bad can you do if I remove this key one line of code like one change operation can break everything so okay another problem is see with reactive observations is that when you update the text again it would fire again the requests even though you are ready made a request for that query does that make sense the texts changed but it changed for the same value that was there before yeah we can avoid that so I can run the test and see it fails because now we have these two straight transitions here and we don't want that because now we're going to be firing requests that we don't need we want only one so we can use another operator another operation called distinct until changed so we're going to ignore values if they match previously emitted value makes sense yes boom des is passing so if we if the I bond keeps sending events for the same text we ignore it until there is a new text so we make a requester or maybe you want to perform the requests all the time 10 yeah you make sure not to have the distinctive you changed it depends on your use case you have the test right yeah make sure you don't you mind that by adding a test what about the tax number when I type the text number should I make a request as well yeah you should so tax number now should we turn tax number with the suggestions as well so this should fail yes it does fail so we need to do exactly the same thing but for the text number the application the application one step at a time let's run a test if you pass this really we factor okay it's passing so when you change the text number we also well we fire request when they request complete we provide a new state fantastic now let's put these in a function you know helper these two are the same so so what is this is a search right it's a yes search search for a specific field and these returns observable of state so we can copy these in here and replace instead of I bun we're going to have a generic field in field here as well that's it now we can replace this with a search for Ibon and search for tax number where the tests the passes we're good boom nice fantastic what else so what should happen when we change the bank name nothing nothing really bring the name text change states doesn't change change state so we don't need a service here and this is the bank name and since we don't expect any side effect we do it only once right any query doesn't matter any change the bank name should not alter this state you have to form okay so this test passes straight away because it's just a safe net you know to make sure that we had not yeah doing something when we shouldn't is you save you from bad with factorings you know increasing coverage yes I think that there's all the states musicians right oh no when you select a suggestion oh yeah okay what happens when you select a suggestion you need to go back to the all fields right so we we lose the focus and we go back to the form with all the fields right so I burned suggestion selected state includes all fields that's it so we are going to expect that after you selected it should go back to fits all so the transition is initial state after there was a text change and then when you selected a suggestion make sense yeah so now we need a way of invoking a suggestion we can get a suggestion from its inside here right right in the state so when we fired an Iban text change we will be in this state the focus state so let's say switch state values stored last case let focus suggestions so we get a bunch of suggestions we can say suggestion let's say the first one dot select so we're gonna fire a selection in the suggestion default we're going to fail I expected suggestions in the current state so let's say we're suggestions sometimes it's not empty okay so if we have suggestions then it's not empty we fire this suggestion otherwise we fail because we are in the wrong state so now the suggestions your model should have a selection really and here's why I like to separate my models from via moldos person who might suggest your model I don't want my suggestion model to know about Iraq Swift I don't want my main models normally about rx whiffed a framework or selection right and this in this case why would the suggestion know about selection in this part of the system yeah the selection is very coupled with the user interface right the view model so we should be in the view model rather than in my main model my model doesn't know about UI doesn't know about our X we've tore any other framework it should be a simple struct or simple class that is an inherit from anything does that make sense it does ok ok so we have a way of selecting a suggestion so again we need to implement equatable and for now let's just return true because there's nothing to equate yet what we're going to get it there okay failing test we are selecting the suggestion but this state is not changing so we need to add a new way for this to happen how can we observe suggestions selections of solutions yes mmm I think we need a new stream for that so private let what is this a suggestion selection is a publish relay of void right it's just a stream where we can send events to say something was selected and we should inject that selection into the suggestion of your mother so we can use another initializer suggestion and the select which is a publish relate okay so instead of the view model creating its own relay we're gonna inject a relay into the view model so we can observe it from the main view model make sense yeah so here we need to call this initializer just so we don't break the tests right now then we can get rid of this let's just create it really yeah so instead of creating it we receive it okay so here when we create our view models from the suggestion we need to create it with the suggestion and the suggestion selection that's how we can observe selections from specific view models [Music] okay so now we have this mechanism for injecting the relay into the view model now we just need to observe it so what happens when a suggestion is selected we want to map it and return a new state which is the same as the initial State correct yeah the top and then the form comes up okay so we added these new thing into the chain every time there's a selection we are going to return the initial state all the fields make sense then does let's run the tests it's passing nice so let's refactor this actually because this is ugly look at this thing yeah yeah we can do better thing create a test helper to extract the first suggestion from the state so that's greater here private extension state let's say first suggestion it's a suggestion view model correct switch self case let focus with suggestions it should return the first one yeah default yep that's it okay so it's exhausted yep right so now we have a simpler way of getting a first suggestion just so we can invoke it so suggestion equals state dot values the last dot first suggestion but this returns an optional so we can unwrap it exit iam rock cool and if you failures we set this message if you fails to unwrap so this needs to throw suggestion select cool awesome that's it now we can do the same for the text number exactly the same okay we proven that selecting a suggestion regardless if it's coming from the text number or not who set the selected state so I think we are done I just want to look at these models now because you said that this suggestion here it has the high bun right and it's optional true and the text number which is also optional directly okay what about the field fuel model are we missing something here well we have the title right yeah we also have a title yeah what is the type of the title does it change what is a fixed no it it is fixed and it depends on the on the kind of field so it's just a string okay so not to break the test let's give here a default value but now if we have a title we can compare the view models per title true so let's create a new test case let's call it filled view model test and test is equal when title matches make sense well but it also has text and the text also needs to match right for it to be equal so when title and text matches so let's create here field one is a field view model with title a title and another field let's say another title and they should not be the same something is broken okay so we need to pass some values here let's give it one on one thirty two four four four cool - now the test is failing let's return false passing next test let's make sure everyone is equal one title equals title f3 this is the same as if one title but f3 text is a text now F 3 and F 1 they have the same title but they have different text so they should not be equal yeah excellent and text value is equal text value so the tests oh good uh-huh it should be text number here okay okay passing let's move this into on your file you okay so this field of your model now has title in text and the suggestion needs also some text right [Music] so let's see suggesting your model tests test text is based on provided suggestion values so if I create a suggestion and I give it an eye bond at about one two three and text number four five six if we create a suggestion of your model one-third equal should be that the system under test text let's say should be I bond one two three text number four five six just to simplify this use case does that make sense yes I bond is suggestion dot i bun or empty in text number is suggestion tax number that's for some wrap it now so notice how they should be equal yes and what should happen if the value is not there so if I bun is not there which is don't mentioned so we show only the tax number correct you're correct without I bond this should crash no ok failure so now we need to unwrap this thing I bun and tax number so Ches some Python some tax number we have covered here if she let okay and case let none and some text number you want just the text number okay now with our text number should be Ibon watch it three thanks what if we have no suggestions can it happen well you definitely can define that right yeah oh well no I burn a note text number I meant when you said no suggestion you cannot create a suggestion view model without a suggestion yeah I meant without the iPhone okay and tax number yeah but that's a better name it's not the test again okay let's move it to a new file exit tests report run again okay I think we are covering all the states we need here the suggestion has a text and you can select it right the field has a title has some text and you can focus on it you have all these transitions that can happen depending on what happens with the viewer models inside the form in it's all centralized in one observable and you have an external service or performing requests that returns array of suggestions so you have to suggest in query it's just a query and a suggestion may contain an Iban it may contain a tax number I actually just one improvement yeah in the merge function observable yeah in the observable merge I think we could make a lead that would contain all the initial fields fields and the one in the just right okay so then we need to return here to say all fields yeah this isn't state dot fields Yeah right and we don't need to capture this thing here nice you can probably create a function to remove this application as well like private function focus for a field that also returns an observable of state so here it's field feud yeah we called your focus or I bun and focus for tax number uh-huh that looks nice yeah and much better fantastic that's it those are the things that can trigger states changes it's easy to see is easy to change them when they happen it's amazing okay let's try to put this in the screen so let's move these to production I'm just put here in the app delegate right so we need to import all right Swift import rx cocoa alright so we have our view models now we need a view controller and this is a payment form view controller let's say it's a UI tableview controller right it's a table view yeah so we need to instantiate it with a view model so this needs to be an optional and in the viewdidload we to bind the view model with the table view right and we want to analyze that states that changed in only updated screen where needed correct correct to react to the states yeah because I noticed that in your project you are controlling the updates from the view model as well and I recommend against it because it complicates a lot of your model and out of your model gets coupled with table views or collection views what you can do is make the UI observe this state and the UI makes the decision which rows to update mm-hmm okay and if you're using rx width they provide bindings that does exactly that for you you just need to define a data source first we need to import our X data sources are you familiar with data sources yeah I use them a couple of times but only in the test project right let's see is one here me it's not obvious how they work I prefer to see cellforrowatindexpath right if you do it yourself then you need to handle all the states transitions yourself right yep you don't get these facilities for free when you're using an axe with you get all these bindings for free if you follow their conventions in the UI side so let's see if you created rx animated yeah yeah datasource table view that's it and you need to define a section that conforms to animatable section model type so let's create a type 8 as here because those types are going to get out of hand very quickly it's an animated section model it has a section in an item type what is our item type well I think that first of all the section type model type could be just a string now the cell type is what field your model well sometimes it's a field and sometimes it's a suggestion view model right yeah true so we need a type to disambiguate this let's call it a cell your model so if it is what is it a field it's a field view model or it's a suggestion suggestions via model okay so that's the type for this section but if are using these data sources he requires you to conform to this identifiable protocol which is single into the new identifiable protocol in the swift standard library so let's make it confirm to it he needs to implement identity so I think we can use the identity of the modules inside the cases right they're gonna conform as well so yeah so case and delegate that field every of your model here needs to be identifiable but you can implement it as extensions in the UI side your of your models don't even need to know about data sources so here is the view model then we just return your model identity and case suggestion return the of your mod weight entity as well okay so the fit view modernise you get in fireball as well what identifies the field what is something that is unique to the field title yeah okay for this use case the title is enough okay now the suggestion of your model also needs to be identifiable what and it finds a suggestion probably the text right yeah oh it's identifiable type which is the i-x swift local okay what now it also needs to be equatable okay the only holds data so that's fine equatable yeah now we need to define Excel configuration so we get here a data source the table view the index path and the item okay so configure cell is the cell for row at index path so whatever code you would have in the cell for row at index path you can edit here so whichever controller you have the cell for row index path if you see a folder screen yeah yeah there's a one view control there I think you should be there okay cell for row at index path okay copy this here but in our case we have these new models so now the model we get here this item is the model we set here so we are getting this cell via model for this index path in the tape of you and we need to return a cell in disclosure so this is a cell for row at index path from the delegate but in a closure form okay so the item has two cases right we have the suggestion yeah that's the solution and we have the field and they both have view models inside okay so the suggestion we just set this ejection is the text yes so it's just a simple cell right no fancy stuff and the view model now we need to create the requisite tell so we need to configure it register it so let's register the cell so it needs to match the same setup you would have in your data source implementation now can we use the cell yeah I think because it expects this Rex itself your model which is your old protocol but now we wanted to use our field view model that we just created so we don't need to hold a reference to it we need to get the view mode though text do we need to subscribe to it or can we just bind it the boot text field so when there's a change it notifies the view model right sure yeah but we can use the rx way of binding marks for rx this pose do we have a suppose back already can I use this one yeah again okay so the text is here we're not handling ever right now let's ignore it what is this keyboard thing it's basically the logic of the focus he did okay so we don't need it yeah see the the cell observes its enabled or not because the decision about showing the keyboard was made from the view model yeah which is the focus right okay so when the this button is pressed is handled tab it becomes a first responder so we probably needs you bind here then bind the tap of the button to a VM focus so every time there's a tap it's gonna forward the message to the view mode of focus alright and of course it's posed by the bag okay so here we're binding the text with the text field but we need to do the other way around as well when the text field updates we need to pass it back to the view model right let's keep the first one and bind the text changes back to the view model text and suppose does it bind statement hold a strong reference to a self twist self yeah well when we put the input text field we don't have to put self right so it itself is not retained right yeah no because it's binding to the return of this thing right which is a control property has no there's no reference to self and that's the benefit of not subscribing to it if you subscribe to an event then you need a reference to self inside of the subscription to forward the message but when you bind to the memory is managed by it how long you keep the dispose bag right and I think you renewed is this post back every time you prepare for views yeah so that's it every time you prepare for use all those bindings go away because there's an easier model right thinking okay I think that's it here that should do it let's go back to the app delegate let's see if it compiles now yes so this is the setup this is the code you had in this cell for row you just adapt it to these new models but this is the self a row you see you get a table view the index path in the current model for that index path and you do the logic you need to do here and you can also set for example data source the title for header in section and here you pass a closure as well the makes a decision on the title you know so you have all the methods you would have in an implementation of the tableview datasource protocol but enclosures we passed closures rather than implement in the protocol if you so okay now we need to get in observe the view model states changes so V o morado dot state and we need to map that state of the view model to something that can be rendered on the screen so we get the state here and it can be fields with fields and we also have the case focus with one field and a bunch of well and maybe some suggestions it could be empty and we need this to be an observable because we are mapping and observable it returns another observable where it's an observable of sections to bind it to the UI because a table view doesn't know what this view model state is so we need to translate it to something that you I understand the UI under sense this section model here so we need to return an array of those sections for the UI so when we have fields we're going to return only one section with all the fields or we can create many sections one for each field but I find it easier to just do it here let's say here we have the fields section and it's an array of all the fields yeah mapped into cell view models so we are mapping this state into cell via models so we get fields we need to put them inside the cell via models that the UI understands now for the focus we are going to have still the fields but only one field and another section for the suggestions and this is the suggestion dot map cell your model dot suggestion of course this view model here is an optional isn't course so let's unwrap it otherwise if you don't have your model don't render anything else you need an answer okay can we compile this suggestions yes now this this should be dot field field right it is very outside yeah fantastic so here we are transforming the view model state includes sections that can be rendered on the screen and here we are setting the closure that will render the cells for a specific section make sense so this is the cell for row and this is just a mapping from the view model state to a UI state you know we need to bind sections dot end to table view rx items passing the data source and of course we need to dispose that with its paws bag so we need its paws bag okay now we need to set direct association let vo model equals payment form of your model he has a bunch of fields so here the view model for the Ibon the title is Ibon text number bank name comment and we need a service well let's create a random CGS cheon's service so here we need to perform a request we can return a just of 10 values is enough right yeah that's for demonstration so we had a suggestion with Ibon let's say int the random something like this you have some random numbers okay that should do it now we need a view controller is the payment form vo controller with a vo model and let's see yeah whoops right so when you're using rx data sources you're not supposed to have a data source by the time you register it and since we're using a UI tableview controller it already sets itself as the tip of your data source so we need to do table view the data source no and you just set the datasource and delegate in you so it works nicely with the alright data source boom look at that nice fantastic let's select here the Iban boom it changed now it performs the queries with random values and if we select nothing happens because we need to bind now the UI with the selection table view our X dot modo select and the type of model is selves your model and we can subscribe and here we get the model ok and what do we want to happen here if it is a suggestion it has a view model we want well first to release the keyboard so this is a UI decision I don't want the view model knowing about keyboards it's too much to keep track in the view model so here inject some side-effects and we also want to select that specific view model yeah for a solution there it is so if we search here you select it goes back to the full screen but if we focus on the bank name it does not focus the comment does not focus the text number focus and you can also search select and go back to the previous state the only thing we haven't implemented yet is validation and when you select something you want to update live your models right yeah that's it you can implement these two features as a as an exercise and you can test drive these two features but when you select something you want to update these two views following the same principles you want to add these events into the chain and withdraw the UI if necessary and then you get all the animations for free if you have this centralized place and you have these models you get all the animations for free it's amazing and it's pretty amazing how you can do this with Iraq Swift in what less than 200 lines of code we have this very complex form talking to services and you know kind of crazy states transitions in what less than 200 lines of code including the UI because we have the centralized place for controlling the state of the form and how many protocols have you created only one and it's super testable we only obstruct it away the service we can add more fields here very easily we can add new field types we can have a button with your model or a special field type or toggles it's fantastic and that's the idea we are injecting the view models here but if you don't want inject only doing tests you can have like predefined values here as well you know you may make a decision that you have default values for them and everything's a struck very simple there's no reference of self anywhere look at this the oldest transitions happened there is no self which simplifies a lot you don't need to care about memory leads here and you have 100% coverage for all the transitions anything that goes wrong here you're gonna have a failing test and if and every time there's a new feature you had a test first you make sure that the state transitions happened as expected that's it and how fast are those tests it's insanely fast it's pretty easy to write as well you know you just assert the state that you get right that's it you're spying on the state and another important thing is separating your core models like this suggestion suggestion request in the services from the view models you know from the view side of things because you can use this service in any other UI you can use this suggestion in any other application you know we can have this abstraction in a separate module that can be reused so separate your models depend on services services do not depend on your models or UI hey can I ask you one question yes I had a troll when I tried to test drive this project I when I tried to assert that some UI element responded to my view model so for example view model toggle that it's has become active and some text fields had to be had to become first responder right and I and I couldn't do it straightaway after this change because probably it happens out there in different thread or the observable doesn't have enough time to respond so how do you test if a UI element is active or has reacted to some UI change so I saw that the requisites Cell has a become responder here when you tap yeah so let's let's let's test that we have test for the requisites itself I think we do but we have tests for the first responder no no don't well this one-to-one that's as you see wrong test number two that I commented it to ask you the question further on okay let's have a look at this so this is not passing right yeah it's not let me run so you're saying that when you sent the event keyboard enabled you would like this to be true which means it is the first responder right yeah so for this to work is a bit tricky with UI kit the view should be no window hierarchy for those things to work so we can create here a window and we can say window dot add sub view as it is a view right yes since the cell it doesn't need to be the key window but it needs to be non widow hierarchy for those transitions to work let's see there you go yeah it's a bit tricky I don't know you need to know you work heat for a long time and have wasted hours and hours and hours yeah you know or know someone okay and save a lot of things that's it thank you it's like the this this last test is just a blessing it's so easy right I tell you what veal super complicated yeah yeah that's it you just put in a window and you should be fine we're doing good text well since iOS 10 at least I remember that before I West and it was complicated more complicated than that he needs to be he had to be the routing window it doesn't anymore it needs to be a window okay I should sit okay so we covert designing the models test-driven designing the view models test or even services binding the services binding all the states transitions together and binding the view model states changes with the UI in the form with animations and now you need to implement the validation on top of these logic and when you select a suggestion I want to see the models updated and the UI updated as well for example if I select a suggestion that has an Iban in a text number I want to see that reflected in the UI and in the model make sense yep so I'm going to send you this project well I'm just gonna push it in the separate branch in your repo and you can carry it from there and if you have any questions if you can Estes but for this session you have any other questions no I don't does it make sense because I know that it's very complicated to understand is it took me a long time to understand how to think in this reactive way because when you start using reactive frameworks like rx Swift you want to subscribe to everything when the idea is to combine those streams and think about state transitions over time we have an initial state and then you have a transition from that state depending on some event and depending on other events and other events you should target you have like these separate streams but combined them you know and when you make this shift things start making sense it's easier to task is easier to design is easier to maintain but it's not easy you're gonna have to practice a lot yeah I remember when I when I was like many years ago with kayo when we used to do this reactive stuff in the objective-c yeah it was objective-c back then it was another library but yeah in Objective C and I remember when I started learning these my my instinct will be to subscribe to a signal and then just you know accept the value so inside the subscription block I would just fire another signal and this is like this is a the problem there because you create side-effects right so what you want basically is just like one endless stream of stuff and just putting operators in between and just transmitting the the values that you want and listening to these things but yeah exactly take some time to some you know some easy to think like that especially if you're coming from procedural and then you have like the the I think the other natural instinct that that plays is mixing procedural and like this functional reactive way of doing things because you're not sure how can I do it purely in in a reactive fashion right you just don't know that so you're gonna mix it and yeah that's that's the path though at some point it's gonna click okay so no mixing I don't see another way like you have to go through this I if you mix you're gonna get the worst from both words that's my experience if I going reactive I would say just use operators on the string avoid subscribing to things in the view model should just combine different events you know if we go back here to the view model you'll see that there's no subscriptions here we just combined different events into a main event every event that came you take the current state of the view of the view model its combined into one is merged and they dictate what is the current state and based on the current state the UI is going to subscribe to the current state compared to a previous state calculated a change set and only update what is needed so it's very efficient as well but if you start adding self in the middle here and start adding a lot of effects I don't know he's gonna start getting a bunch of bugs that is very very hard to debug we start having threading issues as well you know but if you just combine your legs memory leaks just combined it streams with operators rather than subscribing and performing side effects inside the streams only at the outer outer layer you're gonna subscribe to it like in the UI the only subscription we created here was the selection because we want to perform this side effect in the user interface to dismiss the keyboard and that's it any questions remarks well maybe just one about XE unwrapped yeah XE you and Rob does it not break the CIA if it throws here ya know because when it throws XE test is gonna catch that and generate a test failure rather than propagate the the error make sense thing it doesn't propagate the arrows yeah it generates a failure for this test no nice so that's it you can throw in side tests and that will generate a test failure if you simply throw it will generate a test failure not not a CI failure another thing so yeah okay because if you see the implementation of exit test they rub these in a do catch and generate a fader what are then propagating the error and if you first unwrap it any questions you gotta crash yeah if it's new it's gonna crash okay yeah and another exercise you can doing this test here is like there's still a lot of duplication here that you can create helpers for yeah yeah okay is that it yeah no further questions really thank you very much guys for for this mentoring session I I've taken out a lot really absolutely thank you is this what we were looking for yes definitely like I haven't seen our excuse this I know artistically it's really elegant elegant solution yeah it's a completely different way of thinking because if you're used to all or procedural code when you're using a framework like rx whiffed you're gonna try to build your code base following the same principles in you before the same thing if you're used only with reactive programming when you get into a whole code base is gonna try to enforce Ted as well it goes both ways so if you're going reactive you need to think reactive and it's completely different so I need to practice and with time it's gonna start making sense every time I need to subscribe it's stopping think is that a different way of chaining those operations rather than subscribing here and firing some side-effect try to work with values you have values that change over time and some and some other clients we will observe those changes and react to them or combine them with other operations and perform other side effects I would also suggest if you're if you're looking how to accelerate your progress with the reactive stuff just look into other platforms they have so much more content that you can you can learn from again it's the same mindset right it's like it's just a standard you have like this rx standard and you get the implementations into with different platforms yeah in Iraq swift follows the rx standard so if you check the RX documentation which is a general standard you will be able to understand everything and use in rx with the naming conventions are all the same yeah like map flatmap concat merge single or observable everything relays it's a common vocabulary defining the RX standard and if you keep studying this standard you're gonna come better and better at using those operations in your application thank you guys this is probably your longest mentoring session yeah don't worry about this yeah I'm really grateful and it's been really pleasure talking to you guys it was awesome thank you thank you thank you very much thanks for your time and for allowing us to share these with others as well thank you thank you thank you so much sundar right - bye thanks have a great night bye [Music] you
Info
Channel: Essential Developer
Views: 12,990
Rating: undefined out of 5
Keywords: ios, swift, professionalism, ios development, ios engineering, ios app development, xcode, tdd, modular design, architecture, advanced ios development, iphone, ipad, advanced swift, clean code, unit testing, testing, xctest, framework, solid principles, uikit, ios testing, advanced ios, advanced testing, mvc, design patterns, how to, coaching, senior ios developer, iOS lead essentials, podcast, coredata, mvvm, mvp, viper, clean iOS architecture, iOS architecture, rxswift, reactive, rx
Id: pRdYi5MBg7w
Channel Id: undefined
Length: 98min 15sec (5895 seconds)
Published: Thu Apr 30 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.