How to build a Face Pile widget in Flutter | Widget Workshop

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
what is up flutter devs today we're going to build this thing that you see on the screen which apparently is called a face pile because it's a pile of faces a little story to go with today's widget workshop a friend of mine reached out and said that he had to build one of these for the app that he works on for the company where he works these days but of course that company is too cool to use flutter so he had to build this with android and he only had to build what you're seeing here on the screen just show some faces slightly overlapping and of course because the android apis are just terrible that was a real pain to accomplish even in this static form he started with traditional android view hierarchies then he said you know maybe this would be easier with jetpack compose so he goes over there an hour later he says i've looked through all the documentation and i don't think jetpack compose knows how to display an image from the network [Laughter] are you serious so if you want to display an image from the network with jetpack compose apparently you have to bring in a third-party package and after we went down that road for a little bit he said all right forget it i'm going back to the traditional view hierarchy he managed to figure it out with traditional views and then after he got this static version of a face pile working his product team said oh just kidding we don't need that welcome to the world of technology and app development but what we're going to build today isn't just this static display that you see before you [Music] in reality you're always going to want to be able to add and remove faces from something like this and you're going to want to have a nice animated interaction when you do it so we can see here that we can add new faces to this and we can remove faces from it we can go all the way down to nothing in fact and then we can bring them back and notice that up to a certain size we have the same spacing between them but then we hit a maximum width and from there we start packing them in closer and closer because we don't want to exceed that width so we're going to figure all that out today the first time around it probably took me a couple hours to create this hopefully today we're going to do this in less than an hour so let's jump into it [Music] here we are with an empty project or empty enough we have a blank area to work with a blank canvas and we're going to figure out how to create this animated dynamic face pile thing now we could probably start this from a number of different places i generally like to start from the simplest piece of the puzzle and then work my way up i like to build small to large compose things together and so at the smallest level we know that we're going to display faces and each of those faces probably is associated with a user and we're going to need obviously a photo url but because we're loading images from the network or really frankly regardless of where you load images from it can take time to load the image and therefore we want to display something while the image is loading for that purpose we're also going to use the first name of each user so we're going to display the name in text and then fade the image in when it loads and because we're going to use the same kind of face widget over and over again and we want those faces to kind of continue existing as we rebuild our widget tree we know that we're going to need a unique id per face widget and that's going to lead us to model this concept of what we'll call a user now you could call it a face i guess you can call whatever you want it's essentially a view model but let's start by modeling that and then build out so we'll create a class called user let me see i'll scroll down to get you a little more space here and i said we're going to want an id we're going to want a first name and we're going to want an avatar url all right now let's generate the constructor for this and i always prefer named parameters in my constructors by the way if you have any idea how i can tell android studio to automatically use a named constructor let me know in the comments now i don't know if we're going to need this next part but just in case let's generate the equals equals in hash code and that's going to be based on the id and this way if we have let's say a set of users we can prevent duplicates by correctly honoring the hash code and equality operator so this user we're going to have one of these data structures per face that you see on the screen this is totally non-visual this is logical this is the data structure we now need a widget that knows how to take one of these user objects and show the name and then load the image so let's go create that and we'll call that for lack of a better name we'll call that avatar circle it'll be a stateful widget avatar circle all right and i said that it's going to work with one of these users so we need to take in a user we also need to know how large to make it you could hard code this if you want i prefer typically to do to make it to make things like sizes and colors parameterizable and then sometimes offer a default value so we'll take in face size it's just one dimension because a circle is a square so the width and the height are equal we'll call them both face size uh i guess i really because this is let's call it size let's call it size this is an avatar circle not a face circle and uh and size applies to the whole widget so we'll just call it size but we also want a color for the name label and we want a color for the background and with those let's generate a constructor and this time it's a named constructor automatically because flutter is smart enough to do that for widgets but doesn't do that for dart objects in general actually the user is required the size let's default to 48 it can be any default you want i think 48 is a reasonable default we will require the colors we're not going to make any assumptions there now what do we actually want to display in this avatar circle well obviously we need it to be a circle which means at some level we need to crop with a circle we know that circle needs to be need to have a width and a height equal to the given size but we also we also want to drop shadow because when you're overlapping things it's helpful to have a little bit of depth there so you can see what is on top of what really clearly and we know that a container widget gives us width gives us height gives us a circle shape gives us a drop shadow and gives us clipping so we'll take this container here and its width will be widget dot size its height will be widget dot size we will say clip behavior is clip dot anti alias anything other than clip dot none will give you clipping and then we want a decoration box decoration shape is going to be box shape dot circle that's going to give us a circle clip behavior we want the background color to be widget dot background color and then that will well let's go ahead and see what that looks like and then we'll build from there let's come up here to the scaffold that i already have sitting here and let's give it a body where we will center an avatar circle and it's going to want a user for now we will hard code this in here so id is fake first name will be me um avatar url for now doesn't matter then we want a name label color which will go with white and then the background color let me see what i used the last time that i worked on this actually so last time i did this i didn't use white up here i used a light gray so that wouldn't i'm sorry this was no no i had the inverted i used a very dark gray last time the background was actually a lighter gray and it's going to complain about me not making things constant that that warning is really annoying i guess it's good you should make everything const that you can but while you're developing it's so annoying to have these stupid squigglies appear everywhere um because eventually this is not going to be constant so we're just a distraction anyway so we have the we have the fake user here we have the name label color background color and that's centered in a scaffold let's save that and does it realize let's see do you know that you're running lost connection to device awesome okay here's what we've got so far we have the circle of the appropriate size we're missing a drop shadow we're missing text we're missing an image let's go add those details so in here for the drop shadow we will use a box shadow box shadow color colors black with opacity i don't know 0.2 maybe blur let's go 5 offset let's go maybe three pixels on the y-axis and then for the child of the container we're going to center a text widget which is going to be widget.user.firstname and we're going to say maxlines1 now there isn't going to be much room here so a lot of names are going to overflow but you can adjust that behavior as you see fit we're going to take the easy approach of limiting to one line and then we're going to say text overflow ellipsis so that we'll say dot dot if it's too long then the text style say widget name label color we'll go with a font size of 8 make it very small and the font weight we're going to make it bold widget.user.namelabelcolor dot name label uh let's see what did i not call it oh no i said was it was sorry widget dot what am i what am i oh it can't be const i guess in that case sorry all right save that now we've got the name and we have the drop shadow okay now we still need the image what do we do for the image well first let's uh let's come over here to random user dot me this is a tool where you can get a whole bunch of random user data names photos probably addresses if you want them i don't know it's just a bunch of stuff you might need um [Music] and so let's see here we have we have ladies we have gentlemen apparently lego people as well uh let's there this looks pretty much like me right let's copy the image address and come back in here and we're going to hard code that image address into our hard-coded user and then what we're going to do is right now it's not going to do anything because we're not displaying an image but down here in our average avatar circle widget we're going to come inside the container remember the container is cropped so it's cutting a circle out so inside the container we're going to create a stack we're going to leave the text in the stack but then on top of the text we are going to display an image and specifically we're going to use a fade in image and we're going to say memory network now this placeholder thing we're going to say k transparent image i had to import a package to get uh transparent the transparent image let's see in fact what is it complaining about here yeah so this package right here i need to add this to my pub spec i don't know why flutter includes the fade in image widget and then still years later defers to this third party package i don't know the rationale there a lot of people want the ability to start invisible and then fade in an image i don't see why we have to import or we have to load an external third-party package just to get that but it is what it is now for the image we're going to say widget user avatar url and then fade in duration we're going to go with 250 milliseconds save that oh and let's see i'm missing static i don't maybe a hot restart will solve that let's see sure does and there we go we get the little fade in right there uh so now we've got a face circle of the appropriate size clipped to the shape with a drop shadow that's the basic building block of the face pile now in theory if we can figure out how to lay out multiple of those either with that's two situations when we have when we don't need all the available space and when we exceed the available space those are two different layout concerns and then we need the ability to add more faces with animation and remove faces i guess let's try and handle the layout first and then we'll come back and we'll make them appear and disappear so let's start by declaring a face pile widget and this will be a stateful widget call it face pile now a face pile of course displays a list of users so we're going to have a list of user called the users also we're going to again make the face size configurable though it will have a default value and then there's going to be the face percent overlap in other words if that value is 0.1 that means 10 overlap one face sits 10 percent over another let's take those into a constructor actually again face size we will default to 48 but the rest is required so now let's get rid of this hard-coded thing cut that out of there and now let me see i want to constrain the size here for our demo purposes so let me see what i did last time for this so keep the scaffold keep the center then we're going to have a constrained box with constraints of a max width of 200 pixels and then we're going to use our face pile widget right here and then within that widget we need to pass users and i see i made a face percent overlap required instead we're going to introduce a default value we're going to do 10 percent overlap by default okay let's bring back that hard-coded user right here i cut the whole widget but let me just bring back the user part what we're going to do in a few minutes is we're actually going to load we're going to load a group of users from this website in our code so that we have random users to deal with but for now let's try to get some of this set up with just a single hard-coded user or maybe two or three we don't need to parameterize this until we're ready to actually see that stuff in action so down here in face pile what do we want to do i guess the first question is fundamentally what layout makes sense if we're going to display these circles and overlap them a little bit you might be tempted to use a row but a row is not going to work because a row very much expects for each item to not overlap the other and you're if anything if you manage to make that work you're going to be hacking it all to death and really what's more appropriate here is a stack a stack allows you to use any xy position within the stack to display children as long as you use a positioned widget so let's see if we can start building this thing out first i want the height of this thing to be equal to the height of a single face that's one constraint out of the way and then i said we want to stack okay well here's a stack and we're going to get rid of the clip behavior because at a minimum the shadow from one of these faces will will sit outside of the stack itself and we don't want to give it a hard cut off there so we're going to say clip dot none for the clip behavior and then the stack has children and this is where we want to place all of our faces i guess to start off let's just take the one face that we've got and let's put it at the top left to make sure that we're showing something we'll use a positioned widget we will say left zero top zero height widget dot face size and i think we also have a width too so widget dot face size and what is this complaining about okay it doesn't have child that's fine the child is going to be an avatar circle which wants a user we'll say widget users first because right now we know there's exactly one that we're passing in and then i forget let's see if i can remember what these two colors were i think we said that the name label color was 2 2 2 2 2 and then the background color was eights let's save that okay and there it is notice it's off center why is it off center the face pile is 200 pixels wide and so we should be one like the left side should be 100 pixels to the left of center that's why it's off center so this is a good indication that actually this is where we expect it to be now let's bring in multiple faces and see if we can start to figure out how to calculate these positions obviously if i save that we still just have this one face because we're explicitly accessing the first item in the list of users but now we need to do something for all of the users how do we want to lay this out i guess what we want to do is we want to start from the left and then from there we just kind of want to put one after the other after the other and because a stack automatically supports a z index the the later faces will automatically overlap the earlier faces so the left parameter is really what this comes down to and we want many positioned things right so we want to say for variable i is equal to zero where i is less than widget dot users dot length i plus equals 1. i'm using i as a variable instead of iterating directly because we may want we're going to need to use the i value in the calculation we're gonna need to use the index in the calculation so here we can say for example i times widget dot face percent overlap and actually that's not quite right it's 1 minus that because if we're overlapping by 10 percent it means that we want to display 90 percent of a face right to overlap 10 percent it means you're showing 90 and that's the number that we want right here we want i the index times the percentage of each face that we're displaying but we also need to multiply that by the size of a face so this will begin at zero and then it will position one face further and further left and they will share 90 percent they will display 90 of each face and they will overlap 10 percent which is this overlap value let's save that and see what happens all right there it is now one interesting thing is that it doesn't seem like the drop shadow is really showing through maybe it's just too light but i don't like how they're kind of blending into each other let's see what happens if we give the drop shadow a little bit darker of an opacity yeah it's still i guess i guess you can kind of see it it's just it's so much darker against the white or the white background that it is on the other image we'll see how it goes i don't think i've screwed anything up but we'll see if something comes up later now one problem we do have here it's not so visible with four users let's go back to three users and let's let's add around this for just for now at least a decorated box decorated box with a decoration box decoration i want a border order border dot all color is gray notice that we're that we're we're filling from the left to the right and that's really not how this should work it should be centered if one if we don't need all the available space these faces should be centered and here begins our foray into a bit of a more complicated approach to this situation we need to know how much space is available without knowing our overall bounds we can't make necessary decisions for how to lay out these faces this is the situation where one needs to use something like a layout builder so above this sized box i'm going i'll say stream builder to generate it but then we're going to change it to a layout builder layout builder get rid of that and this is constraints so now we have we have access to for example constraints dot max width and we have to make a decision at this point uh either we need all the available space or we don't one so we need to calculate that we need to actually figure that out one thing is that this number right here i think we're going to need up above so i'm going to cut this out and i'm going to call this face percent visible which is the inverse of face percent overlap and then i i want to calculate the let's see i want the intrinsic width so imagine that we are unbounded imagine there are no bounds how wide would we choose to be let's calculate that we'll call that intrinsic width now if face count is 1 then our intrinsic width well if it's one then our intrinsic width is the size of one face if it's zero our intrinsic width is zero but it really doesn't matter in that case anyway um so what we're going to say is uh widget dot users is greater than one because we're gonna do some math here that requires a number a length greater than one we're going to say one plus face percent visible times widget dot use i'm tired of typing this out here let's say uh faces count that's going to equal widget users.length so faces count faces count we're going to subtract 1. so here's the math that we are calculating if you think imagine a number of faces laid out if there was zero overlap the width would be the number of faces times the width per face but the moment that you start overlapping the calculation becomes one full size face and then n minus one ninety percent of faces you're always going to have one fully visible face but then all the other ones are at least partially overlapped that's what we're calculating here that's what this this one plus right here this represents the fully visible face and then here n minus one faces times ninety percent visible and then we're going to multiply by face size now if there's only one face or less we'll just go ahead and make it the size of one face so again if there was no overlap it would just be this face size times the number of faces but because we're overlapping we have one full size face and then we have n minus one partially visible faces and that gives us the natural width if we're not bounded at all that's our natural width now given given the incoming constraints and given the intrinsic width we now need to decide what is our left offset and when i mean the left offset i mean like we're going to say left offset plus this other stuff before we were our left offset was zero which is why we were always pushed to the left side but now we're going to calculate a left offset based on the incoming constraints and our intrinsic width so we're going to say if intrinsic width is greater than constraints.max width this means we want to be wider than we are we don't have enough room for our natural width so in this case our left offset is going to be zero we're going to take we're going to go all the way from the left to the right all the way from the left to the right but if that's not the case then our left offset we need to calculate and that's going to be constraints dot max width minus intrinsic width divided by 2. this is the this is essentially an average so we are this will horizontally center our content once our content is laid out at its intrinsic width we will be horizontally centered okay let's save that and look at that we are horizontally centered but we still have a problem let's add a few more a few more users look at this now so now we're too big we're too big to fit and so we do start at the left but look we we blow right past the right side of our available width we just keep doing the same overlap and we go right be it right past our bounds that's no good and this is so this is the second part of the layout concern we dealt with the part where we're where we take up less space than available now what do we do when we are taking up more space than available well what this means is you see this this face percent visible parameter right here or variable it's not actually constant we don't actually know what it is we know what we want it to be and that's what we set it to up here but if we don't have enough room we need to shrink that number down we need to do it right here we need to shrink it right here so we'll say face percent visible equals constraints.max width divided by widget dot face size minus one divided by faces count minus one if we take up too much space then that equation right there will determine how much overlap we require so that we fit in the available space now this minus one here remember i told you there's one face that's always fully visible that's why we have these minus ones and now look at that now we're taking it now now we are exactly constrained to the amount of space that we have and i think in terms of what we do next let's take inventory about what remains we don't yet have the ability to add and remove faces at all and that also prevents us from seeing from being absolutely sure that this layout works when we grow and shrink in number once we have the ability to add and remove users we still don't have entrance animations and we don't have exit animations so in terms of solving those problems i think first let's add buttons to add and remove users and we'll just stick with hard-coded users for now we'll build this whole thing out with hard-coded users i guess the same user over and over again and then once everything's working we'll actually use random user through a package and we will fill these in with random truly random users so first let me i'm gonna in the in the floating action button area here i'm going to add a couple buttons so here we have this scaffold and everything is in the body of the scaffold right now i'm gonna come down here we're gonna we're gonna put something in the floating action button property and let me look up in my notes what i did here previously okay so we're just going to do a row and we're going to make sure that we don't expand we just want to be the minimum size possible and then we're going to have two floating action buttons one to add one to subtract so floating action button child const icon on icons dot remove on pressed we will say remove user from pile and we're going to make it many because these things are way too large by default on desktop okay and then we're going to copy that and then we're going to change this method to say add user to pile and we're going to say icons.add and then between these two we're going to have a sized box add a little bit of space between them a size box with width of 24. and then up here above our build method we're going to create a method called add user to pile remove user from pile now this requires that we have a pile to add and remove them right so we will have a final face pile users list right there and then we will say let me go grab that hard-coded user definition [Music] we will say set state face pile users add there we go we'll make that a const and then we will say if face pile users is not empty then face pile users this will say remove last although um let's see i want that to be random i don't want to just always remove the last one so let's say random index random next int face pile users.length import that oh sorry not file pile face pile okay and then we're going to say face pile users remove at random index okay [Music] and then down here instead of having all of these users in here we're going to get rid of this hard-coded list and just show [Music] the face pile users looks like i deleted too much we need face pile here there we go save that and we'll probably need a hot restart because we added something to our state object okay we have these two buttons let's add okay that's proving that's proving that our layout is working as we grow and now it's working as we shrink all right so we're making progress next let's animate not the appearance or disappearance but as we add things in here as the existing avatars or faces change their position let's animate that change and thanks to implicitly animated widgets when we come down here to positioned we're going to get this capability practically for free we're going to go instead of positioned we're going to use animated positioned and then we need to provide a duration for how long each one of these animations should last and we're going to go with 250 milliseconds and we're also going to specify an ease in out curve so that the motion doesn't look too choppy let's save that how about that just like that we're getting animation for free that's weird they uh that random ah i remember now so i was gonna say the random index is not working because we're always removing the last one remember i mentioned at the very beginning of this that we need a key or we need an id per user because we need the widgets to remember which is which over time so we will have a value key whose value is widget users i dot id and now for that we do we still have an issue because i'm giving them all the same id because they're all hard coded instead we will create a string that says random [Music] next int i don't know 10 000. that's not the final solution to that problem but it should solve our problem for right now and it's duplicate keys because they're null for the existing ones let's do a hot restart okay so now we can add them like before and now now you see they're disappearing from random places and the reason that we're making it random is because in a real group you don't know when people are coming and going so we make it random to make sure it's going to work for our use case okay now we need the ability to animate appearance and disappearance and there are a number of ways that you could try to accomplish this and there's also a number of different animations that you could do like you could have we're going to have it kind of pop in and we're going to have it shrink out but instead of shrinking out you could decide to make it like fly up and then disappear and fade out but by having the animation by animating in and out without changing the position that's going to allow us to centralize that animation in a new widget specifically designed to do that animation to make the appearance and disappearance work so we're going to make our lives a little bit easier by not flying up and fading out so we'll create a stateful widget called appearing and disappearing face and what are we going to need here we're going to say we're going to bring in the user we're going to take in a face size we're going to have the ability to show and hide the face and then we're going to take a call back so that we know when we're done disappearing because when we don't we don't want to remove a face until it's done animating out and so we need to know when it's done animating out all right user is required face size will default to 48 show face is required on disappear is required now i said we're going to kind of pop in and pop out both of those are scaling transforms therefore we're going to need an animation controller we'll call it scale controller and we're also going to create an animation so that we can apply elastic or elasticity curves to that animation to the controller i should say and that's so we're going to create this animation now to coordinate the animation we're going to need a single ticker provider state mix-in thing and then we're going to need in init state to actually instantiate these things scale controller equals animation controller the vsync is this and the duration we're going to say const duration milliseconds 500. we're also going to add a status listener because i told you that we need to invoke that callback when we're done disappearing so we're going to animate forward then when that's when the face appears and then when the face disappears we're going to animate backward so we're going to say if status equals animation status dot dismissed which actually means it is done reversing then we're going to say widget on disappear so the widget can do whatever it needs to do now that's the controller we also need the animation with the curves so we're going to have a curved animation the parent is going to be the scale controller and the curve is going to be curves dot elastic out and if we init those things then we must dispose of those things so here we will say scale controller dot dispose now we also have another situation we need to deal with which is that event at some point this widget is going to be told to well show or hide a face technically this widget could start by not showing a face then show it and then hide it the point is we need to be able to take action whenever that request changes whenever we in other words whenever show face goes from false to true or true to false we need to do the appropriate thing we're going to create a method to handle that situation and then we're going to invoke it from multiple places we're going to call it sync scale animation with widget and there are two times that we need to do this first when we init state we need to make sure that if we want to show the face we show the face but also we need to override did update widget and we need to call it from there as well meaning whenever the widget changes we need to look at that value and take the appropriate action now what do we want to do in this sync method if we want to show the face and we are not already showing the face and ours and we are not already animating forward then we actually have something to do and this should be scale controller dot status so if we want to show the face if we are not yet showing the face and if we are not in the process of showing the face then we want to say scale controller animate forward which means show the face otherwise if we don't want to show the face and the face is not currently hidden and we are not currently in the process of hiding the face then we want to hide the face okay now what do we do with that information we have an animation we make it go forward and backward at the appropriate time how do we use that visually well first we're always going to take up the amount of space needed for a face regardless of what size we are we're going we're going to take up the standard size and then we're going to center the smaller face inside of ourselves so within that sized box we're going to have a center widget going to have an animated builder which is an animated builder is a widget that rebuilds itself whenever an animation changes so we're going to pass in the scale animation and then there's the builder part which takes in a context and a child the child doesn't matter in this case actually i guess well we could use the child here let me show you let me show you both approaches so ignoring the child argument we could say transform dot scale this is what actually applies the visual scaling how much do we want to scale we want to scale with whatever the scale animation says it should be zero percent fifty percent a hundred percent it's up to that animation and then what are we scaling we are scaling an avatar circle with this user and this standard face size and then here is here are the colors again and by the way you could generalize this for any widget if you were to take in the child if we were to actually take avatar circle into our widget as a child we could generalize all this for any widget i just didn't happen to do that because scalability has nothing to do with this exercise now i mentioned before about this child argument for those of you that don't know the reason for this child argument is that sometimes you have a sub tree like this where part of the tree actually does need to change with the animation but part of the sub tree doesn't need to change like this avatar circle this avatar circle it does care about the widget user and face size but it doesn't care about the scale animation so we can do this we can cut that out and we can pass it as a child to the animated builder itself and what's the benefit of this even though our builder runs 60 times per second and even though the transform scale widget is rebuilt 60 times per second this avatar circle right here is only built one time given a given this user and this face size we only create we'll instantiate this widget one time and this same widget keeps getting passed into our builder so this is a performance concern it's not functional you don't ever need to use this child property but if you have things that don't care about the current state of the animation you can build them once in this child property and then it gets passed into your builder to be used inside of your visual animation either way is fine in this case not a big deal okay so this is this should give us our visual the visual behavior that we want but we still have a problem here and let's see how this looks it we may not see any any animation at all let's see so if we remove stuff there's no animation and when we add actually i'm sorry i'm not even using this widget yet am i we're still going to have a problem but let me at least use this widget so here's the widget that we just created and uh let's see okay i declared this higher up than i wanted to i wanted to clear this below the face pile so that it's easier to read from high level concept to lower level concept now coming into the face pile here's our avatar circle instead of an avatar circle let's do appearing and disappearing face and then show face for now it's always going to be true because we don't know when that should be false yet we need on disappear we'll say to do and then face size will be widget dot face size let's save that let's do a hot restart okay so we do have the appearance animation but watch what happens when we remove them no disappearance animation why is that the reason is that we are literally removing the user data the second we press this minus button we don't that the moment we press the minus button we forget that that user ever existed because up here in this users list we remove it and then it's gone as far as this widget is concerned it's not there because once we remove that user from this list well we come down here we we re run our build method and look we're only building the users in that list and what this means is that we actually need to retain a second list of users there's a difference between the users that are currently in the group and the users that we are currently displaying that's the problem we have to solve so to solve that in this face pile state object we're going to declare visible users and this list is not necessarily the same as this list okay the visible users is going to be based on the users that were given but when we remove a user we're going to hold on to it in here for a little while longer now because we're introducing this list we have to add some behavior to synchronize our lists so in init state we're going to say visible users add all widget dot users that'll get us started and then in i think in did update widget we're going to need to do something here as well i guess here again in both cases we're going to have to synchronize now the init state synchronization is very simple because we know that we're starting empty and so we can just add all of these users but the but when we get down here to a did update widget we may have to add and remove certain user well that's not true either we but we don't want to add any users that we've already added that's the point so let me instead of doing two different implementations let me say void sync users with pile and let's call that from both of these places sync users with pile and then the question is what do we want to do here we want to figure out the new users the users that don't already exist in our list so we're going to say widget.users when i say don't exist in our list these are the users that are not yet visible they were just added to the list we need to figure out which users those are we want to select from the users that we are given and which users do we want well looking at every user that we're given we're then going to look through visible users and we're going to find any situation uh let's see this is probably not at all an efficient way to do this we're essentially running a diff but we'll say visible user equals to user is empty this should be an arrow okay so we're looping through every user that we're given and then we're saying okay for every single user that we are given look at every user that is currently visible and if there is no visible user that is the same as the user that we are given then this must be a new user now you may have a much more concise and efficient way to implement this this was just off the cuff the way that i would do it because i i use these wear methods a lot obviously it's not efficient to loop through the second list for every item in the first list but i'm just not concerned about that efficiency right now the point is this will give us any users that are new that we haven't seen before that are not visible and then with each of those we want to say visible users add all [Music] new users okay and now we're synchronized in the case of init state work new users is going to be all the users but when we run did update widget the difference is probably going to be one or two users at a time now notice i'm not saying set state here and that's because both of these things are run in methods that will then rebuild you can't call set state in a nit state or did update widget and that might be a problem in a minute but let's see i don't know yet if that's going to be a problem we we know okay so now visible users is our state of truth so we want to take visible users we don't want widget.users we want visible users and let's see any other place that we're using widget.users we don't want that there we don't want that there oh i was passing in the wrong user the entire time right here or at least in the latest change this should be visible user's eye okay and then let's let me make sure that this reference is not being used in places where it shouldn't be that's correct okay so now now this widget the source of truth is this list right here from a visual perspective we don't care what you give us in the widget we only care what's in the visible users list now i think we're still going to have let me do a hot restart because we added a state variable i think we're still going to have the same problem because we haven't actually i think now they're never going to disappear okay so i just clicked minus a bunch of times and they never disappeared and why didn't they disappear one thing is that we are never passing in false here show face is not always true we want to say it's only true if widget dot users contains visible users i right so only if we only want to show the face if this user is one of the users that was given to our widget now this will make it disappear but i think we're still going to have layout problems again let me do a hot restart to get us back to a good known state and i'll say add okay now remove they removed but notice that our layout is wrong why is our layout wrong our layout is wrong because even though the animation is running we never implemented on disappear so we're at we're still showing the widgets they're just invisible because they disappeared we need on and on disappear we need to remove those widgets so i don't i think this will work if we say set state visible users there could be a race condition here i'm not sure but i think we can say remove and we'll say the problem is so we can say visible user's eye the problem is when exactly does this line resolve itself because imagine that we remove two users or three users really quickly is this number going to be wrong is it possible that we either we remove the wrong user or we access an index out of bounds this is a language question i don't know when exactly this expression evaluates but let's try it let's save that not terminal i want run let's do a hot restart okay seems to work so that's that's the functionality we built a face so we started with a circle gave it a background color gave it a drop shadow put a name text on the center of the circle then added a fade in image from the network which we cropped to a circle then we figured out how to lay them out horizontally in a stack first we dealt with the case where we don't need the available space then we dealt with the case where we naturally want to exceed the available space then we animated changes between the two then we animated appearance then we animated disappearance and that gives us the final product but for those of you interested in something that actually looks convincing we want random users right so first let me go remove this border because we don't need that anymore and i'm going to show you how we're going to get random users so we'll get rid of this decorated box now i've included in my pub spec here a package that implements api support for random user unfortunately this package is out of date it doesn't have null safety so by the way i've been building this entire time without null safety so if one of you if you know the person who's working on it maybe get them to go updated or maybe you create a an alternative version of this package but let's see what we can do to uh to bring in random users now the approach that i'm going to take to this is that at the very beginning of the of the app essentially i'm going to load 20 random users and that's it i'm not going to load any more than that we're just going to keep selecting from the group of available users so up here in the demo state object we will declare a list of user called available users and then we're going to implement a net state super dot init state and in a moment we're going to implement a method called populate fake users now the point of populate fake users is to actually communicate with this web service and get us the data that we want populate fake users we're going to say random users client equals random user this is something that came from that package we're now using the random user package and then we're going to say random users equals await random user client dot get users and we're going to ask for 20 results then we're going to convert those random users from the web service into our definition of users so we're going to say random users dot map and then we're going to construct our own user object now these users technically have something called an id unfortunately a lot of times the id is null and that's no good for us so we're going to cut we're going to use something else for the id which is the picture we're pretty sure they all have thumbnails and the thumbnails should be different for every user so that should work as an id and then randomuser.name.first for the first name and then for the avatar url we're going to use that same value as we did for the picture and then we're going to convert this to a list okay so now we have 20 users sitting in this list now this list is different from our face pile users this is the pool of all the users we can select from and then we have the ones that we're actually showing which means we now need to adjust our behavior for adding and removing these things if we have no more available users if available users is empty we're not going to add any more you can never add more than 20. that's the restriction that we're putting on here but now instead of this thing right here we're just going to take the last item off the users list we're going to say add available users dot remove last okay and we set state already so that's good and now remove user from pile this is this is similar except we need to grab the removed user and we need to add it back to the available users and we'll add it we'll actually add that to the front so that we don't immediately get the same user we just removed i'll try and should we insert because remember we had to load these from the network so we only have a certain pool if we remove them and throw them away then quickly we're going to run out of available users so again what we're doing is we're filling in available users and then we're moving users from this list to this list and then from this list back to this list okay let's save that let's do a hot restart and there we go we can add a whole bunch in there and then we can remove them and that folks is an animated face pile in flutter i'm not sure exactly how long we took i wasn't paying attention but definitely less than the many hours that my friend spent trying to get it implemented on android only to have his company tell him that now we're not really going to use it so this is why i love flutter you can do a lot more a lot more quickly you do a lot more with a lot less hopefully you learned something here today i this i've decided to try to make my widget workshop source code public from here on out so if you go over to my github if you go to my widget workshops plural repository you should be able to find this in there and i'm going to add more widget workshops to there moving forward so add any questions or comments that you may have down in the comments section uh in the next video i'll probably go back to my processing work for a while but i wanted to drop in and create this since i did take the time to make it to a throw to my friend's face now you guys get it as well so i'll see you in the next one [Music] you
Info
Channel: SuperDeclarative!
Views: 992
Rating: undefined out of 5
Keywords: flutter, widgets, mobile-apps, web-apps
Id: slHjGPV-Jfo
Channel Id: undefined
Length: 68min 56sec (4136 seconds)
Published: Thu Sep 16 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.