Introducing FlashList: Let's build a performant React Native list all together

Bookmark

In this workshop you’ll learn why we created FlashList at Shopify and how you can use it in your code today. We will show you how to take a list that is not performant in FlatList and make it performant using FlashList with minimum effort. We will use tools like Flipper, our own benchmarking code, and teach you how the FlashList API can cover more complex use cases and still keep a top-notch performance.

You will know:

- Quick presentation about what FlashList, why we built, etc.

- Migrating from FlatList to FlashList

- Teaching how to write a performant list

- Utilizing the tools provided by FlashList library (mainly the useBenchmark hook)

- Using the Flipper plugins (flame graph, our lists profiler, UI & JS FPS profiler, etc.)

- Optimizing performance of FlashList by using more advanced props like `getType`

- 5-6 sample tasks where we’ll uncover and fix issues together

- Q&A with Shopify team

by



Transcription


Hey everyone, my name is Marek. I'm a software engineer at Shopify and I'm based in Berlin. And I'll be doing a presentation as an introduction to Flashlist. And then I'll hand it over to Talha to lead the rest of the workshop. So if you want to introduce yourself as well. Yeah, sure. Thanks Marek. Hi everyone. My name is Talha Nikwi. I'm a development manager here at Shopify. I lead a team called Retail Dev Accelerate, which is taking care of a lot of infra-related work for our POS application. I had the opportunity to collaborate with Marek and David on the Flashlist project. And I'll be walking you through the workshop after Marek is done today. Thank you. And then David will be here to support us. So if you want to give a quick introduction as well. Yeah, thank you Marek. I'm David. I'm from Barcelona, Spain. I see some people from Spain as well in the audience. Nice to see. I'm the manager here at Shopify since a year ago and I was part of the Flashlist team. And I'm proud of showing this to you today because it's a project that was like super nice to work on and to see the impact that it's doing in the community. So yeah, glad to be here today. Perfect. Then without further ado, I'll share my screen to go through the presentation and the slides. Yeah, let's start. Awesome. I suppose everybody can share, see my screen if not, tell me. As I mentioned, my name is Marek Fort and today I will be giving a quick introduction to the topic of this workshop, which is Flashlist. So let me start with a quick tweet here. What if React Native ends up holding us back? Hold us back. Where are you rendering? List of cards with checks in them. Now, of course, this is a bit of an over exaggeration, but it gives you a hint at how lists are perceived in React Native and that is inside the React Native community, but also outside. Additionally, we've had our share of issues with FlatList, the default component coming from React Native. This is our shop app running on a low end Android device. And as you can see, it's not responsive at all. And it has a lot of blank cells that sometimes span the whole screen. And in general, this is definitely not the user experience we want for our users. So we need a faster list and we need a Flashlist. But first, let's consider alternatives such as RecyclistView. RecyclistView is a popular open source library that tries to fix the performance of lists in React Native. And the advantages are that it actually really is fast if you implement it properly. And also the API is highly customizable, so you can achieve almost anything with it. And it is also quite well tested. It has been used for a bunch of years now in larger companies and it has held up well. But it also has a couple of shortcomings. Primarily, the API is really complicated, leading to less than desirable developer experience. It is also quite difficult to achieve performance cells with dynamic heights. And additionally, due to the fact that RecyclistView is a JS only implementation, there are also some first render layout spaces that should not be there. So these shortcomings meant for us that we really needed to build our own version at Shopify and we called it Flashlist. The goals of the project have been to achieve a high frame rate for both UI and JSFPS. We wanted to minimize as much as possible the display of empty items and we wanted to make the library really easy to use and achieve as much as we could for a really smooth developer experience. So this is the API for Flashlist that we've come up with. And as you can notice, we have mostly just changed the name of the component from Flatlist to Flashlist and tried to keep the vast majority of the props the same. Now, there are some props in Flashlist that do not exist in Flatlist for achieving better performance and you will learn about some of those later in the workshop. Dynamic heights, they pose no sweat. And as I mentioned, that is even with the Flatlist kind of API. So let me answer a question now. How is Flashlist so fast? And I'll start with actually explaining how Flatlist works under the hood first. So in Flatlist, we have a couple of items that are in the viewport here, indexes from four to six. Then we have a couple of preloaded items that are at the bottom of the list. And then we have a couple of items that are still loaded in memory, two to three. Now, the amount of items in memory is in the Flatlist case, quite high. But as you scroll down enough, Flatlist will need to free up some space in the memory. And that's when virtualized list comes in, a component that Flatlist uses under the hood. And what it will do is, as you scroll down, it will empty the items so that it replaces the content with just an empty view and keeps the dimensions of the project, of the cell. So the list layout does not change. This also means that as you scroll up and you get to these emptied items, you will need to render them always from scratch. And also, as you scroll down and you need to load more items, you will also always need to render these items from scratch, which takes a lot of resources. As a visualization, here you can see cells with their instance IDs. And as we scroll down, we just get more and more IDs, which means we get more and more instances. And again, everything needs to be rendered from scratch. So to sum up, Flatlist uses this virtualized list under the hood. And as you scroll, it replaces cells with empty content. And an important thing to note is that cells are always rendered from scratch, unless in memory, which takes, again, a lot of resources. Flatlist, on the other hand, takes a different approach. We again have a couple of items in the viewport. We have some items that are preloaded, but the amount of those items is quite low. And most importantly, we have a concept called recycling pool. So as we scroll through the list, the items that are not visible on the screen anymore, they are put into the recycling pool. And then when we need to load more items, Flatlist will look into the recycling pool. And if there are any items that are currently not in use, it will move that item back into the list and just re-render it with the new data. This means that, for example, if only a text in the cell changes, it will have to re-render just that part, and it will not have to create anything from scratch. And that makes it quite efficient. As a visualization, you can see as we scroll, the items are being put into the recycling pool. And then when we need to create more items at the bottom of the list, Flatlist will actually start pulling out the items from the recycling pool back into the list, and we will start to get instances and IDs that we've actually seen before. And again, this saves a lot of resources. We also needed to fix the first render layout issue. So this is how the first layout issue looked with Recycle List View. And you can see that the items kind of overlap each other. Now, this is only for a couple of frames, so this recording is slowed down to make this more obvious. To get deeper into the problem on the left-hand side, you have how Recycle List View expects the layout to look like. It expects every item to be the same, depending also on the estimated item size, which is a problem we'll also learn more about later at the workshop. But the actual layout, the thing that's actually rendered on the screen, is different. Here we have an item number zero that has, for example, 100 pixels of height, but then the item number one, it has, for example, 200 pixels, but Recycle List View still comes with it having only 100. And so the item number two is then over the item number one, resulting in items overlapping each other. So we implemented a native view called Auto Layout View that goes through the whole list, and whenever it sees items overlapping each other or when there are spaces between items, it automatically fixes those UI issues and puts everything edge to edge as it's supposed to be. So with FlashList, if we look at the recording, we can know like... To sum up the FlashList implementation, we have an API that's very similar to FlatList. We've implemented a native view to fix the first render issue. We've been able to achieve performing dynamic cell heights with the simple API similar to FlatList. And we have used Recycle List View as the inner engine that we've built on top of it to achieve that API and also give you more performance out of the box. So we'll quickly look at some of the metrics, but you will also get the metrics firsthand during this workshop. I have been using a Motorola Moto G10 device, which is quite low end. And so keep that in mind when seeing the numbers. First, let's look at FlatList UI and JSFPS using Twitter sample, very similar to what we will see in the workshop. And as you can see, the JSFPS drops multiple times to zero and UIFPS drops to sub 10 as well. So what do we get in the end is average JSFPS of 11.8, average UI 44, and JS thread lock is almost two seconds, which means that for almost two seconds, user has not been able to interact with the screen at all. Because when the JS is in the thread lock, that's not possible. If we look at FlashList UI and JSFPS with the same sample, by just changing the name, we can see that the JSFPS never drops that low, never drops to even close to being zero. And UIFPS stays around 60 for most of the time. So we get an average JSFPS of 49, an average UIFPS of 58, which is about a four times improvement just by changing the name. We have also been looking at blank spaces using a Flipper plugin that we've built just for this project. And as you can see with FlatList, we get huge blank cells spanning the whole screen, which are the real large bars at the top. And the average is around 280 pixels. With FlashList, we get a different story. We do get some blank cells, but they are quite limited and only appear for a limited period of time. And so the average in the end is around 45 or so, which is seven times better than with FlatList. So is FlashList production ready? Let me get back to the initial example of ShopApp on the left, you have FlatList, which was for multiple seconds, not responsive at all. And it had those huge blank screens that were spanning the whole screen. And on the right, we do have some small blank cells, but only for a limited period of time. And more importantly, the app is extremely snappy, even on such a low end Android device such as Moto G10. We are also using it in all of our apps, in Shopify, Mobile Shop, POS, and Inbox. And we've also seen a lot of excitement in the community. And the list is also being used externally now in production as well. So you can check out the project on GitHub, but you will also do that during this workshop. And that's it for me. And I'll pass it over to Talha, who will get more into the coding part of the workshop. Thank you. Thank you, Meric, for the amazing introduction to FlashList. I'll take over from here. Let me start presenting my screen. OK, so before we begin, I'll quickly introduce everyone to... All right, even before I start, right, can I get some thumbs up in the chat if everybody can hear me OK? And if you're here just to let me know. Awesome. Awesome. I get to see some responses. All right. All right. OK, so I'm sharing one document in the chat right now. Make sure that you use this link and open this document. So this is what you're going to see. It's right here on my screen. So this is basically all the setup steps that we have for you. I'm going to walk through each of those. I just want to give you a quick overview of what you can do in case you run into problems. So first of all, there's a link for questions. Make sure that you use this link to ask us questions and not the chat. We are going to try and go through most of them towards the end of this workshop. There's a link to a performance tracking sheet, which I'm going to talk about very soon. You have a link to the slides that I'm going to present. Most importantly, there is this section called skip ahead. So at any point, let's say if you have not been able to complete a particular task, you can skip to any task that you want. Let's say if you jump to task four, task one, two, three will already be done in that snack for you already. So no need to worry if you are not able to keep up. You can skip ahead and you can do it at your own in your own time later on. Right. So without further ado, let me get into the workshop. Right. I'll start presenting. All right. So make sure that you have Expo Go installed in your phone and. Just make sure that you open. The link that's mentioned in the document here, open the following scan, you can open this and you'll see something like this. I'm also going to scan this QR code and make sure that my phone is connected. Make sure you scan your QR code and not mine. So here we go. You can see my device on the right. It's a Moto G stylus. So I have my device connected at this point. You can see this device does show up in slide. I can click on the reload button to reload at any point. And on the left, you can click on the no errors and one warning and logs to view the logs that are going to show up. So that was a quick introduction on how snack is going to work. Right. As the second step, go back to the document and open the performance tracking sheet. Going to look something like this. So what I want everyone to do after you have launched the sample and you can see it in your Expo Go application, just go ahead and click this checkbox and add your name and device on the left. This is how we can keep track of how everyone is progressing. Joshua, you make sure that your Expo Go app is up to date. Right. Go ahead, add your names. Okay. All right. First Android device on the list. Glad to see that. iPhone 14 Pro Max is also there. Is there anyone who's running into any issues? All right. Go ahead. Go ahead. Yeah, don't worry about the rest of the columns. I'm going to walk you through what we're exactly going to do and we'll do it together. Just add your names, your device and whether you have loaded up the sample in your Expo Go application. All right. We'll quickly explore what the sample is about and then we'll start with the first task. This sample is a list of Twitter feeds and some of these tweets have related tweets along with them. So if you notice, anything that has more than two retweets has a related tweet. So it's a combination of vertical lists and horizontal lists. If you click any of the horizontal items, it's going to change the primary tweet and the element is going to become selected. And that's all that this sample does. Feel free to play with it. So moving on, I'll just move on to the next task and we'll start doing stuff now. All right. What's the first thing? So what we want to do is, if you notice there's a run benchmark button on the top. I want everyone to click on it and wait for the benchmark to run and finish. It's going to give you an average FPS number at the end of the run. I'll do it along with you. Let's do it together. All right. OK. Mine is running. OK. So I got a result. My average FPS was 1.7. Remember, mine is a pretty low end device. It's a MotoG stylus with a Snapdragon 665. So after you're done, what I want everyone to do is go and put this average number in the baseline column. You just have to click that benchmark button and wait. All right. Very nice. A lot of people have been able to do it. Remember, you have to put the average number, not the max FPS. Josh, your number is pretty high. Right. If you think you have not got the right number, make sure that you reload the application and make sure that you chose the right link and just run the benchmark again and you can update it later. Yes, this number is indeed dependent on device performance and I'm using a very low end device MotoG stylus, Snapdragon 665. Flat list, all the items are memorized. Everything is as it should be. It's just that when you have a vertical list and horizontal list nested together, it's a pretty complex thing. That's what you open the final sample direction. All right, Joshua, make sure you fix it. So we'll go ahead in the interest of time because I really do want to take questions in the end. Okay. So what's the next thing? What we want to do now is let's look at the sample. All right. If you open the SRC folder inside Twitter, you would find three files, tweet cell, tweet content and Twitter. Twitter is where our flat list is defined. So what I want everyone to do is start their flash list migration. I'm going to do first couple of things with you. So follow along. But in the rest of the task, I might pause for a minute or two and let you do it. So what we just need to do is we need to change the name to flash list. And I need to import it. All right. That's all. Change the name to flash list. And I'm going to go ahead and reload the application. And that's it. We have completed the first step to migration to flash list. What's the next thing that we want to do? What we now want to do is we want to observe the load time for this list. What's load time? Load time is the time that it takes for your items to actually become visible on the screen. And flash list has a neat API that enables you to do that. So I'll go ahead and do that. Of course, I'm copy pasting some code. But feel free to type it in. So this is the way you do it. You add an on load listener. It gives you an object from which you can select elapsed time in milliseconds and you can log it. So let me just reload the app again. And you'll see that we have a load time up here. All right. So my load time for this list is 1319. Pretty huge. It's taking over one second to render. If you notice, flash list is giving us a warning here. It's saying that estimated item size is not defined. And based on the current configuration, I can set it to 279 pixel optimized performance. What does this mean? So before rendering, flash list usually has to make a guess on how many items it should start with. And in a lot of cases, that estimate can be wrong. In this particular case, it might think that each item is 100 pixels. But after rendering, as a developer, now I know that my items are on 279 pixels. And flash list has given me that suggestion. And I can provide this information to flash list up front. And it can use it to optimize some of the things. So let's go ahead and do that. What we'll do is we'll have to do estimated item size 279. Once I've done that, let me reload the application again. Let's wait for the load time to show up. All right, my load time is now 485 milliseconds. So what happened here? Flash list was able to make a much better guess on how many items it requires in the beginning. And that led to a smaller number of items being mounted. And if you see, we have already cut down the load times by more than 50% just by specifying that estimated item size. So it's extremely important that it's specified. All right. So we have seen estimated item size and its impact. Now let's give me a quick thumbs up if you have been able to do this. Thumbs up, done. Anything in the chat? Awesome. So I'm seeing some things come up. That's great. All right. So let's repeat what we did the first time. We are going to click run benchmark together on the updated code and we'll record the numbers under task one. Let me do it. All right. It's running for me. Right. It's all white. The JSFES is now just 2.9. Still pretty bad. I'm going to go ahead and call it under my row. 2.9. I'm glad to see that a lot of you are able to get this done. Make sure that you are picking the average number and not the maximum or the minimum ones. All right. Moving on to task two. What we want to do is as Merrick described, right, flash list's goal is to reduce the number of items that are being created and destroyed. So how can we observe that? We can use the following code snippet inside our tweet cell and tweet content items. And all right. Mia, you can simply click on the link here and task one would already be done for you and you can just start with task two if you were not able to finish it. But if you need a few extra minutes, go ahead and take that. All right. All right. Okay. So let's go ahead and do this. So first I'm going to go to tweet cell and I'll use effect and use effect and I'll return a function here which is going to take and solder log tweet cell and mount. And we only we want this to run only when the components unmounts while passing an empty dependency array. Very simple piece of code. And same thing, I will copy and paste inside tweet content. That's on line 72. And I'm going to change my console to tweet content. And I'm going to import use effect. Go ahead and take your time. There's no rush. So I am done. I'm going to reload the application now. Give me a thumbs up if you if you have done this. So. Great, great, great. Okay. Okay. Let's observe. I'll clear the existing logs. Let's see. I'm going to scroll manually. And. Okay. You see our tweet cell is unmounting all the time. Tweet content is also unmounted. A lot of unmount and mount events. This is something that we want to really avoid. All right. So the idea of this particular task was to just observe how many mount and mounts we have. And then just run benchmark one more time. So let's do that. Make sure that you comment out the console.log because it impacts performance. But we are going to go ahead and uncomment it in the next task. So I'm going to go comment console.log. Start the app. Wait for it to load. And let's run the benchmark again. Okay. For me, the result is 8.9 FPS. Right. I believe it didn't do much for, you know, changing performance at all. But given that this is a low end device, sometimes results can be a little bit flaky. And I believe that's what happened because I certainly didn't make any changes to improve anything. Okay. So let's capture those results. A lot of you are 41, 47, 44 already. 56. Yeah, I mean, iPhone 13 and iPhone 14 Pros, they have really solid single core performance. That's why, you know, they can just brute force their way through some of these performance issues. But they are a lot more visible on low end Android devices, even mid range Android devices. I think that's what the result is going to reflect. All right. I will move on to the next one once we are over 50%. Come on. Yeah, sometimes Snag doesn't reload. So if you are suspicious, just just just trigger a reload either from the web console or by shaking the device and using the reload button. All right. Okay. Let's proceed. So what's task three? I wanted to quickly touch on the function of key prop and react whenever he basically mounts your JS like maps your JS instance to an actual UI instance in the DOM. And whenever you change a key in react, it's going to recreate both the UI node and the JS node. And this is specifically what we want to avoid in flash list. So the next task is all about figuring out where these keys have been explicitly assigned and we are going to remove them and see their impact on on the mountain and mount operations happening. We have observed that in a lot of cases when you're using flat list, you tend to add these keys because it's not really impacted by it. It's destroying and recreating anyway. So having it or not having it does not make much difference. But as you'll notice, in case of flash list, it can make a huge, huge difference. So I'm going to remove one and then I'll have you look for the other one. All right. So if you go to Twitter, you'll see that the primary render item has an explicit key defined. What we want to do is we want to go ahead and remove it. That's it. And we also want to go back to tweet cell and again, uncomment our console.log so that we can observe the amount of events again. So that was the first key. Go ahead and try and find the other key if it's defined anywhere and let me know where it is in the chat. Could be in any of these components. Wait a minute and then we'll proceed. It looks like people have found where the key is. Yes, it's inside tweet content. And it's on line number 78. If you see this, this key isn't even part of any map statement. This really isn't required unless somebody explicitly wanted to destroy and recreate this element. So we can go ahead, remove it. Let's reload the application. Let it reload. Sometimes it takes a while even after it shows up. Now it actually reloaded. OK, let me scroll. If you notice, tweet content is still unmounting, but tweet cell is no longer unmounting at all. Like I don't see a single log in there. And the reason is because we removed the key prop that was defined on tweet cell. So the only problem we have right now is tweet content unmounting and we'll figure out how to solve that very soon. So given tweet cell isn't unmounting anymore, I'm going to just go ahead and remove this use effect for tracking unmounts. And like we did last time, I will comment out tweet content unmount because it's going to interfere with our benchmarks. So comment that. Reload. Let's do it. And then we are going to run benchmark again and see if we have any improvement. OK, let it reload. OK, my list loaded. 491 milliseconds. Give me a thumbs up if you're ready to run benchmark. OK, nice, very nice. Let's do it. It's still going black. If you notice my Moto G stylus goes totally black in most of the runs. All right. Eleven point six. So in my case, I have a little bit improvement, but nothing major to really celebrate. It's still pretty bad. Right, go ahead, finish your benchmarks and then we'll move on to task for this is where I think. Things are going to start getting interesting. Yeah. OK, we are over 50 percent. OK, do you notice any issue with the list right now? Ever since we removed that key. If you already see the issue, you can comment. OK, let me show you. Look at the top items. First tweet is by Chris. Second is by Lorenzo. Yeah, somebody got it. The items are repeating. If I go back to the top again. The tweets changed. Why is this happening? This is happening because items are getting recycled and when they are getting recycled, they're not getting updated. And, you know, this could also be because because of which my performance improved because nothing is happening. Items are already just moving. So I'll quickly show you what is going on and why it's a problem and how we can fix it and then we'll fix it together. If you notice to each cell, I told you right that when you click one of the horizontal items, it changes the top level tweet. And this current tweet is maintained using a state. Whenever that click happens, the state is updated. But when the item is actually getting recycled, the state is not changing. So current tweet is actually preserved. And that's the whole problem. This is not a problem in flat list, because when it recreates the item, the current tweet is always equal to the incoming tweet. But here, when this item updates, current tweet still remains the same. So any ideas? Anybody wants to quickly comment in the chat how we can fix it? Right. OK, it's our favorite hook use effect. Let me show you how it can work. So this is the code snippet that we are going to use. So what we need to do is let's let's do it together. Use effect. What we want to check is if current tweet is not equal to tweet, we want to call set current tweet with a new tweet. And we want to run this only if our tweet changes and not on any of those horizontal item clicks. So the tweet will only change in flash list reuses an existing component. And that's where we want to make sure that we are syncing the state. Feel free to copy this code snippet use effect if current tweet is not equal to tweet set current tweet format so that it looks clear. I will go ahead and reload. All right, it has reloaded. So my first two tweets are Aram and by Gurgli. And let me scroll. I'm not showing sure if I'm saying their names right. But forgive me if I go back up. You see everything is all right now. There are no items repeating. Has everyone been able to fix the issue? Give me a thumbs up if it's fixed for you now. Awesome. I'm glad to see everybody is able to keep up nice and easy. We were pretty apprehensive, honestly, in the beginning that is it too difficult. We even did like a trial run yesterday just to make sure that it's not it's not too difficult. Okay, if everyone's ready, let's go ahead and run the benchmark again. And let's look at the results. We are adding another render. It might actually slow down. Let's find out. In the meantime, can anybody tell me if this is perhaps not the best way to update the element and what can be the potential problem? If you see my numbers have actually decreased 6.6. Not seeing a whole lot of improvement. Okay, everybody is already beyond 50% of what I put in my numbers. That's so cool. I'm so glad to see this. Okay. Okay, let's move on. Task number five. If you notice the horizontal list is also a flat list. So what we want to do is we want to migrate that to flash list. And we want to look at bugs. So go ahead and do it. Again, when you migrate, it's going to give you a suggestion for an estimated item size. I'm going to wait a minute to let everyone do it before I also go ahead and do it. All right. And then we'll discuss the bug and how we can fix it. Okay. Let's do 40 seconds. It shouldn't take much time. And then I'll do it. Also try to find out or notice any bugs if there are any. All right. I'll also start doing it. Okay. Starting it. Have to go to tweet cell. Change flat list to flash list. I need to import flash list from at Shopify flash list. I am going to now reload. Hopefully it will give me a suggestion for my estimated item size because I don't want to guess it right now. Okay. I have a suggestion. It's 352. Let me go ahead and add that estimated item size 352. And reload one more time. Anybody see any issue? A few items can repeat in the horizontal list because we are randomly generating that recommendation suite. But the repeating is not the problem. Okay. I'll show you what the problem is. When you click on any of these items, the top tweet is going to change, but the item itself is not going to get highlighted. Previously you would see like a light blue border show up. That's no longer happening. Although it's functioning. So, why is that? The reason for that is flash list is extremely aggressive in cutting down renders. So, instead of opting out of re-renders using use memo and things like that, you rather opt into re-rendering. If your data has changed and your item has changed, only in that case flash list does a complete re-render of items. Otherwise it won't. So, and if you see our border color is dependent on whether the current, like the tweet displayed is the current tweet or not. And if this condition is true, the border changes. But in this case, flash list is not going to re-render at all because the data has not changed when we click the item. So, how do we solve this? We use the extra data prop, which is part of flat list and flash list both. And we can ask flash list to also re-render if current tweet changes. I'm going to reload the app again. And once it updates, this issue should be fixed. All right. If you look at my load time is again reduced by almost 50%. Like I started out with 1.3 seconds and it's not 238 milliseconds. It's an insane amount of improvement. But let's see if our bug is solved. All right. Yeah. I can see the light blue border again. That's cool. Okay. All right. Now let's go ahead and run the benchmark again and look at our new performance numbers at the end of task five. Right. It's looking better. It's looking a lot better. At least for me it is. 21.2. It's a pretty big improvement actually. It's very likely that folks with iPhones are already hitting the 60 top line at this point. Okay. That was it. Before I move on, I'll also quickly go ahead and you don't have to do this. You can just observe it with me. I will uncomment this console.log and reload the application again. And you'll see that the tweet content will no longer unmount because we have moved to flash list. All right. Four unmounts. These are the previous items. Let me get rid of the logs. I'm going to now scroll. Right. Oh God. It's still unmounting. That's weird. Okay. Right. What might be happening? Okay. Let's reload one more time. Maybe there's still something wrong. All right. Reloaded again. Let's see. Okay. There are a few unmounts happening which is okay. We'll figure out why that's the case. All right. All right. So to itself we have. Okay. Let's move on to task number six. Get item type. What is get item type? This is the final piece of the puzzle if I put it that way. If you notice the unmounts are still happening because we might choose the wrong base to recycle items. For example, this particular tweet has no related tweets. And it might get recycled and reused to render a tweet which has related tweets. What that means is we are going to see a lot of mount events as part of that. And vice versa, if I use this tweet which has related tweets to render something that does not have those related tweets, all of those related tweets which we rendered before go to waste. Let's look at the code to understand why this happens. Inside tweet cell on line number 34 you'll see a check where I'm checking if recommendations.length is greater than zero. Only then we are rendering the flash list. So if this condition is mismatching between items, we can run into that issue. But we already have a fixed logic on to decide whether or not we are going to show related tweets which is basically this. This is where we are checking if tweet.retweetcount is greater than two. We generate some related tweets and we show it to the user. What if we tell flash list that we are doing something like this? And flash list has a way in which you can do that. All right. So how does this work? This is the code snippet that we are going to use. Get item type. You can go to your top level Twitter element and add this code. What's the code? Get item type. You are going to get an item. And if the item.retweetcount is greater than two, we tell flash list that this is like an RT type element which is short for recommended tweets or related tweets. If the retweetcount is less than or equal to two, we return T. This way flash list can now figure out internally what items it should use to recycle what kind of items. And this is going to reduce the number of unmounts that we are seeing even more. Let me reload. My console.log is already uncommented. As you can see here. We will observe if we are still seeing any unmounts. All right. Everything is loaded. Let me scroll. Some unmount. Two unmount. Flash list can unmount a few things based on number of factors where it doesn't need. But on even scrolling quite a lot, you can see we are not getting repeated unmount events at all. Right? I'll give you a minute to do this. And give me a thumbs up once you also don't see any unmounts. Did someone scan my QR code? I saw an iPhone. I saw iPhone log. Awesome. I think some of you have already been able to do it. Yeah, unmounts are totally gone. That's awesome. And that's what we set out to do in the beginning. I'll actually go ahead and get rid of this use effect because nothing is unmounted. So I don't need it. I'm going to reload one more time. Whoever has connected to my Slack, your app will also reload. Okay. Let's see. Okay. Awesome. Let's do it again. Let's run the benchmark and see the new numbers. It's looking a lot better. Most excited to see what the new number is going to look like. And remember, my iPhone is also recording. 32.4. 32.4 for me. 32.4. And see, even if you don't have a low end device right now, the material is all there. The Slack is there. So you can in the future lower the starting Slack on a low end device and then try the final one to figure out how much of an improvement you can expect. But yeah, like on an iPhone, you would have already hit the ceiling right now. Because the JS render is not going to capture anything more than 60 FPS for now. A lot of people might be at 60 FPS at this point. But there's one more task left. It's not over. Okay. If you remember, in one of the tasks, we added a use effect. This is something that I wanted to touch on exclusively. Because usually people add use effects to update their states and sync it with external data. But there's an alternate way where you can avoid the extra render. Our current setup, if I go back to the code, this use effect is going to be called after we have done one update. And then it's going to check this condition, call set state, which means that the item is going to render two times. And some of these items have these nested horizontal lists, which are super expensive to render. So we definitely don't want to do it twice. So let's see if we can do it just once and how to do that. Okay. So the code snippet is in front of you. Let me do it and you can follow along. What I can do is I can create this ref source tweet to track what my source tweet is, which is coming from, let's say, Flash List. I made a copy of it. Instead of use effect, I am now going to, in the same render loop or render path, I'm going to check if my source tweet was equal to the tweet that is now passed by Flash List. If it isn't, we capture the copy of it in the ref and we call set current to it in the same render call. What this does is, so in this particular case, let's say I talk about tweet content. So the render method of tweet cell, our current component, is going to get called twice, but the render of tweet content is going to be called once. So it's like two renders, but below the tree, it's just one render. So Flash List is also going to get rendered just once. So if you do it this way, there are, of course, multiple ways of doing it and React has this documented on how this works and why this is better than using use effect. But for Flash List, we strongly recommend that if you have states, you sync them using this method because this is much faster than using use effect. Again, the improvements depend upon your particular component, but as you'll see in this case, it's going to make a difference. Let me reload. And we're also just going to observe if we still see those repeat element bug or not. It has reloaded. Let me scroll a little bit. Let me come back. Everything is looking good. Give me a thumbs up if you were able to do this. Awesome. Awesome. We are not going to run benchmark just yet. There's one more thing. Draw distance. Many of you would be familiar with the window size prop in Flash List. Flash List also has a prop. It's called draw distance in this case. You can change this prop to a smaller, like by default, Flash List only draws 250 pixels ahead while Flash List draws ten times of your viewport ahead of time. So Flash List draws a minuscule number of items compared to Flash List. But in case of horizontal list, you do not even need 250 pixels ahead of time draw. At least, you know, in this particular example, the item is pretty big. So I can probably just go ahead and change draw distance also. I'm going to make it zero. You can also choose not to do it. Like even without it, we'll see a lot of improvement. But let's do it because we know that don't really need to. And in case of Flash List, setting it to zero is not going to lead to any huge functional problems. Like in case of Flat List, I know in some cases when you change initial number to render or window size, you run into issues. Items do not load. But it's really a no compromise thing in this particular case. Right. If you are done with setting draw distance of the internal list, reload the application and we are going to do the final run. Right. Just 200 millisecond load time. OK, let's do it. It's looking pretty good. Don't even see blank spaces anymore. 50.6. 50.6 for me. 50.6. Zack's results are pretty interesting. He started with 60 FPS. Still at 60 FPS. All right. So that's draw distance. I think we have we have. And you know, that brings us to the. The end of the actual coding part of the workshop. Let's quickly look at the results. So in my case, if you see, I started at 1.7 FPS on a low end device. And my final number was 50.6. So I actually increased my. JSF is 240 by 48.9 FPS and that constitutes to like 29 times improvement in JSF. Yes. Right. And if I look at some of the iPhones we saw, see, interesting thing to note here is even an iPhone, right? If you're just seeing one point to X improvement, Flashless will actually be consuming. Very small amount of CPU compared to the flat list. So like brute forcing is possible, but you are burning through a lot, lot of energy and battery. Your device is going to warm up, things like that. So you will still see battery improvements and devices going to remain cool even on iOS. But on Android, it's like night and day. I can already see. You know, KB getting close to 16 times improvements before the numbers was 7.2. Now the increase increased to 116 FPS, that's like almost 120 FPS. What are the other big numbers? OK, iPhone 11 Pro going from 8.8 to 60. That's like almost six times increase. So on latest iPhones, we do expect you to see around 1.8 to 1.2 times improvement. But if you observe the CPU usage, it's going to show a similar trend as what you're seeing on Android. All right. All right. Since I have two more minutes before I finish, I would want to show you one more thing that's pretty interesting. So we have been looking at JS thread till now. What about UI thread? Because Flashless actually reduces burden on your UI thread even more. So I can do that. So on my Android device, I'm going to go to settings. Developer options. Sorry, wrong option. I do this all the time. If I scroll down, there's something called profile hardware UI rendering on screen as bars. OK, going back to Expo. So first, I launch what we started with our original sample. So this is with flat list, both vertical and horizontal. Let me reload. So every time you see these bars going above the red line, that's a frame drop. And the longer it is, the worse it is. Let me run the benchmark and see how it goes. OK. A lot of tall bars. And it's pausing because JS thread is not able to keep up and is not able to even issue instructions for drawing. But maybe while coming back, it's going to be better. All right. 1.7 again. And you see these huge frame spikes. And this is where your UI is also going to freeze, not just blank, actually freeze. Let's go to our Flashless sample with everything done and fixed. Yeah, let me reload. I just reload all the time just to be safe. OK. Let me run it. OK. All right. Even with recording, you see that we remain below 60 FPS for quite some time. Of course, we are dropping frames because it's a low end device and we are also recording or mirroring. But yeah, if that's not the case, then it's honestly possible even on this device, if I disconnect from mirroring, it'll do 60 FPS. And you do not have a single huge spike freezing your UI thread at all. So that was quickly some notes on UI thread performance. I'm going to quickly go and disable that. Right. So that brings us to the end of the coding part. David, do you want to run through any questions if you have any? Yeah, I was just remembering the chat. We have this Lido. But in case that you want to go ahead and raise your hand if you want to do the question here. I see no questions there. I see one question in the Q&A here. But I think you're coding. So yeah, it's difficult to go to because it was extremely fast paced. But yeah, go ahead and raise your hand. We will unmute you and you can ask your question. Or you can type it in the chat. We have a question from Juan that says, can the items have different sizes? I think we answered that in the presentation. Yeah, if you look at this already, right, the items are of different size. Right. Flash list does not ask you anywhere. And this is one item like this tweet and the horizontal tweet is actually one item because we are rendering it inside to itself. And FlashList did not ask you for different estimates or fixed sizes anywhere. But it still works very well. So dynamic heights are no problem at all. Cool. If you want to share your thoughts or comments, go ahead. You can also do that if you do not have any questions. Yeah. Thumbs up if you actually do not have questions. Either we did something really good or really bad. Maybe I can ask a question like for the audience, like how many of you know about FlashList before this workshop or how many have you tried before joining the workshop? Maybe just give a thumbs up as well if you tried in the past or now it's the first time that you see it. Nice. All right. I see Zain mentioned that they're going to get back to React Native from Flutter after this workshop. That's a win. Yeah. And folks, the more complex your list, the more gains you're going to see. On our website, we actually claim up to 10x, but you can easily go over that if your items are complex enough. Yes, we do have section lists. We do not. It does not come out of the box, but we have a tutorial in our documentation how you can very easily use FlashList to build a section list. You can even copy code from there if you want to. But section list is nothing but FlashList with sticky header indices. Also recently we released the masonry layout, something that FlashList doesn't have. So this is like a Pinterest style layout that the different size elements. Maybe if you have. I can show it. I think it's there on my Slack. Yeah. Not Slack. So used to saying Slack. Okay. Yeah. This is what the masonry FlashList looks like. You have different size. The elements can all have different heights and they'll still fit together. And FlashList can also automatically arrange them for you so that you do not get columns of very different heights. And it's pretty fast. So it's as fast as your regular FlashList. Maybe 1% or 2% more overhead is there to compute all of this, but you can still get like 120 FPS, 60. You can max out your devices. Okay. So a lot of people actually heard of it but never tried it. I'm hoping that now you will. And, you know, feel free to let us know or reach out to us on Twitter to share your experience. We also have a Discord community where if you run into issues, you can it's there on our website. You can go there and ask us questions, share your specific examples if you need help. Okay. As a final note, yes, thank you, everyone, for joining us. It was awesome to run this workshop along with Merrick, David, and Mercy. It's a lot of fun for us to see these numbers and see the improvements that this library brings to all the application and community as a whole. We would really appreciate if you would go on the repository and give us a star. We love our stars. And the document also has all the developers that worked on this library. You can go ahead and you can follow us on Twitter if you have questions. And if you got some good results today, feel free to tweet about them and let the word spread. Because I imagine, right, a lot of people still do not know about this. So if you tweet and spread the word, it's going to be really helpful for us and the community. Right, I'll stop sharing now. But we still have two minutes if anybody has any more questions. Okay. All right, then. Mercy, I think we can stop recording. Cool, folks. Feel free to drop off. We are done for the day. Thank you for joining us. Thank you, everyone. Thank you. See you. Ciao.
81 min
19 Oct, 2022

Watch more workshops on topic

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career