Why & How Words With Friends Is Adopting React Native

Words With Friends Engineering
Zynga Engineering
Published in
28 min readSep 17, 2018

--

John Bacon | Principal Software Engineer

Brian Liang | Architect

Peter Turner | Principal Software Engineer

Introduction

App and game developers who work on frequently updated products are often tasked with keeping up with a rigorous roadmap of new features, growth initiatives, and tech hygiene tasks to enhance the satisfaction of users, improve product quality, and enhance the product’s impact on the business. We try to deliver as much of this roadmap as we can as quickly as possible because it’s an industry where consumer trends and what’s in vogue change rapidly. It’s also an industry that celebrates building for your current requirements and starting with the customer experience before working backward to the technology. These are best practices in the early days of a product, but what happens when the product is 1 year old, 2 years old, or even 10 years old? That’s the question we set out to answer on the Words With Friends engineering team in 2018.

In 2017, we were all hands on deck trying to launch Words With Friends 2, which was the most ambitious update to the franchise in its history. The game was well received, and helped our business in a big way, creating an opportunity to rethink how we wanted to allocate the team in 2018. We decided to spend some time in 2018 to see if we could make a step-function improvement to our technology that would allow us to deliver more to our players, faster, and without sacrificing quality.

Framing The Problem

Disconnecting from the Past

The first important task at hand was to develop a mindset and a framework for this initiative. We felt that it was important to understand, but also disconnect from the past in the early days of the initiative in order to be as objective as possible and keep all options on the table.

Words With Friends Engineering History

Words With Friends has always been a largely native app, authored in Objective-C and Java, with the core gameplay experience created with Cocos2dx. In 2009 when Words With Friends was first developed, mobile device hardware had a fraction of the power it has today, and small performance optimizations were extremely meaningful. The downside of this approach was that logic was largely duplicated, and the best tool for the job on either platform led to constantly bifurcating architecture, along with classes of bugs that were unique to each platform. Here is a high-level diagram of what the Words With Friends mobile client looked like coming into 2018:

Existing Words Client Structure

The Cross Platform Dream

Early cross platform frameworks were generally achieved by using a webview, where the efficiency gained in development speed and developer friendliness was significantly detrimental to the user experience. But, over the last 5 years, mobile hardware and software have come far enough along that cross platform, developer-friendly options such as Unity, Xamarin, and React Native have become viable alternatives to native development for apps that push the boundaries of user experience. The bad rep that multi-platform apps had from the early days of mobile was essential for us to discard in order to give new technologies a fair shake, especially in the game industry where cross platform development is the norm. Our gameboard was a good start and was already written in a cross platform fashion using C++ & Cocos2dx, but only accounts for about 15–20% of what we do on Words With Friends. Our goal was to be able to consolidate everything else into shared code as well. This was our engineering vision coming into 2018:

Words Client Vision

Creating a Rubric

In order to make the investigation fair and objective, we created a template to grade each option based on industry best practices for software evaluation including engineer usability, software stability, and interoperability, among others. We also had two guiding principles: whatever we chose had to improve code reusability and be able to be integrated into our existing codebase. A full simultaneous rewrite and re-release would be irresponsible due to the time it would take to do that before seeing any results and the risk it posed to hot-swap the live game with the ‘new’ one.

Picking a Solution

Exploring A Broad List of Technologies

We brainstormed and evaluated over a dozen technologies. Our original list included a wide range of options including HTML5, Unity, Djinni, J2ObjC, Kotlin/Kotlin Native, Cocos2dx, Xamarin, and React Native. We had two primary constraints we graded every technology on as pass/fail:

  • Improves Code Reusability
  • Applied Iteratively to Our Existing Codebase

We also letter graded all options across several different criteria:

  • Developer Usability
  • Development Efficiency
  • Codebase & Build Integration
  • Software Quality
  • User Usability

Each of these topics broke out into several subtopics such as documentation, community support, and performance impact to name a few. The grades were based largely on research and some proof of concept work.

Picking Semi-Finalists

Certain technologies fell off the list quickly. J2ObjC was not a great fit because of the emergence of Swift & Kotlin, and its limited ability to help share UI related code. Unity also fell off quickly because of its lack of simple interoperability with an already native app. We also wanted to retain the look and feel of the game, which would be too difficult to emulate with a web app, crossing HTML5 off the list. This left us with 4 semi-finalists, which we chose via a majority vote of our most senior engineers based on the research presented:

  • Cocos2dx — a game engine written in C++ & used for our gameboard
  • Djinni — a code generation tool that facilitates bridging native UI to shared C++ logic
  • Xamarin — a cross platform app framework based on C# and Mono
  • React Native — a cross platform app framework based on React

Cocos2dx was a natural semi-finalist because our gameboard was already written in it. Djinni could enable us to share all non-UI related code as C++ which our team was already familiar with from Cocos2dx development. Xamarin and React Native were idyllic on paper because they promised extremely high levels of code sharing, and still rendered native UI at runtime so the look and feel could remain the same. Xamarin was also exciting because Zynga uses Unity quite a lot, and both use Mono and C#. React Native had a clean integration into existing apps and was an emerging technology using the popular React library for writing native UI.

Picking Finalists

A large part of what we do on Words With Friends involves writing UI, and so Djinni quickly became a backup option because it did not offer a UI solution. We focused on going deeper on the other 3 semi-finalists. We built out complex UI to see if each technology could be applied to any feature in the game. We also evaluated and tried bridging to native code to see how easily we could integrate and iterate on embedded cross platform UI in the existing game.

Why Not Cocos2dx

With Cocos2dx, we ran into technical challenges that demonstrated why a game engine is not the best option to write optimized app-like UI such as long lists.

In game engines, the contents of the screen are redrawn each frame. That means each element on screen regenerates its pixels 60 times per second. If a frame takes longer than 1 / 60 = 16.6ms, there will be dropped frames. In UI engines (like UIKit), the framework only redraws elements as needed. Under the hood, these optimizations are largely abstracted away from the developer.

We also ran into issues with scrolling performance. On Android, touch movement events are received from the OS on the main UI thread, queued onto the GLThread, and then dispatched synchronously to cocos. The touch events are not synchronized with the rendering loop at all, so we end up with inconsistent times between touch events and between frames. Some frames have multiple events and some have none. The cocos ScrollView touch handling code does not account for smoothness or velocity between frames. This leads to jankiness. We also came up with solutions to these problems, but the effort required didn’t seem worth it with native UI alternatives on the table.

With Xamarin and React Native, these problems were avoided because their cross platform abstractions in C# and JavaScript are really just controlling native UI elements under the hood. Lists have smooth scrolling, and the UI benefits from all the optimizations of the native platform. We rebuilt our leaderboard using both technologies and achieved very high levels of performance. We were also able to integrate it into our existing game, proving out the feasibility of a piecemeal codebase conversion.

Finalists: Xamarin vs React Native

We were happy that we found 2 technologies that we could use to achieve our goals, but in the end, we had to choose one. In order to help make a decision, we went deeper on several topics we felt were important beyond the viability of the technology, such as industry momentum, career development for our team, and added complexity to workflows during the transition process. We developed a scorecard to rate the two technologies relatively, to see if a winner emerged. Below is that scorecard which reflects our opinion. If both technologies received an X, it meant we felt the technologies were roughly equivalent in that area.

Scorecard

We had to make a decision about whether we favored a more mature enterprise-grade toolchain that’s hard to integrate into an existing app, or a rapidly evolving and promising toolchain that cleanly integrates into an existing app.

Making A Decision: React Native

Ultimately we decided to go with React Native for the following reasons:

  • Integrating into existing apps is well supported and documented
  • Iteration tools such as live reloading are more mature, which speeds up development
  • Industry momentum and community support are strong; the technology is emerging

We were able to integrate a react native view into the existing app targets inside of a view controller or fragment without having to do much upfront modification to our codebase. Writing bridged code to handle user interactions inside of React Native that needed to be handled by our native code was also simple, especially with great community resources like this cheat sheet.

We also really loved the toolchain, along with the speed and lightweight nature of Visual Studio Code. Reloading code changes without needing to recompile is a great feature we really appreciated that increased our productivity a lot. React Native also brings us closer to the web ecosystem of technologies that are re-emerging in places like Facebook’s instant games, which we are investing in at Zynga. Features like being able to push over the air updates also stood out to us as opportunities where we could do our job as game developers better by being able to test and iterate more quickly.

There has been a lot of news lately with companies such as Airbnb sunsetting their use of React Native, and Facebook’s announcement of a large re-architecture. We see React Native’s current shortcomings as overcome-able challenges that we are excited to help push forward with the community, and these learnings have informed our adoption strategy, which we will talk about later in this article.

Why Not Xamarin

Xamarin doesn’t officially support integration into an existing app, although some technologies such as Embedinator4000 may make it easier in the future. As such, we had to repackage our app as a framework for iOS and a library on Android. We consumed them through a Xamarin iOS and Xamarin Android app respectively. Xamarin’s toolchain for integrating native frameworks and libraries is really designed for pulling in open source libraries and SDKs that change infrequently. The toolchain is not well suited for rapid development and iteration, especially on a large team like Words With Friends. Rebuilding our leaderboard did work with their toolchain, but Objective Sharpie and Android Library Bindings Projects require manual verification and tweaking that wasn’t where we wanted the broader team spending their time. Adding on to this was build time sluggishness in Visual Studio on Android due to the volume of code and resources we were pulling from our AAR. Xamarin’s toolchain is extremely powerful, but it’s not optimized for our use case.

React Native Technologies & Architecture

Going Deeper on React Native

Saying ‘we want to use React Native’ was only the beginning — we next needed to figure out what our React Native technology stack and architecture would look like. The ecosystem is evolving at a very quick pace, and figuring out what parts of that ecosystem to leverage can be difficult to navigate.

Less Is More

We established a principle which we are still using to this day — less is more. In the past, we had preferred more verbose technologies and more layered architectures to minimize making mistakes and keep code files extremely well defined. While those are best practices, they can also lead to inflexible code, with boilerplate getting the way of building out the functionality required. For example, the more layers in an architecture, the more burdensome adopting dependency injection can be, and more verbose technologies ultimately lead to the use of code templates to streamline efficiency, which gets in the way of code review.

Having had our core team together for several years now, we have become comfortable with technologies that are doing a lot behind the scenes to make our lives easier, knowing that we have a team experienced enough to get the benefits of these technologies without getting tripped up in the process. We had success introducing RxJava to drive complex data workflows in our Android app, and Cora Data to manage local storage in our iOS app. These experiences showed us that our team would be able to excel with technologies that do a lot of heavy lifting behind the scenes, thus mitigating their drawbacks.

Learn By Building

We wanted to shift from a research phase to a build and ship it phase — the ecosystem is evolving and changing on a weekly basis, and too much forethought can actually hold a team back. We put together a high-level block diagram as the first draft of our architecture and wrote up brief 1-page documents making a case for what we believed were the most promising technologies to execute the architecture. We then immediately began building out features from the existing game inside of a standalone sandbox app with the intent that we would ship parts of it inside of our existing game to a small percentage of our users to try it out in production. Getting dirty with each technology hands on to make a call on if it would work for us was faster than spending more time up-front discussing the pros and cons of various options.

MobX As the Foundation

The core of a React Native app architecture is state management — how best to encapsulate parts of the app outside of your components for reuse and separation of concerns. MobX is our state management technology of choice. We do really like Redux, and we found it important to learn Redux early on to understand the motivation behind MobX, but there are a couple of things we really like about MobX. Here’s the basic way MobX works — this is from their homepage:

Here’s what we like about MobX

  • Data is observable, which means we can setup reactions to listen for changes
  • Focuses on productivity due to having less boilerplate to write
  • Data is typically put in separate logical stores, making non-UI functionality very modular

Here are the tradeoffs compared to Redux

  • Larger learning curve due to more happening under the hood
  • Not opinionated in terms of architecture, leaving room for interpretation
  • State is mutable rather than immutable and is thus harder to debug

We believed the ceiling was higher with MobX for productivity. We have been using it effectively since we adopted it, with no plans to switch to Redux.

High-Level Architecture

Words High-Level React Native App Architecture

Our architecture is very simple, favoring fewer layers. Our React components talk to MobX stores. Stores are either domain stores for logical non-UI parts of the game, or screen stores which hold onto UI state such as the text typed into a search field. These stores can talk to Native Modules which we call our bridge, for interoperability with the existing game. We also have services to encapsulate network calls or 3rd party SDK invocations.

While this does seem to some extent overly simple, it has held in all of our use cases so far, including building some of our most complicated screens. There’s also some great technical advantage to keeping things simple. Many of our components take models from stores as props. For example, our Avatar component takes a User model as an input (a prop) and thus can be leveraged by any game feature which needs to represent users. This includes game opponents, leaderboard users, and our friend list. Every new bit of functionality we add to Avatar such as profile picture frames or presence indicators is automatically available everywhere it is used. This strategy also keeps us flexible for the future — any component which is powered by a model from the domain can be used for new screens we haven’t thought of yet which are often a composition of parts of screens we have previously built. This is a huge win for developer efficiency. It’s also an opportunity for us to work more closely with our design team as they evolve the user experience over time.

Serializr

Also developed by the MobX team, we are using serializr to aid in the persistence of our application state. Words With Friends has always had a robust local storage implementation to ensure we handle situations like being on a bus with close to no connection, so our players can load the game and play their turns. We first looked at robust offerings such as Realm to replace our SQLite and Core Data implementations on Android and iOS, but we were inspired by the simplicity of libraries such as redux-persist and mobx-persist (mobx persist also uses serializr). In the same way that our data could be tagged observable with MobX, we wanted to be able to tag data that we wanted to be automatically persisted to local storage.

Serializr gave us just that. All of our data is tagged as both observable and serializable, which allowed us to setup MobX reactions that listen for any change to serializable data in a store so that we can always have the latest state of a store written to disk. Each store is given a key in AsyncStorage whose value is a JSON string — the serialized representation of the store’s data. Here’s the essence of how the reaction looks such that any piece of data tagged as both observable and serializable triggers an update to Async Storage:

Certainly, this simplicity comes at a price. We are writing to disk a lot even if it is asynchronous on another thread, which makes MobX features such as debouncing reactions must-haves as we expand our usage of this technique to make sure these developer efficiency boosts don’t come at the detriment of battery life or performance.

TypeScript

TypeScript is our language of choice for React Native on Words With Friends because of its usefulness when working in large teams, and especially for our team which is migrating from Objective-C and Java. TypeScript is a superset of JavaScript — it supports all the latest conventions and ES6 syntax, and adds type safety along with interfaces to create more structured code. TypeScript is one of the fastest growing languages right now and was developed by the same person who developed C#, which gave us confidence that it would be good for both the game and our engineers’ careers.

Our hands on experience with TypeScript has been very positive. It’s true that it is more verbose than JavaScript, which at first seems opposing to our goal to favor productivity over verbosity, but it speeds up the overall feature development process quite a bit. First, TypeScript removes second guessing when reading someone else’s code — a big problem for large teams working on JavaScript codebases. On a large team, you are often modifying code that was written by someone else, or many other people. TypeScript is, in a way, self-documenting, so long as you keep usage of ‘any’ to a minimum, it immediately makes even undocumented code far easier to get the gist of. TypeScript also speeds up diagnosing problems from production builds, since you can be confident that most type related issues were weeded out during the development process. Not having to always ask ‘what type of data am I dealing with’ keeps engineers focused on more important areas like business logic and UI.

Visual Studio Code

We love Visual Studio Code because it is lightweight, fast, has strong extension support, strong React Native tooling support, and even has built-in refactoring support for TypeScript. There are many good code editors out there, and lots of viable options for React Native, but we have been loving Visual Studio Code and have no plans to switch.

TSLint & Prettier

Linters and style tools aim to be prescriptive about code formatting, style, consistency, and simplification. Over years building Words With Friends, we have found conversations about these topics, especially in code review, to be a waste of time. TSLint and Prettier allow us to automate a certain level code quality and consistency that both saves time writing code that conforms to our standards and reading code that an engineer may not have written. We have even integrated TSLint into our continuous integration system via Jenkins to ensure all code is sanitized.

InversifyJS

Dependency injection is something we find useful on Words With Friends. We like InversifyJS because it is lightweight and accomplishes our main goal for dependency injection: keeping the door open for writing unit tests and having design time data. Because we code everything against interfaces which are mapped to concrete classes via inversify, swapping out services for mocks to test business logic or serve up static JSON data is very easy.

React Navigation

Navigation is not a solved problem out of the box with React Native, and getting it right can mean saving many engineering days making sure flows between screens work predictably, reliably, and smoothly. We like React Navigation because it provides a large degree of customization with navigation since it does not rely on the native platform navigation elements like other libraries do. On Words With Friends we want our designers to have flexibility with visual and animation design for things like tab bars, navigation bars, and screen transitions. React Navigation gives us that ability.

Integration With & Migrating The Existing Game

Integrating Into The Existing Game

We needed to integrate parts of our standalone React Native app into the existing game to verify that React Native is delivering the same high quality experience to our players. We also aspire to remove our legacy code and move to a simplified 100% React Native app down the line. Not getting to 100% React Native has been a trapping of early React Native adopters that were perpetually in an in-between zone between native and React Native.

ViewController, Fragment, and ViewHolder

The React Native library only provides a simple view on each platform to host React Native components. This makes it more difficult to integrate small components into larger screens because certain classes are required, such as pushing a view controller on a navigation controller on iOS or using a view holder to represent an item in a list on Android.

As a result, we ended up building our own containers to host React Native views in these different contexts. We register these components in our index.js file and instantiate them via our custom native containers.

Sandbox vs Main App

We have an internal standalone React Native app we call our sandbox app which has no ties to any pre-existing code or infrastructure. It’s where we start when building all new React Native features, so we can develop quickly and not worry about how it integrates into the existing game. Our integration points are generally only forwarding tap events that lead to navigation or other actions. We do reference Native Modules that only exist in the existing game, but we make sure that we gracefully handle their nonexistence in the sandbox app.

We are continually bringing more and more of our React Native work into the main codebase as we ship each update of Words With Friends, via the native containers we just touched on. We plan to remove as much of our existing native code as possible as we make our full transition to React Native.

A/B Testing Native vs React Native Implementations

Because our integration points are simple and few in number for each React Native screen, we can easily A/B Test React Native replacements versus their original native counterparts. This helps us ensure we are not hurting performance, business, or stability metrics as we cutover to React Native. This gives us confidence that what we are doing is actually working, backed by data. Once a new React Native piece is determined to be on par or better, we remove the A/B Test and delete the old native code.

Favoring Integration Simplicity vs Optimization

In trying to learn from the lessons of early React Native adopters, we made the decision not to bridge any complex infrastructure, and build whole features from top to bottom in React Native, only integrating at the UI level, save for some low hanging fruit to save extra network calls duplicated on the React Native side. In general though, we’re ok with some inefficiencies around network calls, local storage, and asset duplication so long as we stay under OTA app download sizes, do not greatly spike out of memory crashes, keep our server SLAs in check, and do not greatly inflate the amount of local disk space Words With Friends consumes. We believe that these sacrifices remove short term pain, making the conversion process far more feasible, and still leads to an optimized path down the road after native code and infrastructure are removed.

Data Driven Approach

When we released our update with the React Native leaderboard we added instrumentation to be able to see if we were impacting engagement metrics, business metrics, load time, memory usage, and crash rate. Engagement metrics & business metrics remained steady, indicating that players’ experiences were not being impacted. User crash rate remained steady, indicating the base React Native integration plus our leaderboard & supporting infrastructure was not impacting stability. Average memory usage went up ~9 MB or ~5% which is what we expected, which did increase our crashes per crasher on Android since we were already approaching memory limits on older devices. This is an area we are actively optimizing to make more room for React Native, including being able to turn off memory intensive features for older devices. Load time of the app was not impacted at all, but we anticipate it could be as we start rebuilding pieces which are viewed at startup like the game list. Below are some graphs illustrating the comparison. Variant1 and new_stack ‘yes’ both indicate clients with React Native turned on, whereas control and new_stack ‘no’ indicate clients with React Native turned off.

Total Memory Usage in MB of React Native vs Native Leaderboard

% of Users Crashing With React Native vs Native Leaderboard

Average Load Time in Seconds With React Native vs Native Leaderboard

Embracing and Overcoming React Native Challenges

Taking the Platform’s Challenges Head On

There has been a lot of news recently in the React Native world around companies such as Airbnb and Udacity sunsetting their usage of React Native, while other companies such as Discord have come out defending their continued investment in it. It was important for us to evaluate these articles — having big players investing heavily in React Native, but now backing out was a concern for our team. Here is our take on some of the reasons React Native was not working out well for other companies.

React Native Immaturity

There are times when React Native’s immaturity becomes apparent and makes something that is easy to do in native code very difficult, but we have observed this to be decreasing at a steady rate over time. A 2018 example of this is resizable images with cap insets, which are handled differently on iOS and Android, with no included mechanism for handling this in a cross platform way.

React Native’s early adopters, however, had to deal with problems such as iOS 11’s introduction of safe areas or inefficient list implementations, whereas we can build from day one leveraging components like SafeAreaView and FlatList. Adopting React Native in 2018 means the scope of React Native’s shortcomings are much smaller, and many of the hardest problems have already been solved. Further, React Native has matured to a point where extremely high levels of code sharing are possible — something early adopters did not benefit from.

We rewrote our leaderboard screen in React Native and achieved user experience parity with 95% shared code — something not possible two years ago. We see gaps in today’s React Native APIs as opportunities to leverage our native expertise on Words With Friends and contribute to the open source community with our solutions.

Maintaining a Fork of React Native

We have not yet had to make changes to React Native that have required us to have our own fork. While this does seem like a technique used by early adopters of React Native, we have not yet run into a case where this was required, again showing that React Native has matured a lot even in the last two years.

JavaScript Tooling

We were able to take advantage of TypeScript from day one since we had no existing JavaScript infrastructure to take into consideration. Our experience with tooling has been very positive, with a few snags along the way, such as lower level issues preventing us from seeing line numbers in our Android errors, but we see those as short term problems since important issues are quickly prioritized by the React Native team. And while features such as hot reloading are finicky at best, reliable live & manual reloading of code is already producing orders of magnitude increases in efficiency compared to re-compiling after every code change.

Refactoring

This is made much easier for us since Visual Studio Code has built-in support for TypeScript refactoring. In practice this works well, refactoring quickly and accurately for common tasks like symbol renaming. Also, since we are working in a cross platform manner, gone are the days of refactors only happening on one platform that leads to inconsistent architecture and conceptual inconsistency.

JavaScript Core Inconsistencies

Inconsistencies do exist between JavaScriptCore running on iOS and Android, with further inconsistencies generated by debugging with Chrome V8 and not JavaScriptCore. We’re less worried about this because we already heavily test our changes using release builds on both platforms during our QA process, so any glaring problems are caught early. This does, however, inform our testing strategy, and we have been focusing on Android testing first for new React Native features since if it works on Android it likely will work on iOS, but not necessarily vice versa.

Subtleties between debug and release environments have always been a thing in mobile development, even native (for example push notifications), and this is another important, but not deal-breaking difference to be aware of. The React Native team is planning to upgrade Android’s version of JavaScriptCore in a future release, which mitigates this issue, getting both platforms’ versions of JSC closer together.

React Native Open Source Libraries

We have tended to focus more on leveraging battle tested React Native agnostic libraries such as MobX, InversityJS, and Axios where the risk is low and the rewards are high. Having access to these kinds of libraries makes React Native open source libraries a huge strength of the platform.

React Native specific libraries, on the other hand, are riskier and tend to fall short on 1 platform. The excitement and rapid evolution of React Native open source libraries create noise, making it harder to find good, reliable open source libraries, particularly for UI, but this is not a reason to avoid the platform.

Parallel Infrastructure and Feature Work

Companies that have been sunsetting React Native have relied too heavily on React Native’s bridging technology, Native Modules which invoke methods on the native side in a batched and asynchronous nature. We actually view the bridging technology as a strength if used sparingly, and we keep our bridge calls mostly limited to UI interactions between React Native and native UI. We made a new vertical client tech stack based on the technologies listed earlier in this article so we could build out whole features without any dependency on existing infrastructure.

Crash Monitoring

We are currently using Bugsnag for React Native crash monitoring on Android and iOS due to its best in class support for React Native & sourcemaps, as well as its ability to roll up React Native errors & native crashes into 1 place. We are leveraging all the great work done by Airbnb and others to help improve Bugsnag’s technology so that we continue to have live-ops excellence in a React Native world. Adopting React Native in 2018 means crash monitoring is far more developed than it was 2 years ago. Overall we are finding the quality of life with crash reporting to be comparable to native, and are working with Bugsnag to improve other areas that are important to us, such as co-existing with our myriad of 3rd party SDKs and supporting functionality we’re used to with Crashlytics such as daily user crash rates and shareable crash links.

Native Bridge

We have found the native bridge via Native Modules easy to write, certainly easier than other options on the market, and was a reason for choosing React Native. We think it’s important for our team to operate as cross platform developers. We believe it’s an asset to have all engineers setup both iOS and Android environments in order to be able to write bridge code and run the app — we have been doing this successfully for several years with our C++ to native bridgework for Cocos2dx.

But, as previously mentioned, containers for Fragment, ViewHolder, and ViewController do not exist. These were foundational pieces of infrastructure we had to build to make native integration smooth (possibly an opportunity to contribute back to the community in the future). That was a one time cost with a big payoff, so it’s not a big con for us, but it would be nice to have these included in the base React Native library.

Initialization Time

We concur with others in the React Native community that initialization does take significant time for large JavaScript bundles. On Words With Friends we have a splash screen with a lengthy animation that gives us some cover for initialization time as our JavaScript bundle size grows, but initialization time has not been a factor so far in shipping large features in React Native. As our bundle size increases and as we work to rebuild our game list which appears immediately after the splash screen, more advanced techniques such as bundle splitting and dynamic imports may be required, but we know technical solutions exist if they are required.

Initial Render Time

Initial render time, like initialization time, is also a factor with React Native. Airbnb saw close to a half second p90 for initial rendering on Android so we know it’s important to be mindful of. We have not shipped much navigation code yet, but are using React Navigation which has been performing well in our initial prototyping. The jury is still out on how much this will impact us as we migrate more of our codebase to React Native.

App Size

It’s true React Native can add close to 10MB to app size just for a basic implementation, but that’s room we are happy to accommodate for on Words With Friends given the value the technology provides us. With React Native integrated, we are still well under the OTA limit for downloading apps and have had success using a single @3x image for image assets across all screen resolutions to mitigate not having access to native app thinning technologies. There’s also the possibility of hosting our JavaScript bundle remotely rather than bundling it into the client in order to further reduce our app size.

64-bit

It’s true we cannot ship a 64-bit APK until all of React Native does, and it is increasingly becoming a pressing issue, with 64-bit support being required in about a year from now. On Words With Friends we use lots of libraries which will require 64-bit support, so it’s already on our radar, and React Native not supporting it today doesn’t deter us from using it, especially since supporting 64-bit is something the React Native team knows they need to do in a timely manner if they want to support Android at all past 2019.

Gestures

While complex gestures are not something we do a lot of on Words With Friends, we understand that there are limitations. Having native gesture APIs available made swiping through cards in our old Community feature far more achievable than the React Native JS Responder System. React Native Gesture Handler hit 1.0 recently and is a great asset if we need to build gesture-heavy interactions.

Long Lists

Originally a big issue with React Native, with the advent of FlatList we have found long lists to be very achievable in React Native. Much of the UI in Words With Friends is composed of long lists, and being able to do this well even on lower-end devices is critically important to us. RecyclerView and UICollectionView have served us very well over the years. We have so far had success using FlatList for long lists such as our leaderboard screen without any issues. Yes, there is some visual oddity when scrolling very quickly, but FlatList has configuration options to mitigate this such as pre-rendering items which will be on screen soon that we have found to work well in production.

Upgrading React Native

We have only had to do one upgrade so far — from 0.55 to 0.56. We viewed this upgrade as much easier than taking a native SDK upgrade by comparison. We understand that past upgrades have been very disruptive, but our experience starting with 0.55 is that upgrades are not very painful and come with lots of fixes that make us excited to keep getting onto the latest version.

Accessibility

Another huge pain point of React Native’s past is accessibility, which was recently addressed by the React Native team. While we don’t leverage these APIs to a great extent on Words With Friends yet, we are always looking for ways to let more people experience the game, and it’s good to know this area of React Native is burgeoning.

Troublesome Crashes

Some developers have ran into some very bizarre crashes with React Native. This hasn’t been our experience so far even at high levels of scale.

SavedInstanceState Across Processes on Android

Activities hosting React Native views cannot easily save their state if the OS needs to clean them up while in the background due to React Native’s asynchronous bridge, but it’s not an Android feature we need in a React Native world. Because of our ability to keep our application state up to date on disk, restarting an Activity hosting a component that needs to recreate that state is done from local storage.

Entering the React Native Community

At this point, you have read through our evolution on Words With Friends in 2018 from an entirely native iOS and Android app to our current trajectory towards a full React Native app. We are in the middle of this transition process as of this writing and couldn’t be more excited to emerge into the React Native community. So far, we have shipped one of our most complex features, the leaderboard, on both platforms without issues, and are working through converting the rest of the game over to React Native in the next year, as we try to learn the lessons of early adopters. We are also taking a calculated approach to ensure we do not sacrifice quality for our players.

New doors open up for us as game developers as we work to complete this transition. We will be able to deliver more to our players at comparable quality. With the possibility of over the air code pushes, we unlock the ability to act more quickly on player feedback and tastes. And as a new member of the React Native community, we hope to meet new potential future friends and colleagues.

This is only the beginning for us on Words With Friends with React Native and we look forward to being a productive member of this community. See you all in game and on github. Thank you for taking the time to hear our story. Peace out.

--

--