 So, this is my first talk in a conference. Be kind to me, please. So, I'll be talking about the mistakes that we fixed. A retrospective of the TRIBO mobile app. So, a little bit about me. I'm Kashesh Grover and I've been building with React Native for about two years now. You'll find me speaking about React Native to my fellow developers in our local React Bangalore community. I also like to play drums, drink beer and cook. So, about me at TRIBO, I've been here for one year almost and I've been solely responsible for our consumer app. And I joined when the project was orphaned. So, basically I got a code base which I had to fix for them. And the developers at the time, they had left and they brought me in to take care of this app. So, at TRIBO, we absolutely love React Native and we've been using it for about two years and our consumer app is completely React Native. So, all that said, it's been a super bumpy ride for us and even though it has been a really rewarding one. So, today I'm here to talk about our experience with React Native. I'm here to talk about some mistakes so that you don't make the same mistakes that we made. The mistakes I'll be talking about are three of them which is unhealthy code, bad developer experience and poor performance practices. So, I'll be talking about all of these in detail one by one. So, let's get started. So, talking about unhealthy code, I'll be talking about engineering baggage, chaotic rules, improper app versioning, confusing project structure and unsystematic components. So, starting off with engineering baggage. Now, any not so simple app needs third-party libraries to implement different features. So, our app, it was an experiment at the time by people who didn't know anything about React Native. So, a lot of libraries at the time were chosen just to save some time. I mean, we already had a React ecosystem and we just wanted to be able to copy-paste code. So, but some of these libraries would end up causing us so much trouble later that I had no idea. And it was difficult to fix them because I didn't code them right. So, it was difficult for me to fix all these issues. And things like this would prevent us from going to a newer version of React Native. And React Native development is super fast. I mean, you have to keep up to be able to, you know, get all those benefits that every other new version gives you. And some of the newer libraries, they also required us to update to the newer React Native version. So, we were sort of stuck in a loop. And I mean, so, let's talk about an example. So, we had a library, we have a library called React Native RazorPay, which helps us facilitate payments in our app. Now, this particular update, it required us to update to the latest version of React Native. And it says this package has been tested on version React Native 0.58, which is a very newer version. Now, you may ask me, why did we need to update this in the first place? I mean, it was working fine, but still, why did we have to update it? That's because a few months back, Google changed their permission requirements around SMSs. So, we couldn't ask the users for SMS permissions anymore based on our use case. We are a hotel booking app. We cannot ask the users to be able to read their SMSs. So, we cannot take this permission anymore. And this permission was being asked in this library. And these guys, they went ahead and removed that permission. And so, we had to update it to the latest version. But to make our app compliant again, we had to update React Native as well. Now, you know, we all know what an app experience is like. Some libraries were chosen to make it easier for us to copy page score. For example, we have a navigation library, right? We had a navigation library which didn't even save the scroll position. You go from page A to page B, you come back, all that scroll position is gone. I mean, that's just really poor user experience. That's not like an app at all. So, in our case, it was primarily visible when we went from search page to hotel page. And then we came back. The whole search page would get remounted. So, imagine you are on hotel number 40 and you just go back to the top right away. So, these kind of libraries, they got stuck with us for a very long time. And we also had been using a few component libraries. But tell me one thing, is it fair to use these kind of component libraries if we hadn't been using all those components that React Native gives us out of the box? And we weren't even using these component libraries completely, right? So, figuratively, this is what our app used to look like. So, to fix this kind of an engineering baggage, we have four options. Update it. I mean, is this library better now? Is this something which is popular and does it continue to have good support? Remove it. Can we make an alternative for this in the existing codebase? Replace it. Is there some other library which does a better job? Absorb it. Okay, so we cannot get rid of it and we cannot keep fixing these issues. But maybe we can fix some small things. So, why not we copy that in our codebase for now so that we can replace it later on? And we ended up building a few abstractions as well for things like components for load-ash functions and other utils, for services that work with business logic so that it becomes easier for us to change these abstractions later on. And we ended up replacing this old navigation library I talked about with React navigation. And now that Skoll thing is not there. Skoll position is saved because React navigation works on a stack. So, after getting rid of most of this engineering baggage, we spent some time to upgrade our stack. And I'm happy to tell you this that we are on the latest, I mean not latest as of two days ago, but the latest version of React Native now 0.58.5. So, the latest version is 0.58.6 as per what I read. And after making our package.json as lean as ever, it has become much easier for us to upgrade our dependencies for 90. Next thing I'll be talking about is chaotic rules. All our front-end projects are based on React. And this React Native app didn't even follow the same LinkedIn guideline. That was just super bad developer experience. Imagine you are a web developer, you are taking a look at my PR, but it doesn't look the same. So, PR reviews were super difficult and because at the end of the day I'm just writing React still, it should be easy for other front-end folks to be able to jump to the app and probably implement a feature or two. So, also the same business logic was there between three projects, which is our website, our mobile site, and our app. And it was being written thrice, right? So, how do we fix that? So, what we did was we shifted all our front-end projects including our app to our monorep. And we now follow the same LinkedIn guidelines. And we, you know, some code is shared now through some common utilities and some common service functions. And there's even a possibility now to share business logic. This is what our front-end monorepo looks like and that's the mobile app. Not sure who came up with the Transformers naming convention. Then I talk about improper app versions. Now, the app was, the way the app was versioned at the time was really bad. Even if something was a feature, it sometimes was released as a patch. It was just really annoying. And our commit messages weren't doing enough. While the developers, they did try their best to write the commit messages properly. Something was still missing. And, you know, as you know, React Native supports on the air updates, right? Which basically means that you can manually release a JavaScript bundle, your app, which will, which your app will download and apply it on runtime. The most popular way of doing this is using code push, which is an open source project by Microsoft. But you cannot code push any native changes. So, with React Native, you can also write some native modules, right? You cannot code push anything which is with respect to that. So, how safe does this look? It says code push, release, React, Vivo, Android, deployment name, production, target binary version 2.3.0 to 2.3.8. Rollout 100%. How do I know, without any proper versioning, that there were no native changes which were released as patches? There were no features that were released as patches, between 2.3.0 to 2.3.0. So, to become much more aware of what was happening and to be much more confident, we decided to follow a simple semantic versioning of major.minor.patch, which is something we were already doing. But now we decided to put some rules around that. Now, Commitize and Forget is an amazing software which helps name each commit properly. And so we started using that. Even though we still update our app versions manually, it is easy to automate this using the Commitize and Naming convention. Let's see how that looks. So, say I run a command, npm run commit, which internally runs a Git, a CZ, which is Git Commitizing. And now I have some things to select, right? Whether it's a test, whether it's a feature, whether it's a fix, so that this particular tag gets added before my commit message. And obviously, you can easily run a script on that, right? Because all your messages now follow the same guideline, you can easily write a script around this. So, if I select a feature, some script should run later to increase the minor version number. And at the moment, even for any native changes that we make in our app, we increase this particular number because it's just a convention that we follow so that we don't code push anything wrong. So, if I select fix, it should increase the patch version number. How safe does this look now? With all those native changes not being code pushed anymore, I will be only code pushing a patch in this particular code push. So, it's the same exact command, but I'm much more confident about it. Confusing project structure. So, earlier we used to follow a type-based structure. And this is a project structure you get out of the box in most of the boilerplate projects out there. It's a very good project structure. It aims to solve a very nice thing that, you know, everything which is a container will be in a container folder. Everything which is a component will be in a component folder. Everything which is related to Redux will be in a Redux folder. Similarly for styles and assets. But this made it difficult for us to plug and play features. Now we are a product-based company. We sell hotels, right? We sell room nights at hotels. So, if I'm, you know, releasing a feature which is, you know, to get more users, I'm adding a wallet. So, something like a wallet. Let's see how that looks like in this project structure. You have your assets which is, in images you have wallet image. Containers you have your wallet page which has all the other components with that. In Redux you have something called a wallet duck. A wallet duck is basically just actions and reducers together, which are related to wallet. And then in services you have wallet service. I mean, how about we move all of these to one single folder called wallet? This is the question we ask to ourselves. So, that's where we came up with the project structure that we use today. We now use a feature-based project structure. And it makes easy for us to plug and play features. And the context of the features belong to one single folder now. Now it's also much easier for us to, you know, search for code snippets and locate files which are just related to wallet. So this is what it will look like now. You have a wallet. You have some components inside a wallet. You have a wallet image, a wallet duck, a wallet service and every other file which is related to wallet. Then you have user pricing, booking, hotel navigation. Everything is just a feature. Even if I talk about navigation, it's just a feature at the end of the day that my app follows. So earlier it was sort of like this. Assets, components, conflict containers. On the left you see how it was like before. Then in containers you have all your pages. You have Redux and services. But now it looks sort of like this. So if I added AB testing, it will go to the AB folder. Something related to analytics goes to analytics. And obviously this is still being refactored. It's still in the works, but you get my point. Then in unhealthy code, I'll be talking about unsystematic components. Over time as we progress, we noticed how unsystematic our components were. Even between our react-based front-end projects, code would look extremely different. There was no single pattern being followed, which would say, make my app portal page look similar to the mobile site portal page. Why did this have to be a case? At the end of the day, it's just react that I'm writing. To bring in some system and how we write components in our applications, we decided to invest in a component library. And I think everyone should do that. They should have even a components folder where you get your components from. Have your abstractions ready. In our case, we call it leaf UI. And we set some ground rules. This is where all the basic components are exported from. We import everything from leaf UI and we rarely write styles anymore. I'll show you how we do that. Instead of doing that, we use something called enhancer components. And these enhancers simply pass down styles as props to their children. Take a look at this example. You see a screen which has a center-aligned card. Card has two components, a tag and some text. Notice the code from top to bottom. Flex enhancer passes down some flex properties to a view which makes your full screen. Then flex once again passes down some flex properties to a card which is elevated. And it renders a tag and a text in a row. Now the space enhancer takes care of the padding and margin. Numbers that pass down to it are multiplied by eight and it takes an array. So if you see space margin over here, see over here, space margin 0, 2, 0, 0. That's basically top, right, bottom, left. That's being multiplied by eight. So I said that to the right side, apply a 16 margin. Other than flex and space, we have enhancers for size and position. This kind of a change obviously takes time. You cannot do this thing overnight. When you decide to set up some ground rules, it will take some time. Now my code base is like my room. It gets dirty. I clean it gets dirty again and I clean it again. That's because it's my room and I love it anyway. So what I mean to say here is that we are always refactoring and we don't ask for extra time to refactor. It's just a part of our daily life right now. So if I'm working on say a hotel page, I will make sure that I refactor that as well as per the new leaf UI guidelines that we have in place. The second mistake I'll be talking about is bad developer experience. So I am able to speak to you over here today in this talk is just because how confident I am about my releases. I now rarely have to give any production support and the practices I'll be talking about here are what gave me a work life balance. Let's get started with point A, bad local debugging experience. So when I joined, I noticed that there was no proper tools. There were no proper tools for debugging in place. And myself was rather new to quality tools for react native. I used to mainly rely on console statements. And at the time the local debug flow in general at Revo heavily used Chrome developer tools and console statements. And there was no information regarding Redux actions, API calls, local storage, among many other things. So fixing this was really easy. I just had to Google to find the right tools. And locally, there are two amazing tools available that I use personally. It's react native debugger and reactor. Both of these are open source. And they give proper feedback around API calls, Redux actions, local storage and a lot more things which are just related to react native. So debugging locally has become a breeze now. And they're both awesome. And I would suggest that you use both of them. So this is what reactor drawn looks like in its timeline. What is how beautifully displays all the actions. So on top of where it says get landing page content, then you have some other Redux action, then some async storage actions that took place. And if you decide to filter this timeline, you'll see all the features, other features that it gives you. So log image, custom display, connection, benchmark, API, mutations, actions, and even other things. But I personally don't use reactor drawn that much because of this. Now this is called react native debugger. This is again an open source project. And this is something that I use every day. It gives me a combination of Chrome developer tools, react developer tools, Redux developer tools, and certain features which are specific to react native. So say for example, if I have to make my internet slow, I have to test my app on slow internet. I just use this tool like I would use Chrome to do that. Basically set it on 2G or 3G connection. And on the right side, you see the API calls properly similarly similar to Chrome. And on the left side, you see the Redux actions. So it's just too easy to debug locally. But the nightmare for me was production debug. Since the beginning, we have used bug snack to track our crashes. But our production crash reports on bug snack, they were paralyzed. I tried to upload react native source maps to bug snack, but the result just wouldn't help me enough. Even after uploading the source map, the stack based bug snack would point would give me used to point to a different line. I mean, imagine you have, you know, undefined is not an object. That's your error statement. And the line it is pointing to is some import statement in some library. That is something that used to happen. But so, you know, I stopped, uh, stopped uploading these source maps and the report would now show me, uh, obfuscated code. A line number in the obfuscated JavaScript bundle. Luckily for me, we were using test IDs and accessibility, accessibility label. Most of the components. So this is how I used to debug at the time. You know, uh, I would get the line number pointed by the bug pointed by bug snack and manually find it in the JavaScript bundle. I would then copy this line of thousands of characters to a new file and then carefully go through it. I would pray for something like a test ID or accessibility label, which wasn't obfuscated. I would then spend a few hours trying to reproduce the crash using all this information. But they had to be a better way. So this one time I got so pissed. I decided to post this roses are red. My app is red and handle JS exception type error and defined is not an object. This is what my life was at the time. But, you know, as it turns out, bug snag is awesome. It's a brilliant platform for tracking crashes. Why it didn't work properly for us before was because of my own point. As I said earlier, source maps weren't working correctly, right? But it turned out that my builds and source maps weren't clean. Clean builds, they deserve clean source maps. So after setting up our CI CDs, I basically added a source map upload script, which runs after every clean release. So we have a clean release. It deserves a clean source map. And so we also added some slack integrations on bug snag. And now each time there's a spike in crashes, we get some slack alerts. So take a look at this snippet from bug snag. It says on top, it says null is not an object evaluating E dot measure. And over here, you have some stack trace. Now, after the source map is working, it's telling me everything properly, evaluating E dot measure points to component dot measure, which is something that was crashing every once in a while. But wait, it said null is not an object. Let's go to the next slide. Go CI CD. This was another pain point. Now, even many months after I joined, we still were releasing to production from my own laptop. Imagine the amount of problems that could have caused them. The folder I was using to release was the same I used to work in. And you don't eat where you take a dump. Did I do a clean npm install? Did I clean Xcode or Android Studio before releasing? I think you understand what I'm trying to say. Now, we tried different CI CD solutions and we decided to stick with Bitrise because it gives Mac machines. Over time, we set up multiple workflows in it. An example is an idly bird. Here also, we get Slack alerts for everything, whether the build failed or whether it was successful and releases are also made using Bitrise. These are built on clean instances and so there's no chance of them being dirty. And Bucksnack source maps are also uploaded from here, as I mentioned. So this is what my app dashboard on Bitrise looks like. On the left side, you see proper builds that ran every single day. I talk about no test automation. In general, I'm a sole developer for the app at Revo. So I don't have time to write unit tests or any kind of test for that matter, but we did have some QA bandwidth. So let's see what we did. Now, all releases that we were making at the time were being tested manually. And due to human error, sometimes test cases could get missed. We also came to realize over time that certain bugs were device-specific and all this uncertainty was hurting us. Because of all this, we decided to invest in building a test automation suite on Amazon device map. A colleague of mine from the QA team wrote down a bunch of test cases of four flows in app. For example, the booking flow. You go from your home page to your information page to booking. Everything's there. And they achieved that by using test IDs and accessibility labels because those are what point to that particular component so that you can write automation on that. Now, nightly builds on bitries are followed by multiple device regression. There's high predictability in app stability, which has resulted in very good business. And this has also freed up some QA resources for better work because you cannot just expect them to keep testing everything manually. Now, they're writing test cases and they often do better things. So let's see what this multiple regression I talked about looks like. You notice six different devices on Amazon device firm, which ran about 20 different test cases. And you can see how for every different device they were failing. This could very well be some UI mistake that we made because devices have different screen densities. Maybe some component was overflowing, wasn't rendering properly. Something happened. Maybe even the API is crashed. You don't know what this meant at the time. So let's go to the next slide. And I'll be talking about some performance practices. So three things, bad school performance, overdraws in Android and poor APM is app performance monitoring. React Native has its own performance issues. It does. But you know, if you follow the correct practices, you don't have anything to worry about. It gives you a very nice platform to build amazing mobile apps. But you cannot keep on blaming React Native or whatever problems it has if you don't take a step to improve your app performance. So let's go through these one by one. And the issue that we faced back then and to some extent we still face today was the highly janky scroll experience we had on the home page and search page of the app. After some digging in, after some spikes, we were able to find some factors which were at least visible to us. Now, we were using a library to render SVGs, which internally used web views. Since React Native doesn't support SVGs out of the box and we were fetching these over the web, this seemed like a simple solution, right? The card component which was being listed out on the search page, it was rather heavy and didn't follow proper, you know, warning signals that React Native gives us. And the flat list wasn't well optimized. We hadn't read the documentation around flat list properly to be able to put some optimizations which helped you a lot really. And the animations on these pages were poorly implemented. Wait a minute. I said web views rendering SVGs. Let that sink in, okay? This is probably the most embarrassing thing that we ever did. So you see those scroll bars on top, right? On that particular component. Those are SVGs. And that's a web view. And on Android, you cannot even get rid of those scroll bars. It's probably still an open issue somewhere. You just cannot get rid of that on Android. So for a better scroll performance, we got rid of these web view-based SVGs. And now we use PNGs. And we simplified the flat list cards and removed some extra animations which were there in them. We added some simple to implement optimizations to flat list which are clearly mentioned in, you know, react-native documentations. But we still haven't done, you know, enough gestures to the animations. And we might just remove them for maybe a better and easier to implement UI. While I spoke about all these visible factors, there was also something really big, you know, a big invisible factor which was in the play. I'm talking about overdraws in Android. It's a major performance issue. It's not specific to react-native. Open any app in your phone, and I'll show you how to debug overdraws. It means that a single pixel is being rendered multiple times in a single frame of rendering. It happens when two components overlap. And it happens primarily when, you know, there are two different color components which are overlapping. So fixes for these overdraws, they can be too easy to be true. And they have a very high return on investment. Basically, just don't overlap your components. Don't overlap your background colors. You have to overlap your components. Don't overlap your background colors wherever you, you know, can do that. Take a look at this example. This is, say, for example, a root of any app. It has, it is mounting root navigation inside some view which is a container. Now that has some styles on top which says flex is equal to one. Flex is one and background color is white. But don't do this if possible because you can always write a background color at some point later. So how do you debug this GP overdraw? You go to your developer options, you select debug GP overdraw and you say show overdraw areas. Now there is some color convention which is followed here. True color, I mean no overdraw. Blue overdrawn one time. Green overdrawn twice. Pink overdrawn three times. And red overdrawn four or more times. This is what our overdraws used to look like back then. And I started debugging this. This is what it looks like right now after the app looks exactly the same. There are few places where, you know, I could have used some extra background color, but it doesn't impact the usability of the app at all. This clearly still room for improvement. Say, for example, in the hotel space, last screenshot is from the hotel space. Still room for improvement, but we have, we have reached we have crossed a long way here. So I also decided to do some performance profiling of our older app version before we did all these performance improvements. This is the profile of the user opening the app, selecting a city, scrolling, going to a hotel, booking it, the whole major flow that you would expect the user to do. This is what the profile looked like. That's, you can clearly see the CPU and memory usage. Whatever the numbers are. I mean, I didn't go into detail as to what percentage this thing denotes. I just decided to do a performance profile of the newer version. This is what it looks like. So profiling GPU rendering. How do you profile that GPU rendering? You go to developer options. Select on screen as bars. Let's take a look at these demos. So this is our older app. And just let that sink in. This is our page and I go to my search page and I was talking about janky scroll experience. Do you see those bars crossing the roof of the phone? This is how bad it was at the time. So let's take a look at the GPU rendering profile at this point. So not much of a difference on the home page or landing page. You still see more or less a similar graph. But wait till you go to search page. Much better, right? It's at least far more usable now. It wasn't even usable before. And we must have lost a lot of business just because of our performance practices at the time. This is nothing going through the roof exactly. So last thing I'll be talking about is poor app performance monitoring. We didn't have any statistics to measure apps' performance in production. This would make it difficult for us to convince business to implement performance improvements in general. There was no way that we could relate these kind of performance improvements with conversion improvements. And there was no way to tell whether the app was slow or whether APIs were slow. And we were already using New Relic for all of our other apps. So we decided to integrate their Android SDK with the app along with the native module, which we built to trigger it from React Native. We connected our pre-existing analytics events for which we used segment for that. So segment analytics events also now connect with New Relic so that we now get some performance metrics in place. We can now relate analytics events and performance and we then prepared some relevant dashboards and we now have some well-defined benchmarks. So I'll go through that. You know, this is part of the dashboard. All these shots I'll be showing are part of the dashboard. So this shows crash rate by version, which is already there on Google Play, Google Play console. But it's good to see it all in one place so that you can track your releases properly. Then you have, again, crashes. Another graph for that. Then you have number of users by version. And then you have number of users by OS version on Android. Now, here you see four different dashboards, four different tables. ATFI, it stands for time-to-first interactive. It means the amount of time it takes for something to become interactable. You know, how often, how soon can a user start using that page? And this dashboard, it shows exactly that for both Wi-Fi and cellular on both our search results page and hotel details page. And this can directly impact user conversions if the numbers are too high, right? So if it's taking more than 30 seconds on a particular page for a page to become interactive, that's a really bad warning sign. And this is where we had some benchmarks in place and this is what we strive to improve over time. And you have prepared bookings day-wise. Can't show you the numbers obviously here. Then you have error rate by domain. So API crashes. Something went wrong in the API. Were there any 500s there? So you get to know all of that. Then prepared bookings by app version. Now, since this directly comes from segment, I can even track my iOS app here, which is something we'll be working on at some point. So finally, the conclusion of my presentation. We have an updated stack. We have much less dependency on third-party libraries. You have a great debugging flow. You have a proper CI CD setup. You have automated regression testing. WordPress is thriving in the Monorepo. Great component library in the name of Leaf UI. And easy to read components. You have a much better navigation experience, much better performance in the app, greater understanding of the native side. And now we actively monitor performance. But we still have a long way to go. But where we wish to see React Native is, you know, we'll continue to use React Native, no matter what. I absolutely love it. So as long as I'm there, I'll be using React Native for sure. So we wish there was some great performance out of the box. Maybe the newer architecture of React Native will solve that for us. And I wish there were some more warnings on preventing overdraws in Android. And more warnings on preventing some general bad practices which I noticed were wrong in how you write opponents in the first place. And it should be easier to upgrade. I spent about 25 days to upgrade to a newer version of React Native. Meet me in the cafeteria or somewhere outside. I'll tell you how we did that too. And improved animated performance. And an easy to implement gesture system. I don't know how to use the gesture system even today. So I done it. I think we all deserve a cake. That was a long presentation. Thank you, Krishna.