重点(Top highlight)
I had to check Medium, because I felt like perhaps I wrote a draft of this story 15 months ago. That was the last time I thought “perhaps we should rebuild our app in React Native.” That lasted a few weeks as I made the shell of an app, discovered various roadblocks and got a sense for how hard the overall task would be. It did not reach activation energy then. There were many reasons for that, both from a maturity standpoint in React Native and the circumstances at GasBuddy. We still retained the majority of the team that built our native apps from the ground up more than a decade ago. Most of our iteration was backend heavy — we built a payment product that sold a half a billion dollars in fuel and we integrated telematics and vehicle recall notifications and such. Having to build the front end to those features twice (iOS+Android) and sometimes thrice (iOS+Android+Web) was annoying, but not prohibitive.
我不得不检查中,因为我觉得也许15个月前我写了这个故事的草稿。 那是我最后一次想到“也许我们应该在React Native中重建我们的应用程序”。 当我制作一个应用程序外壳时,这持续了几个星期,发现了各种障碍,并对整体任务的艰辛感有所了解。 那时它没有达到活化能。 出于多种原因,无论是从React Native的成熟度角度还是GasBuddy的情况来看。 十多年前,我们仍然保留了从头开始构建本机应用程序的大多数团队。 我们的大部分迭代工作都是在后端进行繁重的工作-我们开发了一种付款产品,该产品售出了十亿美元的燃料,并且集成了远程信息处理和车辆召回通知等。 不得不两次构建这些功能的前端(iOS + Android),有时又要构建三次(iOS + Android + Web),这很烦人,但并不是禁止的。
On the React Native side, there were several big blockers that made me less than excited about the long road to feature parity with the existing apps.
在React Native方面,有几大阻碍因素使我对与现有应用程序实现同等功能的漫长道路感到不满意。
- Navigation was messy. react-native-navigation seemed the most mature option, but it was incredibly finicky to get working, and as new version of React Native came out, even in the short months I spent dabbling, I had to do those finicky things all over again. 导航很混乱。 react-native-navigation似乎是最成熟的选择,但是它上班的难度令人难以置信,而且随着新版本的React Native的出现,即使在短短的几个月里,我也不得不重新做那些挑剔的事情。
- Image handling was daunting — SVG is clearly the right answer for source material, but it seemed to be poorly integrated into the workflow of RN apps. That may have just been my lack of understanding, but it was a concern for large scale development. 图像处理令人生畏— SVG显然是获取原始资料的正确答案,但似乎没有很好地集成到RN应用程序的工作流程中。 那可能只是我缺乏理解,但这是大规模开发的一个问题。
- Native modules were a complete pain. Anyone working with RN before automatic linking would likely agree that it was messy — modifying pod files, native code on both platforms, managing version clashes — it was just drudge work. 99% of that pain is just plain gone. 本机模块是一个完全的痛苦。 在自动链接之前使用RN的任何人都可能会认为它很杂乱—修改pod文件,两个平台上的本机代码,管理版本冲突–只是费力的工作。 那种痛苦的99%只是消失了。
Additionally, it was about 6 months after the infamous AirBnB middle finger to React Native. I think the articles were quite balanced and honest, but every time someone wants to bash RN, these articles are point number 1. I feel that misses some key nuances. First, a great many of the problems AirBnB had have in fact been addressed. They were a victim of timing, and their loss and pain are the rest of the ecosystem’s gain. Second, GasBuddy is not AirBnB and if you’re at a technology company considering React Native, you probably aren’t either. Their native app teams are probably bigger than our entire company. Their app is known for leading edge design, animation and visual polish that is hard to achieve with anything between you and the hardware.
此外,在臭名昭著的AirBnB中指使用React Native大约6个月后。 我认为这些文章非常均衡和诚实,但是每次有人要抨击RN时,这些文章都是第一点。我觉得这遗漏了一些关键的细微差别。 首先,AirBnB实际上已经解决了许多问题。 他们是时间的受害者,他们的损失和痛苦是生态系统获得收益的其余部分。 其次,GasBuddy不是AirBnB,如果您在考虑使用React Native的技术公司,那么您可能也不是。 他们的本机应用程序团队可能比我们整个公司都要大。 他们的应用程序以领先的设计,动画和视觉修饰而闻名,而您与硬件之间的任何事情都很难实现。
So, fast forward 15 months and a variety of changes made me wonder whether it was time to take another look. I started out the same way as before, but with the learnings of the past I had some demands of the code:
因此,快进15个月以及各种变化使我想知道是否该该换个角度了。 我以与以前相同的方式开始,但是随着过去的学习,我对代码有了一些要求:
- Monorepo. Because of the pain I had dealing with React Native version changes, I wanted as little code in the “app” as possible. This is of course a good Javascript discipline in general — small easily consumable modules. Javascript has infrastructure for exactly this scenario, via Lerna and yarn workspaces, but it is notoriously finicky with React Native. I wanted to solve this from the start. Monorepo。 由于我在处理React Native版本更改时遇到的痛苦,我希望在“应用程序”中尽可能少的代码。 一般来说,这当然是一门好的Java学科-小型易耗模块。 Javascript通过Lerna和yarn工作区为这种情况提供了基础设施,但众所周知,它与React Native的关系很复杂。 我想从一开始就解决这个问题。
Death to Redux. I do not like Redux. I do not like repeating myself. I do not like strings as identifiers in code. I do not like four files for every single thing the app needs to do. In the React world, I prefer unstated. I initially expected to use unstated-next but eventually decided on mobx-state-tree even though it’s a little heavy handed.
Redux之死。 我不喜欢Redux。 我不喜欢重复自己。 我不喜欢字符串作为代码中的标识符。 我不喜欢应用程序需要做的每件事的四个文件。 在React世界中,我更喜欢unstated 。 我起初希望使用unstated -next,但最终决定使用mobx-state-tree,即使它有点笨拙。
Typescript. This wasn’t a must have at the beginning, but people I respect had recently started a React Native project and used Typescript, so I figured we’d give it a try. In retrospect, it’s been instrumental in making a pleasant and fast developer experience, mostly because of its impact on Intellisense in Visual Studio Code. Getting some veil of type safety for free was a nice addition. This is another place where time has helped a great deal. The number of RN-related modules that have Typescript support now is significantly higher than what I recall from the last attempt.
打字稿。 这不是一个必须的开头,但人我尊重最近开始了一个阵营本地项目和使用的打字稿,所以我想我们会试试看。 回想起来,它对获得愉快和快速的开发人员体验很有帮助,主要是因为它对Visual Studio Code中的Intellisense有影响。 免费获得一些类型安全的面纱是一个不错的选择。 这是时间帮助很大的另一个地方。 现在,具有Typescript支持的RN相关模块的数量大大高于我上次尝试的数量。
- react-navigation. It has matured a great deal over the year, performance appears on par with native navigation on modern devices, and there is no fuss. The declarative model seems a great fit for modern React and works smoothly with hooks and contexts and Storybook and all such things. React导航。 它在过去一年中已经非常成熟,其性能与现代设备上的本机导航相当,并且没有大惊小怪。 声明性模型似乎非常适合现代React,并且可以与钩子,上下文和Storybook等所有东西一起顺利使用。
Perhaps as importantly, our circumstances have changed. We are focusing more on front end iterations like “search along route” and in-app personalized gas discounts and now spend a high percentage of our time building the same things twice. We are also just experimenting more often, trying to find the perfect copy or the best way to onboard casual app downloaders into GasBuddy members and users of our Pay with GasBuddy product. We’ve built a bunch of server side infrastructure to enable that experimentation, but honestly a bunch of it is just bending to the realities of upgrading mobile applications. Some of those original developers have also moved on to other opportunities, and as it was several of their first jobs in industry, I can’t really fault them for that.
也许同样重要的是,我们的情况已经改变。 我们将更多的精力放在前端迭代上,例如“沿路线搜索”和应用程序内个性化的汽油折扣,现在,我们花费了很大一部分时间来两次构建相同的东西。 我们也只是在做更多的尝试,试图找到完美的副本或将休闲应用程序下载器引入GasBuddy会员和我们的Pay with GasBuddy产品用户的最佳途径。 我们已经建立了许多服务器端基础架构来支持该实验,但是老实说,其中的一部分只是针对升级移动应用程序的现实。 这些原始开发人员中的一些人还转移到了其他机会上,由于这是他们在行业中的第一份工作,因此我不能对此感到真正的错。
Of course all code eventually needs to die. We’ve made lots of changes — to Swift and Kotlin, to a new service layer, etc — but there is no denying that there are plenty of cobwebs in our codebase. Dark corners that everyone is afraid to touch or has no clue what they even do anymore. By the way, that turns out to be one of the biggest determinants of migration speed in React Native: our ability to find documentation or even descriptions of why certain things do what they do. How is login supposed to work when you use a Google sign in to an account for which you no longer have access to the email address? How does the current search persist as your location changes if you had been on some sort of search along a route? The list is endless and each of them takes nontrivial time to reverse engineer from the code. Often the two platforms don’t even take the same approach, and I suppose that’s no surprise since the code bases are entirely separate.
当然,所有代码最终都将死掉。 我们进行了很多更改-对Swift和Kotlin进行了更改,对新的服务层进行了更改-但无可否认,我们的代码库中有很多蜘蛛网。 每个人都不敢触摸的黑暗角落,或者甚至不知道他们现在做什么。 顺便说一下,事实证明,这是React Native迁移速度的最大决定因素之一:我们能够找到文档或什至描述某些事情为什么要做的描述。 当您使用Google登录无法再使用其电子邮件地址的帐户时,登录应该如何工作? 如果您沿路线进行过某种搜索,当前搜索如何随着位置的变化而持续存在? 列表是无止境的,并且每个人都花了很短的时间来对代码进行反向工程。 通常这两个平台甚至都不采用相同的方法,我想这并不奇怪,因为代码库是完全分开的。
As we began this second attempt at React Native, I felt it was important to have some experts available to guide us, and got a recommendation from a friend for Infinite Red. In addition to just extra horsepower that didn’t detract from the existing roadmap, they helped us avoid a great many pitfalls, while still allowing us to have our own opinions and explore some new ground for both of us. React Native has been a fast moving platform since inception, and experience is extremely valuable and best when loosely held, and they’ve done a very good job of balancing that in our case. We have made great use of a shared Slack channel, and I would argue that channel has been the most “irreplaceable” part of our engagement with Infinite Red. They’ve done a tremendous amount of “real work,” but in terms of figuring out whether React Native is a realistic option for GasBuddy, the thousands of random questions and explorations we’ve done in that channel have been much more valuable. I think it’s fair to say there are innumerable blind alleys left in a production high-visibility React Native development effort, and it is crucial do avoid those which have deep pits at the end.
当我们开始在React Native上进行第二次尝试时,我感到很重要的是要有一些专家来指导我们,并从一位朋友那里获得了Infinite Red的推荐。 除了提供不超出现有路线图的强大功能外,它们还帮助我们避免了很多陷阱,同时仍使我们有自己的见解并为我们俩开拓了新的天地。 自从成立以来,React Native一直是一个快速发展的平台,经验极其宝贵,最好是在松散状态下保持最佳状态,在我们的案例中,他们做得很好,可以很好地平衡这一点。 我们充分利用了共享的Slack渠道,我认为该渠道是我们与Infinite Red合作最“不可替代”的部分。 他们已经做了大量的“实际工作”,但是就弄清React Native是否是GasBuddy的现实选择而言,我们在该渠道中完成的数千个随机问题和探索意义更大。 我认为可以公平地说,在生产高可见性的React Native开发工作中留下了无数的小巷,并且至关重要的是要避免最后出现深坑。
I made some big process decisions early that shaped our work in the first month.
我在第一个月就做出了一些重大的流程决策,这些决策影响了我们的工作。
- We were going to build from the ground up. We were not going to integrate some React Native screens into the existing app, even as a proof of concept. I believe many of the gains React Native may deliver will require not carrying the baggage of the existing apps. 我们要从头开始。 我们不打算将某些React Native屏幕集成到现有应用中,即使只是作为概念证明。 我相信,React Native可能带来的许多收益都将要求不承担现有应用程序的负担。
- We were going to replicate the existing app design as closely as possible. There are plenty of reasons to redesign our app, and you can argue that combining a redesign and a re-platform would be more efficient than doing them independently. Since we don’t have a completed redesign, I felt this just meant we would never finish the re-platforming. Our goals are around efficiency and flexibility, so the longer we delay sunsetting the existing apps, the longer we end up having to build each feature 3 times. Now, as a practical matter, if we know redesigns are coming to elements in the app long before we would be done with React Native, we will wait for those to be completed in the existing app before porting them to React Native. If and when we truly decide to go to React Native, I believe there will be redesigns that we choose to execute first on an unreleased React Native app, because we can user test more easily, perhaps even beta test more easily and flesh out all the non-mobile elements (supporting back end services, etc) ONCE on React Native and then just copy the design to the native apps without having to wait for any supporting changes. 我们将尽可能地复制现有的应用程序设计。 有很多原因可以重新设计我们的应用程序,并且您可以辩称,将重新设计和重新平台结合起来比独立进行更有效。 由于我们还没有完成重新设计,所以我觉得这意味着我们永远不会完成重新平台设计。 我们的目标是围绕效率和灵活性,因此延迟淘汰现有应用程序的时间越长,最终每次构建3次功能的时间就越长。 现在,实际上,如果我们知道重新设计要早于应用本机中的元素,那么在将它们移植到React Native之前,我们将等待现有应用中的重新设计。 如果并且当我们真正决定使用React Native时,我相信将会有一些重新设计,我们选择先在未发布的React Native应用上执行,因为我们可以使用户更容易地进行测试,甚至可以更容易地进行beta测试并充实所有非移动元素(支持后端服务等),只需在React Native上执行一次操作,然后将设计复制到本机应用即可,而不必等待任何支持性更改。
Given those decisions, the Infinite Red developers and I set about building as much of the app as we could in a month. We made it pretty far — navigation, registration and login (simplified), service interfaces, state management, basic location management, gas station listings… All of these worked well enough to fool you into thinking they were done. They looked close to pixel equivalent to the native apps. They performed well enough. The code was maintainable, clear, and predictable. We had some basic tests, CI/CD, style guides, setup scripts… It was a real project. We decided it was time to share it with the broader teams.
鉴于这些决定,我和Infinite Red开发人员开始着手在一个月内构建尽可能多的应用程序。 我们已经走到了很远–导航,注册和登录(简化),服务界面,状态管理,基本位置管理,加油站清单……所有这些都运转良好,足以使您以为自己已经完成。 它们看起来接近与本机应用程序等效的像素。 他们表现很好。 该代码是可维护的,清晰的和可预测的。 我们进行了一些基本测试,CI / CD,样式指南,设置脚本……这是一个真实的项目。 我们认为是时候与更广泛的团队分享它了。
The #1 thing that would kill React Native at GasBuddy would be our engineering team deciding it wasn’t worth the effort, or wasn’t the right long term decision. We made a pretty good demo with some elbow grease and experienced help from Infinite Red. The work we did was not throw away work — the code is a solid and real foundation for a full blown app. It would be naive to say, however, that we were 25% done, or even 10% done. So the idea that we could get to 100% without meaningful involvement from the existing team, not to mention the horrible position that would leave us in at the end, is wrong. It will likely take us 5–6 months with 4–5 resources at the least to have a fully working app. So we need coverage on the existing apps in that time, and we will need to reduce our overall output to accommodate direct and indirect work on the React Native app. It is eminently reasonable to think this is too high a price to pay, and even more so if you aren’t convinced of the benefits or the likelihood of success.
在GasBuddy杀死React Native的第一件事是我们的工程团队认为这不值得付出努力,或者这不是正确的长期决定。 我们用了一些肘部润滑脂做了一个很好的演示,并得到了Infinite Red的帮助。 我们所做的工作并不是放弃工作-代码是功能完善的应用程序的坚实基础。 然而,天真地说我们完成了25%,甚至完成了10%。 因此,我们可以在没有现有团队有意义参与的情况下达到100%的想法是错误的,更不用说最终会使我们陷入困境的可怕位置了。 拥有完全可用的应用程序,至少需要4-6个资源才能花费我们5-6个月。 因此,我们需要在那时覆盖现有应用程序,并且我们需要减少整体输出以适应React Native应用程序上的直接和间接工作。 认为这是要付出的代价太高了,这是绝对合理的,如果您不相信收益或成功的可能性,则更是如此。
So we did a big demo, described the merits of React Native, and etc. In general I think it’s fair to say people were impressed at the progress in a short time with a few people. But our team pointed out a very obvious thing that hadn’t really occurred to me until then — they don’t even have time to EVALUATE whether React Native is a good idea or not. They’ve got 70 points to finish every sprint, and this would take a bunch of them. I imagine the response at most companies of our size would be the same. We decided that what might be most helpful is if we could all focus on React Native for some short period, and “React Native Week” was born. It took some time to convince the rest of the company that a 1 week “work freeze” was worth it. We came close to cancelling the whole damn thing. But we decided it was now or never.
因此,我们做了一个大型演示,描述了React Native的优点,等等。总的来说,我认为可以说,在短时间内与几个人一起给人们留下了深刻的印象。 但是我们的团队指出了直到那时我才真正想到的非常明显的事情-他们甚至没有时间评估React Native是否是一个好主意。 他们获得70分才能完成每次冲刺,而这需要一堆。 我想我们这个规模的大多数公司的React都是一样的。 我们认为最有用的是,如果我们都可以在短时间内专注于React Native,那么“ React Native Week”就诞生了。 花了一些时间才能说服公司的其他成员,为期一周的“冻结工作”是值得的。 我们几乎要取消整个该死的事情。 但是我们认为现在或永远不会。
I am finishing this article at the end of React Native Week. Before it started, we spent time cleaning up the React Native build process and simplifying code where possible, and we created dozens of github issues for simple changes that would be useful for novice React Native developers to take on. The first day we did a little “firehose” spraying covering the basics of modern Javascript, React, and tooling and we concluded the day making sure everyone had a build up and running. Day 2 was pretty quiet, and I was concerned maybe it wasn’t going well or that there wasn’t enough training to make sense of an already fairly large code base. By the end of the day, the pull requests starting coming in, and they were much better quality than I expected. They provided great opportunities to discuss approaches, highlight CI/CD deficiencies and talk about why some structural decisions were made.
我将在React Native Week结束时结束本文。 在开始之前,我们花时间清理了React Native的构建过程,并在可能的情况下简化了代码,并且我们创建了数十个github问题来进行简单的更改,这些内容对于新手React Native开发人员来说非常有用。 第一天,我们进行了一些“ firehose”喷洒,覆盖了现代Javascript,React和工具的基础,我们结束了这一天,以确保每个人都已构建并运行。 第2天非常安静,我担心也许进展不顺利,或者没有足够的培训来理解已经相当大的代码库。 到今天结束时,拉动请求开始出现,它们的质量比我预期的要好得多。 他们提供了很好的机会来讨论方法,强调CI / CD的不足以及谈论为什么要做出一些结构性决策。
I plan to keep updating this article as we make a final decision. If we choose to move to React Native, I plan for us to do it “in the open” as much as possible so perhaps our experiences help someone else. While I don’t think we are likely to open source the GasBuddy app, whatever modules that we can isolate that might be generally useful we will open source from the start. We’ve got 3 so far, hopefully of some use to someone!
在我们做出最终决定时,我计划继续更新本文。 如果我们选择迁移到React Native,我计划尽可能“公开”进行操作,因此我们的经验也许会帮助其他人。 虽然我认为我们不太可能开源GasBuddy应用程序,但是我们可以隔离的模块通常可能有用的任何东西,我们都将从一开始就开源。 到目前为止,我们有3个,希望对某人有用!
React本机图像生成器 (react-native-image-builder)
We needed a workflow that accommodated SVG and PNG inputs and produced easily usable components. In the case of SVG, it needed to convert them to Typescript-safe components and for PNGs I’d prefer to store one image in the repo and generate the other resolutions automatically in the common cases. I did not find tooling that let us do all these things, so we wrote some. react-native-image-builder takes a directory of image files and produces typescript files and scaled PNGs in a way that can be built and consumed as a module in a monorepo.
我们需要一个能够容纳SVG和PNG输入并产生易于使用的组件的工作流。 对于SVG,需要将它们转换为Typescript安全的组件,对于PNG,我希望将一个图像存储在存储库中,并在通常情况下自动生成其他分辨率。 我没有找到可以让我们做所有这些事情的工具,所以我们写了一些。 react-native-image-builder获取图像文件目录,并以可在monorepo中作为模块进行构建和使用的方式生成打字稿文件和缩放的PNG。
React本机字符串 (react-native-strings)
It’s been a long source of embarrassment that the GasBuddy app is only available in English. It is not purely a client side problem (not to mention marketing emails and the like), but it is predominantly one. We wanted a simple, typesafe mechanism for defining localized, templated strings. We decided on a yaml format and open sourced the react-native-strings module that converts templated string specifications into Typescript and then runtime values. We will also be extending this to allow runtime overrides of strings from the server, even though this is marginally useful once you have CodePush and dynamic updates of the whole bundle.
长期以来,GasBuddy应用程序仅以英语提供。 这不是纯粹的客户端问题(更不用说市场营销电子邮件之类的问题了),但主要是一个问题。 我们需要一种简单的类型安全机制来定义本地化的模板化字符串。 我们决定使用Yaml格式,并开源了react-native-strings模块,该模块将模板化的字符串规范转换为Typescript,然后转换为运行时值。 我们还将对此进行扩展,以允许服务器上的字符串在运行时进行覆盖,即使您具有CodePush和整个捆绑包的动态更新后,此功能也将非常有用。
React导航代码 (react-navigation-codegen)
The navigation stack of an app can get fairly complicated. Nested stacks and modals, tab navigators, top tabs… oh my. Each of the screens in this web has a name, and some have typed parameters. I found myself repeating the same strings and types multiple times for more complex scenarios and I didn’t like it. So you are probably detecting a theme from the previous two examples, and wedid the same — created an intermediate format where we specify the structure of the stacks, the parameters each screen takes and the names of the screens (with sensible defaults). The react-navigation-codegen module is part of the build script for a low level module (called infra in our case) and allows us to navigate to screens with full type safety and not have to write lots of painful type declarations or worry about complex cross-module dependencies on where these types and screen name values are defined.
应用程序的导航堆栈可能会变得相当复杂。 嵌套堆栈和模式,选项卡导航器,顶部选项卡……我的天哪。 该网络中的每个屏幕都有一个名称,有些屏幕具有键入的参数。 我发现自己在更复杂的场景中多次重复相同的字符串和类型,但我不喜欢它。 因此,您很可能从前两个示例中检测到一个主题,并进行了相同的操作-创建了一种中间格式,在该格式中,我们指定堆栈的结构,每个屏幕采用的参数以及屏幕名称(明智的默认设置)。 react-navigation-codegen模块是低级模块(在我们的情况下称为infra)的构建脚本的一部分,它使我们能够导航到具有完整类型安全性的屏幕,而不必编写许多痛苦的类型声明或担心复杂性跨模块依赖性,这些类型和屏幕名称值的定义位置。
陷阱(和解决方案) (Gotchas (and solutions))
For our setup, storybook and the app are separate “small” modules in the monorepo. We want to be able to simultaneously run the storybook bundler and the main app bundler, and thus have to set them to run on different ports. This turned out to be tricky to do cleanly. Running the bundler is easy, we modify package.json in the storybook app to be:
对于我们的设置,故事书和应用程序是monorepo中单独的“小”模块。 我们希望能够同时运行Storybook捆绑程序和主应用程序捆绑程序,因此必须将它们设置为在不同的端口上运行。 事实证明,这样做很棘手。 运行捆绑程序很容易,我们将故事书应用中的package.json修改为:
"start": "react-native start --port 8082",
But it proved to be much harder to modify the iOS project to default to this port. We had to modify the post-install step for our storybook Podfile with this at the end of the main target section:
但是事实证明,将iOS项目修改为默认为该端口要困难得多。 我们必须在主要目标部分的末尾修改此故事书Podfile的安装后步骤:
post_install do |installer|
flipper_post_install(installer)
installer.pods_project.targets.each do |target|
if target.name == "React-Core"
target.build_configurations.each do |config|
if config.name != 'Release'
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= ['$(inherited)', 'RCT_METRO_PORT=8082']
end
end
end
end
end
That magic spell modifies the build config for the React-Core CocoaPod to set the default metro port. On Android, it was simpler once I knew where to put it. In gradle.properties in storybook/android, I added a single line:
该魔术咒语修改了React-Core CocoaPod的构建配置,以设置默认的城域端口。 在Android上,一旦我知道将其放置在哪里,它就会变得更加简单。 在storybook / android的gradle.properties中,我添加了一行:
reactNativeDevServerPort=8082
翻译自: https://medium.com/swlh/gasbuddy-entertains-react-native-5f4daa535990