I Bet You DON'T Know These 3 Performance Optimizations for your Jetpack Compose UI

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey guys and welcome back to a new video about jetpack compose and this will be quite an advanced video in this video I will show you three major performance optimization techniques you can apply in your composed projects if you actually encounter performance issues the last part here is really important don't take what I show you here as a general best practice place these things that I show you here might make your code readability a little bit worse and yeah just have some side effects so please only apply these if you actually experience performance issues in your composed UI that's what this video is intended for if you have a compose UI it does not lag at all and your users are not having any issues with this with it then don't do this premature optimization only really optimize parts of your app if your users are having struggles with it you will find all the code for this video down below I already prepared it just to show you something here this is not about building this stuff and that that's something you should already know if you're watching this video but yeah let's just dive into it and I will show you these three things that you can actually make sure to consider in case you want to improve your compose you are and you will learn a lot about compose um because this is quite advanced stuff that goes a little bit into compose internals so considering what I have here for optimization number one I built this little RGB selector and all this does is if you take a look is it has a little box with a color and we have three buttons red green and blue and if we click these then the Box will change its color accordingly so as you can see nothing very special all that is happening inside of this RGB selector we just pass our color for the box and we have a Lambda when a color was clicked then yeah we just retrieved the color that was clicked on so for our three buttons if we click on red and then we will pass red if we click on green we will pass green and so on so that should be clear and pretty simple so what do I want to show you here I want to show you if we open our layout inspector in the bottom right and I will reset all these counts here and make this a little bit larger this is used to actually debug your compose UI and see how often certain parts of your UI recompose if I now open up my device and if we now actually click on any of these buttons what do we expect to happen if we click on Blue for example then we expect that this box actually gets recomposed because it's it obviously changes its color and the button will of course also recompose because we have a ripple effect but these two other buttons should actually remain unchanged because um yeah we really only want to change the color of this single box and if we click on blue then you will notice something interesting if we take a look here on the left in our row all of our three buttons actually recompose you can see these have one recomposed count here even though they should actually get skipped so to understand more why this actually happens and how compose works when it determines which composable should actually be recomposed and which shouldn't we need to dive into a concept called stability so in in terms of compose we have stable classes stable objects and we have unstable ones so what is the difference compose tries to determine the stability by considering all the public fields of a certain class and in general all primitive fields are considered stable by compose and if a certain class is stable and composed determine that and that class did not change its value so the equals function of that class returned the same value as in the previous recomposition then compose will skip that are composable so it will not read write let's take a look at our RGB selector if we actually take a look at this color colors are marked as stable I assume if we take a look here then yes here we have color is marked as immutable which is even stronger than stable but with that annotation compose make sure that if this color for example changes but it does not change its value then compose will also skip this box which uses this color so it will actually not read that redraw that box because it's smart enough to recognize hey that color actually didn't really change and it will do that for all composables that somehow use State off or yeah that somehow use values and state that we passed here in our composable so if we take a look at our buttons then these buttons don't really make use of our color State since yeah they don't need that all they already have is our Lambda or on click Lambda and the text which just has a hard-coded value so that really never needs to recompose but what now happens and the reason why all of our buttons recompose is it will take a look at Main activity the issue lies in this Lambda function why is that an issue well um what actually happens with Lambda functions in kotlin when these get compiled they come the kotlin compiler will convert Lambda functions just into simple anonymous classes so these things that we know from object colon something something some kind of class and that is what these will be compiled to and everything that we use inside of the Lambda function that comes from the outside so in this case our view model will get passed as a parameter to that Anonymous class and the problem here is that our view model is not considered stable by default by the compose compiler because it's a more complex object and it can directly infer that and the viewmodel might also not be stable so that's completely correct from composed that it does not automatically marked as that is stable about since that's the case the Lambda function that is created here will kind of be considered as yeah as a new instance on every single recomposition so what compose will think is oh that Lambda function actually changed on this recomposition so let's take a look in this RGB selector composable and see which composables make use of this Lambda function because the Lambda function changed and if we then scroll down we see all of our buttons actually make use of this same Lambda function so compose will recompose all of them that's exactly what happens if we again take a look here we open our device we click green then you can see also the red one recomposes if we click red then the blue one will also recompose and all the others as well so um obviously normally this is not an issue and also in this app it's not an issue that is what I really mean um this is not a performance issue in this app but if you have an app where such an issue leads to tons of recompositions for for whatever reason maybe you're scrolling and due to that scrolling your whole UI recomposes then you need to start thinking about how you can solve this and one thing how you could solve this is looking at such Lambda references so that could be one source of this issue and the the way we could solve this there are actually multiple ways is by going into main activity the easiest way is to just remove this Lambda and directly replace it with this way of writing it so we just directly refer to the view model change color function here with this double colon and then if we now relaunch this app take a look at our layout inspector increase the size a little bit zoom in if we now take a look here at our three buttons and we click on green now you can see that our other buttons are actually skipped so these grayed out ones here actually mean that these buttons were skipped because now this um yeah the button we clicked on or the Lambda function rather that we passed here to our RGB selector is not considered as a new instance anymore by compose so it won't recompose any of these buttons or rather just the one that we clicked on because there is this ripple effect but what actually if you have a more complex Lambda and you can't easily use this way of writing it so this will only work if the the function of the view model in this case change color accepts exactly the parameter that the Lambda function provides so a Lambda function here just um yeah has one color parameter and it returns unit and that has to be the same signature of our view model function that doesn't always work so what could we do in uh instead if we can solve it like this then another way would be to use remember and explicitly remember our Lambda function and now it starts to get unreadable and that's what I mean only do this if you really have performance issues but let's see how this would work we could actually have a Val change color Lambda and we say we remember that so in here we would have another block of code which would be the Lambda instance and we also want to declare that this is actually a Lambda that takes in a color and returns a unit and in here you can see we now get our color and we could then say viewmodel dot change color and we pass it then we can take this change color Lambda and pass it down here in our RGB selector we launch our app and we should then see a similar result as before or rather the same result we'll take a look zoom in open our tree right here and if we then click green you can see then also our two other buttons get skipped just like before when we directly pass the reference of the function from our review model just if you have these Lambda functions that you directly create here then they will yeah just get created on every single recomposition and this is really only an issue if your Lambda function here uses external references so it uses fields that are outside of this Lambda function that are not marked as stable by compose so if we if we would have a color State here directly without a view model um so value our color by remember mute will stayed off let's say color.red initially and if we then say we simply update our color here to it since this is marked as stable this is an external field but it is marked as stable then the result we see should be that even though we now create a new Lambda here we should not see any issues with recompositions so we take a look here there were buttons we click on green and then you can see only the button we clicked on actually gets recomposed and not the other ones so now it doesn't even get skipped because we don't really yeah it doesn't need to update these so let's now get to issue number two or performance potential performance optimization number two um let me clear all these other tabs here and I want to go into my optimization 2 package what now happens if your compose code or your compose module uses some kind of external modules you can see I have an external module here that could be your own module it could be a library it's just important that this module does not use compose so it's just a pure kotlin module or an Android module that doesn't use compose if we open this all that really that's really in here is an external user data class so that represents some kind of user and you must imagine that this comes from a library or from your other module if you now go ahead and have your welcome view for example which just welcomes a certain user and you pass in this external user reference here like this and then this can be an issue and compose because these classes that come outside of the compose World from a different module are marked as unstable by default fault so it's again the stable versus unstable Concept in compose and since compose can't really guarantee that these classes will never change and that compose can guarantee that two equal instances of this class will always return true for the equals function it will simply Mark that as that as unstable and the issue of this is simply that it could also lead to more recompositions as you like because compose might always consider this as a new instance because it's marked as unstable instead of yeah if it actually didn't change then compose normally would also not update its UI but here it might because it comes from an external module so how can we fix this this fix is rather simple and you will probably already know this pattern and that is that we can use such a mapper so we will have a mapper in our UI layer and we just map that external user class to our own custom user class which contains the same fields and we then have a function external user 2 user to convert it to our own user which is now inside of our compose module and therefore marked as stable since it only exposes Public public stable Fields all these fields are strings so they are stable by default from compose and we also have this function to convert our user back to an external user that of course makes sense if you very often deal with these users and you need a lot of these fields here another alternative to this approach would be that if we take a look in our welcome view all we really need is this username here of this external user reference so what we could instead do is we could also implement this alternative which simply takes in the username so now it's suddenly not an external reference anymore because it's yeah it's just a string and strings and compose are just marked as stable so yeah then compose can actually safely detect changes in that string and only recompose this view when it needs to be recomposed but if you need a lot of your users Fields here in this composable then the better alternative will probably be to have such a mapper to map here in between of your users all right cool so let me close all these other tabs here again and let's get to optimization number three which we can see here I implemented a little custom grid so let's imagine you have some kind of video calling app and you want to display a bunch of video feeds in a grid but you you're not okay with a simple grid view you actually want your custom grid with custom alignment just like I I did here so we have three video feeds In This Very simplified example and in the first row we simply want to take the first two feeds and then we want to have another feed which is just aligned in the center in the second row and then we just yeah in this simple example we just have a row which displays both our first feeds and yeah in this case just a single or other feed but you can imagine if you have some kind of layout that displays a bunch of items in a certain order with your own custom layout then this optimization will yeah might be necessary and I also implemented a little feed view model which simply provides a bunch of feeds which are generated here so just a video feed object with a random ID a certain user it belongs to and we also generate a random background color so we can just distinguish these feeds a little bit and then it has a function which will rearrange these feeds so it will just Shuffle the list and therefore update our compose layout and if we now take a look in main activity and we let's uncommit rather comment all this code and we Implement our other custom grid here we first of all want to get a review model reference of that which we can say is equal to view model and this time it's a feed view model and we get the feeds by saying is equal to viewmodel dot feeds and then we can simply have a column make sure we fill the whole size of our screen fill Max size and we can then say okay in here we have our custom grid we pass in our feeds just make sure that we also fill oops that we fill our whole width from x width and below that let's just have a button and when we click that button we save you model rearrange feeds so when we click that button we just Shuffle our feeds and we say Okay Shuffle fades if we launch this again take a look at our layout inspector then we will see something like this so we open this composable tree again we have our color with our custom grid with our two rows and our video feed views if we zoom in a little bit and we open the app here then that's how you need to imagine that so we have three feeds which are just arranged in this kind of grid each belonging to a certain user if we now click Shuffle feeds and we just Shuffle the order of our feeds then something interesting will happen you can see our whole layout recompose basically if we click that again everything is red and you can also see here every single video feed just recomposed and if we think about that is that really necessary because none of these Feats actually changes its layout but only the order of our feeds changes so none of these feeds needs to be redrawn on our screen but here in this example it actually gets redrawn so why is that um the issue with this is that compose when when the list actually changes when the order of all this changes compose can't really see okay um that feed that was first at position 3 is now at position one because compose does not have a way to actually compare what belongs to a feed or what identifies a feed and that's very similar to what we are actually already be able to do with a lazy column so if you have a lazy column you are able to pass in a key and this key can be used to achieve this same behavior so lazy column already has this behavior that we are implementing here but since we have a custom grid here and also not a lazy Grid or so we we need to implement this on our own to have such keys and tell compose hey when when this modifier or this this value of our feed in this case changes here it would be simply the ID but then you don't need to recompose because it really just changed the um the position in our grid so let's take a look how we could implement this it's actually a very simple fix by going into our custom grid and here in the first row feeds we want to surround our each video feed with this key and in here we simply pass in the value that identifies a certain video feed so we can say feed.id and then simply pass in our video feed here so this will now only recompose if the feed data actually changes that has an effect on the on the actual composition on on the actual look of our composable but if just the order gets changed then with this key we we achieve that yeah there is no recomposition when the order changes we also want to do the same down here for our second row tracks pass in feed.id and then launch this and if we now also take a look at our layout inspector again open up our composable tree for custom grids here we have our video feeds and we now take a look at the app if we now click this you can see now nothing actually gets recomposed so only the order of overfeeds changes we don't have this big red box here that this whole section gets updated so we can now Shuffle this as often as we like without having any recompositions of each individual video feed here so that is really quite cool so whenever you implement some kind of custom layout that displays a bunch of items then it makes sense to consider these things here so I really hope this rather complex topic of composed performance optimization got clear to you this is really not simple and compose seems very simple on the surface which it is but there are quite a lot of difficulties when you take a look at its internals and I hope this video just simplified that a little bit to help you yeah optimize your app's performance if it really has issues apart from that if you are interested in yeah more of such Advanced Android videos then you will definitely love my Advanced premium courses I have on my website which you can find down below so there we have project where we build whole projects about yeah hours and hours of course material where you can definitely learn a lot to become an industry ready Android developer so that sounds cool to you definitely do check it out first link in this video's description and apart from that I will be happy to see you back in the next video bye bye foreign
Info
Channel: Philipp Lackner
Views: 27,707
Rating: undefined out of 5
Keywords: jetpack compose, compose, optimize, performance, boost, laggy, scrolling, scroll, recompose, recomposition, philip, phillip, phillipp, lackener
Id: EVVFhyuVV5g
Channel Id: undefined
Length: 21min 42sec (1302 seconds)
Published: Wed Nov 09 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.