How I learned React Native by building and shipping an app to study faster

Posted on Apr 13th, 2017

This post is an overview of how I learned React Native by taking a problem and shipping a product to solve it. This post details the entire app design and development process I took to build YouSpeeder, a simple app allows you to adjust YouTube playback speed. This post covers my process from idea to implementation to shipping in both Google Play Store and Apple app stores .

Watching some lectures and want to finish studying them by the afternoon instead of the evening? That's the point of YouSpeeder .

Product Design - 1. Identify the Problem

You can create a product that solves your own problem or someone else's problem. Generally, it is easier to solve your own problem because you deeply understand it.

Sometimes multiple problems can be solved at once, but it's usually easier to clearly define a single problem and focus on it entirely. My main problem was wanting to learn React Native, but that's not enough for a product. I thought about the biggest pain points I have when using my iPhone, and the one that stuck out was:

Problem: I wanted to watch lectures on YouTube on my phone at 2x speed

I waste a lot of time, but I'd like to waste less. I listen to a lot of audio books and lectures on my daily commute, and when doing things like jogging or cleaning dishes. To save time, the playback rate is always set at 1.5x or 2x.

When watching lectures on YouTube on my laptop, I can increase the playback rate. I cannot do this on my phone. For me, this problem makes the mobile YouTube app unusable.

2. Design a Solution

This is a simple product. The problem is so clear that the solution immediately presents itself: Provide playback speed controls. While the solution might be clear, the way we get there is not.

My process involved jumping directly into the code and prototyping possible solutions. This isn't always a great approach, but I wasn't sure exactly what kind of in-app experience I wanted to create. I only knew I wanted to increase the playback speed. Because I wanted to also learn React Native while prototyping the app, I skipped a traditional wireframing / mockup process. Framer is a great prototyping tool, but in this case for me it would be just as quick to iterate in code as it would be to iterate in a different prototyping tool.

Before coding though, I imagined how I might solve the problem at a technical level. I knew from working on a previous product that wrapping an app inside another app isn't really possible on iOS (but there are ways to achieve similar kinds of experiences on Android). Therefore, it seemed the answer would be to use some sort of webview and inject client side code to manipulate the client side <video> elements to adjust playback speed.

3. Build a Prototype

I do experience with React, but none with React Native. To get started, I followed the terrific Facebook React Native tutorial. After getting a basic project going, I did spend too much time looking at different "starter kits," like Ignite and Este, but decided to keep it simple. With a more complex app, I would likely pursue Ignite. These kits are extremely helpful when building any kind of application that requires multiple views or components, as they bundle a lot of best practices and tools in an easy to use package.

The one potential downside, however, is by using them you may not as easily fully learn how the pieces work together (but you can get going much faster). I personally learn best by doing, and so would not recommend diving into a starter kit before building a simple app so you can experience for yourself the decisions made by starter kits.

My design process was intertwined with the development. I began by getting a simple, ugly prototype working that would adjust the current video's playback speed. For this product, I needed to actually play around with it and get the entire experience to know what direction I wanted to the design to take.

As ironic as it sounds, the app is basically a webview with native app playback controls. In fact, the entire app could just be a webview, but then I wouldn't learn much about React Native. When you press the "2x" button, it sends a message to injected client-side code to increase video playback speed. I won't get into the fine details of the code here, but you can find the full source on GitHub. I already have experience with React, so the learning curve was very quick. If you do not have experience with React though, the React Fundamentals course is excellent.

I made a lot of use of logging throughout the dev process. The app is essentially a webview with React Native controls to adjust client content (the videos). Because the injected javascript lives on the page and not in the native app itself, console logs did not show up in the React Native debugger - so, I tested the injected code locally (outside of react native), and also injected log messages into the page itself in the React Native app.

After getting a prototype working, I started working on the UI so it wouldn't look like you didn't need to learn VIM to use it.

It's not going to win any design awards. However, it does condense all app actions into just a few buttons. Playback speed is changed by cycling through values. I discovered that I rarely ever needed to jump from 1x to 4x. It does force more taps to get to the playback speed you want, and there is some hidden complexity in that you don't know the bounds; but the tradeoff is a simplified, small UI element.

Initially, there were over 10 playback speeds you could cycle through, going all the way from 0.1x to 4x. This was painful and unnecessary. I simplified it to use 0.5, 1, 1.25, 1.5, 2x values, and it covered all speeds I ever actually wanted to use. Before actually using the prototype, it's hard to know how seemingly small decisions, like number playback speed options, can make the app experience painful or pleasant.

Next, I started next to implement advanced controls hidden behind a drawer using React Native Drawer, but after using the app more, I realized it was solving my goal and I didn't want to get entangled by feature creep.

Polish and Knowing When to Stop

There are number of things that can be improved, but the goal of this app was to learn React Native and ship a simple product that would solve playback speed for "most" people. To that end, I added some polish (a ripple effect) and ensured all provided playback speed values would still provide a quality video experience.

The first bit of polish was adding a ripple effect. I tried a couple different material UI libraries, but in the end this simple ripple library proved to be the quickest, simplest, and easiest to integrate. It's a simple effect, but it adds some juicy feedback to the UI.

In terms of UX polish, one thing that was slightly annoying was that when the video was at 0.5x playback speed, the 30sec time skip was too great. To make that experience better, at 0.5x playback speed the time skip buttons will go back or forward by 15 seconds instead of 30. It was not obvious before using the prototype that the time skip should change based on playback speed, but that's the benefit of iterative prototyping: you can quickly discover how each element affects the entire experience.

The navigation controls (back / forward arrows) could be improved, but overall they effectively accomplish the goal (you can't go forward / backward without them). Alternatively, a swipe gesture recognizer could be used to achieve the same effect, and it may have led to a better experience. However, I made the tradeoff to ship it now and improve it later.

Secondly, a more advanced control pane should exist. While the playback controls are great for all the use cases I had, it would serve power users better if there were a way to allow more fine grained playback speed options. For example, being able to play videos at 2.5x or 3x. The reason I did not include anything higher than 2x was because after 2x, video playback on mobile looks bad - it stutters and is janky. 2x was the upper limit for an acceptable "user friendly" playback speed. However, if higher playback speeds were enabled behind an advanced settings pane or drawer, stuttering videos would be acceptable as the user is more amenable to a degraded video watching experience.

With these things in mind, the goal of this app was to learn React Native and ship a product that solves the problem of speeding up videos. More polish and features could make it better, or more pleasant, but perfect is the enemy of good (or something). My goal was to ship a product that solved my problem in a pleasant way. There's always room for improvement.

Shipping to the App Stores

I followed the following steps for each platform. iOS wasn't as streamlined as I hoped, but it is pretty straightfoward after doing it the first time.
Apple App Store Deployment

To publish it, I followed Facebook's Tutorial, along with this guide to release to the App Store.

Important note - make sure to change the Deployment Target in the Deployment Info section under your project settings, otherwise you may be limiting your app to newer versions of iOS unintentionally.

Problems When following steps, everything went fine until I tried to archive it. I received the error "YouSpeeder has conflicting provisioning settings. YouSpeeder is automatically signed for development, but a conflicting code signing identity iPhone Distribution has been manually specified. Set the code signing identity value to "iPhone Developer" in the build settings editor, or switch to manual signing in the project editor. Code signing is required for product type 'Application' in SDK 'iOS 10.3'." It seems this is because I manually tried to set the build settings as per the second tutorial. This was unnecessary - set it back to automatic and then in the General -> Signing second, check "Automatically manage signing." (see this StackOverflow answer if you get stuck).

After submitting it for review, it took two days to be approved.

Google Play Store

Very straightforward. I followed Facebook's tutorial and then Google's release guide and had zero issues.

1. Build App. 2. ??? 3. Profit

If you build an app that's compelling enough and solves a problem, some people will pay for it. It's a simple concept, but it's not easy.

I priced the app at $0.99 and got more downloads than I expected. Apparently, a number of people had the same problem I did. There are a few areas that I think led to downloads.

First, the icon is simple and mimics existing concepts (both YouTube and Fast Forward buttons).

The app is just YouTube with playback controls, so my goal was to capture the core YouTube logo but change it enough to communicate the added value.

After creating the logo in photoshop, I used to generate app icons for different device sizes.

Second, I included screenshots that clearly communicated what the app did. Before I created them, I looked at the most popular apps to get a sense of what works. I tried to immediately communicate the app's value and tried to take what worked from other successful apps by imitating their marketing aesthetic. Don't copy, but steal what works. Figure out the components and use them yourself.

One problem I tried to hook into was the problem of wanting to study faster. When you're watching a video at twice the playback speed, it takes you half as long to get through it. This doesn't always mean you'll be studying faster of course, but I have saved a lot of time by watching lectures that were sped up.

Lastly, I'm not sure how much the description helped convert users, but I aimed to provide a concise summary of the app.

Speed up YouTube videos
Condensing the value into a single sentence with no more than ten words is a good constraint that can help focus your product.

The Google Play Store accounted for far fewer downloads (surprisingly to me, only about about 5%) than the Apple App Store.

React Native Problems and Stumbling Blocks

I encountered a number of issues throughout the whole development process that led me with the impression that while it's incredible, React Native still has room for improvement.

The first issues were a class of build error problems with iOS, including:

  • "RCTBundleURLProvider.h file not found in AppDelegate.m"
  • Errors with profile; XCcode, provisioning profile, Xcode would just freeze when compiling
  • Errors with "No bundle URL present"
  • Can't package react native because of port 8081 sunproxyadmin
  • ProxyComponent has no propType for native prop ... and View has no propType error
All build issues were solved by some combination of rm -rf node_modules, "Command + Shift + K" in XCode (to clean), watchman watch-del-all, npm cache clean, npm start -- -- reset-cache updating package versions, and restarting my computer, and re-running :w npm install. These errors, which seemingly had nothing to do with code, were the most frustrating part of the whole dev process. But, no toolset is without its own gotchas.

I also ran into a few smaller issues tripped me up but become obvious after you've spent some time with it (or fully read the whole React Native tutorial), such as:

  • On iOS, all animations slowed down unexpectedly. The reason: I unwittingly pressed COMMAND + T, which slows down animations (can be toggled in the toolbar under Debug -> Slow Animations)
  • After running it on my iPhone, the app would not open when I was disconnected for the network. A release build must be created to allow this.
  • With iOS, simply running `react-native run-ios` will boot up the sim and I'm good to go; but for android development, I had to open the simulator through Android Studio.

Implementation - Web View Problems

Because the app uses a webview, ideally we want bi-directional communication. Unfortunately, I was able to only get communication from React Native to Webview working.

The Facebook docs on WebViews have examples of bi-directional communication. However, when running the code with the versions of React Native I had, it did not work. There are a number of open issues about this problem, and all attempts to solve it were fruitless. Fortunately, to solve the problem I only really need to send a message to the client code. Unfortunately, because YouTube is a single page app, onNavigationStateChange is not properly triggered, so it's much more difficult to track navigation since the client cannot send a message to the native app when the route changes.

None of these problems were show stopping, and after experiencing them they become easier to manage. Like any system, there is room for improvement, and I could see some of these issues being very frustrating for beginners. However, there are countless resources to help overcome these issues.

React Native Community

The React Native community is likewise encouraging and the amount of resources out there is staggering. The Awesome React Native List is a terrific resource that gives a thousand foot overview of what's out there, but it could seem overwhelming at first. The code and community is always improving, which leads to pretty fast changes. There's a great overview of React Native as of March 2017, but even this will be "outdated" in a few months.

To keep up with progress, I cannot recommend enough the react native newsletter. It's an invaluable hand curated resource that highlights what's going on in the react native world.

If those sources aren't enough, there is also a terrific community on discord.


React Native is incredible. It's not perfect, but overall I love React Native. I've made both simple and moderately complex iOS apps before with Objective C and Swift, but I was amazed by how fast I could iterate and build with React Native. Most the dev time was focused on the injected client side webview code, not even the actual React Native code! The contributors and maintainers are doing a fantastic job.

The source code for YouSpeeder is available on GitHub. Build it yourself, or take what works and use it as a base to get into React Native yourself!

You can also check it out on the Google Play Store and the Apple App Store . Happy React'ing!

PREVIOUS | Crafting Experiences with Data Visualization
All Posts