Tour of React Native in 2024

January 2, 2024 · app devtool

Happy new year everyone! It’s a day into 2024 and I’m already back at work on the blog.

I wasn’t planning to spend my holidays making a phone app, but with no travel planned and Google holding me ransom, that’s somehow what I ended up doing.

In this post I’m not explicitly sharing what exactly the app does. Instead I’m presenting some technical notes and my experiences in building it. The reason is that it’s still under review in the app stores, and I’d like to announce it “officially” when there’s actually something to announce.

Naturally, I wouldn’t be surprised if you can make a pretty accurate guess about what the app does based on prior knowledge of its author and clues from the details I’m sharing.

Call to Action: Do you want to beta test this app? I’d be interested to hear your feedback! It’s quite simple and you could cover all of its functionality in under 5 minutes.

Hostage letter in question

Technical Introduction

As alluded to in the title of the post, this app I made is written with React Native, the JavaScript-based mobile framework that lets you run the same code on both Android and iOS. For context, the last time I had touched React Native was in 2021, on the Oculus mobile app (now renamed Meta Quest), the one that connects to VR headsets and lets you buy games and send chat messages.

At the time, I remember feeling surprised that such a crucial app was written in JavaScript, but the more I think about it the more I become convinced that it's clearly the best choice. Writing an Android app or an iOS app on their own might be easier than React Native, but writing one of each and dealing with keeping them in feature sync is undoubtedly more work than a single codebase, even if a few edge cases require platform-specific changes.

Thus, the tech choices were made: thanks to Google, I was going to make a mobile app for my idea, and thanks to Meta, I was going to write it in React Native.

So what is it like to start a project in React Native in 2024?
The answer: refreshingly easy!
A platform called Expo comes with pre-configured tooling that lets you jump straight into writing the app, rather than dealing with all the faff of mobile environment setups. Of course, that’s their hook, and once they have you on board they default you to building through their managed web service.

Interesting Details

Like most apps, the bulk of the project is made up of code that routes the user between different screens, shows various buttons in pretty colours, and lists data in scrolling views.

However, there are a couple of interesting spots in the app I’d like to share.

Charting module with reactive split

Back in GPX Replay, I had written code to render shading on the elevation graph to follow the current replay position. Well, for this app, I had a similar requirement.

I wanted to let the user swipe a cursor through their activity and see recorded metrics like heart rate, elevation, speed, and power. React Native has a few community libraries that make it at least mostly straight-forward to render a line chart. However, none that I found had something like this shading functionality built-in.

It may have been possible to recreate the effect by rendering the chart as two datasets with different line colours: one before the cursor, and one after.
But redrawing an SVG-based chart each time we move the cursor is a lot of extra work to do for our poor phones. Thus, I implemented a familiar solution by
calculating a percentage of the graph and drawing a divider line at the right spot.

// 64 and 32 are the margins of the chart axes, determined by inspection
<View
  style={{
    position: "absolute",
    left: 64 + (chartWidth - 64) * splitPercent,
    top: 32,
    bottom: 32,
    width: 2,
    opacity: 0.7,
    backgroundColor: colors.primary,
  }}
/>

n.b.: the 64 and 32 in the comment are coupled to the code!

0:00
/
To be clear, I'm talking about the vertical line on the chart that moves with the slider

Authorisation Redirect Server

Linking with Strava means going through the Authorization Code flow defined in OAuth 2.0. Have you thought about what happens when you log in to a service with Google or Apple?

Without getting too technical, I’ll summarise the process:

  1. You click the button on the app or web site
  2. You’re taken to the authorisation provider (Google, Apple, Strava, etc.) to confirm the access
  3. You’re redirected back to the app successfully

Between steps 2 and 3 there lies a hidden step. Namely, how can the underlying service know that the access request is coming from a client it knows about? To address this issue, we introduce the idea of a client secret. This secret is some special password which is shared between only the app owner and the authorisation service owner.

If this secret were leaked, then someone could impersonate the app and do evil things (that look like they came from the allowed app).

On Strava, evil things could include deleting your activity history or downloading your GPS data to figure out where you live and where you've traveled. No good!

Thus, between 2 and 3 lies an important code exchange half-step:

This step is normally invisible unless you’re paying close attention to the redirects that are happening in your browser!

The Catch

The problem with code exchange for mobile apps is that if the client secret is known on the app, an evil individual could dig through the compiled code and find it.

To fix this issue, I host the code exchange on a server I own, where the secret is stored. If you allow permission from Strava for my app, you’re not immediately sent back to the app. Instead, there is a quick round trip back to San Jose for this token exchange. Of course, the quickness depends on where you live! But the speed of light is certainly enough to keep the wait reasonable unless you leave the planet.

Key: purple = phone app, orange = Strava, blue = auth redirect server

Troubleshooting

Why, Strava?

For my app, I needed GPX files of tracked activities. Strava, which is the most popular source of these activities, has an “Export GPX” button on the web interface from an activity. However, the API, which the app uses, lacks this function. Instead, you query activities and receive a stream of points in JSON format.

The answer in this case is to construct a GPX file from these points. In the end this is only a small issue, but it’s somewhat perplexing that they clearly have the functionality available, yet they do not offer it to developers.

Expo Auth Module on Android

Now here’s a trouble that kept me busy for an entire day.

After I’d gotten all the features working on the iOS simulator, I decided to do some testing on the Android side. That’s when I discovered that the Expo Auth module’s promptAsync function simply does not function on Android.

In theory and in iOS, the function returns a response object after the authentication completes which contains the access token and other API information. On Android, I found that this function always returned a dismiss object, regardless what happens in the authentication stage.

After digging through StackOverflow posts, forum threads, and a still-open 127-comment-long 2-year-old GitHub issue, the only way I found to fix the issue was to hack a temporary listener onto the deep linking system (only on Android). This function listens for the deep link that the authentication server uses to send the browser back to your app and parses the necessary parameters from that directly, ignoring the outcome of the promptAsync.

useEffect(() => {
  // workaround android bug (https://github.com/expo/expo/issues/12044), so if not android, skip
  if (Platform.OS !== "android") {
    return;
  }
  const handler = async (event: Linking.EventType) => {
    // See: https://github.com/expo/expo/issues/12044#issuecomment-889031264
    handleAuthResponse(event.url);
  };
  const subscription = Linking.addEventListener("url", handler);
  return () => {
    subscription.remove();
  };
}, []);

That's all for now; once this app is ready you can expect I'll share that here too!

In other news

Previous: Ljubljana Half Marathon, 2023 Race Report
Next: Announcing: GPX Splice
View Comments