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: