Development

Reacting Natively: A Developer Diary

By Alfonso Cejudo - 6 July 2017


A fictional account of how this experimental project was created.

 

March 15th.

Got a memo from headquarters. They want to see something in React Native. Swift and Java are passé. They’re on the way out. We need the new hotness. React Native. You write some JavaScript and boom. You get an iOS AND an Android app. You get instant code reloading. You get a fervent open-source community backed by the big guns. Facebook. Airbnb. Some guy in Ukraine. The big guns.

So pull up some tutorials, copy and paste some example code, and just start building something. Anything.

I’m a native app developer with experience in Objective-C, Swift, Java, and even Android NDK for multi-platform C++ code. When HTML5 tried its darndest to get in the app game, I laughed at and mocked its futile attempts. But React Native is getting some serious buzz — I mean, some guy in Ukraine — and the bigwigs want something. Anything. Oh I’ll give you something alright.

 

March 16th.

I got nothing.

 

March 17th.

Managed to get React Native’s equivalent of a Hello World app to load on both an iOS simulator and an Android emulator. It wasn’t nearly as easy as just pressing the Play button in Xcode, though. Had to have a command line open in the root of the project directory and type in one run instruction for iOS and a separate instruction for Android (which required an emulator to already be open).

Speaking of IDEs, I decided on using Atom, with Nuclide on top of it. Reminds me of the Android plugin on top of Eclipse days. I don’t want to be reminded of those days. But until some sort of React Native Studio is released, it’ll have to do. Also installed the Yarn package manager to replace npm, simply because I thought “yarn add” and “yarn remove” are easy, straightforward commands to remember when installing third-party React Native components. Turned out yarn inits and installs were noticeably faster than npm ones, too. This is absolutely critical because Friday.

 

npm command

npm install --save [package]
npm uninstall --save [package]

yarn command

yarn add [package]
yarn remove [package]

March 20th.

As far as third-party goes, I found myself pulling down a lot more React Native components than iOS CocoaPods or Gradle dependencies. With native apps, I would typically just include AFNetworking/Alamofire for managing HTTP requests in iOS and that’s it. Maybe the Google Maps SDK if the project called for it. Similar story in Android with Retrofit. For everything else, including UI elements and persistent storage, I was able to accomplish most things with classes built into iOS and Android.

In React Native, I was yarn adding for things as basic as buttons and ListViews! However, once each component was added to the package.json file, they were really easy to start implementing. With React’s JSX syntax, laying out the UI for a screen was fairly straightforward, especially if you’re already experienced in Android’s XML approach to designing for every possible screen size. Both of these solutions are much easier to use for adaptive layouts than Xcode’s Interface Builder and even an iOS Auto Layout upgrade like SnapKit.

 

React JSX

< Button
   onPress={onButtonPress}
   title=”Press Me”
/>

Android XML

< Button
   android:layout_width=”wrap_content”
   android:layout_height=”wrap_content”
   android:text=”@string/press_me”
   android:onClick=”onButtonPress”
/>

Swift Code

let btn: UI Button  = UIButton(frame: CGRect(x: 100, y: 200, width: 100, height: 50))
btn.setTitle(title: “Press Me”, for: .normal)
btn.addTarget(self, action: #selector(onButtonPress), forControlEvents: .touchUpInside)

self.view.addSubview(btn)

As someone who kind of just jumped in right away from Swift+Java (Swava? Jift?) to coding in JavaScript, I’m pretty much learning the fundamentals as I’m going along. Or when I write a block of code that gets the red error screen. Or when I write a line of code. Or when I write anything.

Why do some imports use curly braces and other’s don’t? Turns out you don’t need the braces if the thing (e.g. component, function, constant) is the default export of its module. Curly braces are used for all other non-default exports.

import mainDoer from ‘./Doers’
import { thingDoer } from ‘./Doers’


export default function mainDoer() { code }
export function thingDoer() { code }

What’s the deal with the arrow syntax?

someNumberArray.map(item => item + 1);  // Add 1 to each number in the array

Well, it’s just a cleaner alternative to the old way of how this logic might’ve been written:

someNumberArray.map(function(item) {
   return item + 1;
})

Ok, well what about all these ellipsesess? There’s different kinds of those, actually. There’s the spread syntax that takes a single element that’s expected to expand to multiple arguments or elements. So instead of this code that uses a useless null:

function doThinger(a, b, c) { code };
var args = [0, 1, 2];
doThinger.apply(null, args);

I can now just write:

function doThinger(a, b, c) { code };
var args = [0, 1, 2];
doThinger(...args);

Different from the spread syntax is the rest operator, where instead of expanding an array to its elements, multiple items are condensed into a single one.

var [dope, ...wack] = [1, 2, 3, 4, 5];  // wack = [2, 3, 4, 5]

So that’s all good and fun. But as I kept writing more and more JavaScript, the language revealed itself to be… weak? Weak in the sense that Swift and Java have “strong” rules about the use of semicolons, but they’re not actually rules you have to memorize or think about and more just part of the code you write because it makes complete, unambiguous sense. The fact that JavaScript continues to have debates about the use of semicolons and that things like trailing commas are optional (while not allowed in JSON) makes me miss the native app languages where you don’t worry about any of that stuff and just focus on solving problems.

 

March 21st.

Instead of meandering around the React Native world aimlessly with tutorials and code samples, it’s nice to finally have a goal to focus on and work toward. Decided on building an app that populates a map with markers of points of interest from Google’s Places API. Tapping on a specific location marker will then grab Instagram photos of that location in a GridView of thumbnails. Lots of concepts at play here: HTTP requests, JSON parsing, image downloading and caching, maps and markers, and UI fun.

Pulled down Airbnb’s react-native-maps component. Evidently the original built-in MapView component had been surpassed by Airbnb’s implementation, and support for the former was cut off in January with an official recommendation to go third-party for maps. That’s some serious open-source community power. Seemed to be a good decision, too, as dropping in the Airbnb JSX (along with a Google Maps API key) resulted in a functional Apple Maps view in the iOS simulator and Google Maps view in the Android emulator much more quickly than if I was adding maps natively one at a time for each platform.

To fill up the map with markers of real location data, I used React Native’s Fetch API. By providing Fetch with a Google Places URL, I get back a JSON response that’s readily parseable as an object or multidimensional array. I was also able to quickly yarn add and make use of a third-party library called traverse to evaluate each key-value pair in the response while taking into account its depth in the structure. That this library hasn’t been updated in almost 4 years but still worked right out of the box seemed incredible to me, and I don’t know if that would ever happen with an iOS or Android lib.

The next step after extracting relevant geolocation data from the JSON response was to create models — same step as it would be for native — so that we can build an array of markers to put on a map. Or maybe that shouldn’t have been the next step, as far as reactive programming is concerned? Is there a proper, functional approach I should’ve followed? Oh well. Made sense to me. I express concern only because there weren’t any standards or many examples to follow for making models in React Native whereas iOS and Android have fairly ubiquitous patterns as models are a large part of MVC. One-third of the initialism, even!

I coded my MarkerData model like so:

 

export default class MarkerData {

   constructor(locationId) {
      this._locationId = locationId;
   }

   getLocationId() {
      return this._locationId;
   }

   set name(name) {
      this._name = name;
   }

   get name() {
      return this._name;
   }

   set latitude(latitude) {
      this._latitude = latitude;
   }

   get latitude() {
      return this._latitude;
   }
   …  // NOT CODE. Just saying etc. and so on and so forth...

Creating objects of this model would then look something like:

for (var i = 0; i < idArray.length; i++) {
   var markerData = new MarkerData(idArray[i]);

   markerData.name = nameArray[i];
   markerData.latitude = latArray[i];
   markerData.longitude = lngArray[i];

   markers.push(markerData);
}

And then creating map markers from the models might go as follows:

getFeedMarkers() {
   return this.props.feed.map(
      (markerData, i) =>
                     key={ i }
            title={ markerData.name }
            coordinate={ {latitude: markerData.latitude, longitude: markerData.longitude} }>
            
               
                  { markerData.name }
               

            

         
   );
}

March 22nd.

Moving from traditional Swift and Android Java to React Native required more than just using a different language and tools. I also had to try solving problems in a reactive way instead of how I was used to approaching them in native. For example, after creating an array of MarkerData objects, my next step in native would have been to loop over the array and, in each iteration, access the map object directly to call its function to add a single Google marker or MapKit annotation. If our set of markers changed (e.g. when the user moves around the map), we have to clear the previous set from the map object and then run the loop again. Never gave too much thought about the tediousness of this imperative, object-oriented process because, well, this is just the way things are.

In reactive programming, however, we can accomplish the same result (placing the current set of markers on a map view, in this example) with less effort and, perhaps, a more natural way of thinking. What if we just told our map view “Hey, here’s the array of map markers. If you’re rendering yourself on the screen, just present the markers that are in the array at that given time. Don’t be lazy and wait for me to hand you markers one by one — react to the array as it changes.” Seems to be a little more natural no? Apart from talking to a block of code.

This mindset shift also helped me decide to use Redux in the app. Any new React Native developer is bound to see that word mentioned as they read more and more articles and tutorials. I figured if I want the full React Native experience, I need to collect the whole set. Of tools. And I’m glad that I did. For the task of updating the markers on a map, it was total overkill. But I got so much more out of it than that. Going through the learning pains of using Redux forced me to change my way of thinking to be more reactive.

Step one: give the MapView component an array with which to make location markers. I’ve already talked about this part, actually — it’s the getFeedMarkers() function from yesterday. Redux lets you use a prop for the stateful thing that you want to observe. In the previous example, it’s the “this.props.feed” property over which I ran a map function describing how to render a marker for each location in the array.

How do we update this geolocation feed array whenever we get a new set of locations? Here’s where the overkill part comes in. In Redux, we can make our feed an object that’s part of the global application state, called the store. You don’t write to that array immediately with new values. You dispatch an action that pretty much says, “Hey, I just successfully received a geolocation feed, here it is.” A dispatched action will be caught by something called a reducer, which carries out the necessary logic to update the Redux state. This action is dispatched from a successful fetch call.

fetch(url)
.then((response) => {
   if (!response.isOk) {
      throw Error(response.statusText);
   }

   return response;
})
.then(response => response.json())  // Extract the JSON body (if valid)
.then(feed => {
   dispatch(feedsFetchSuccess(feed));  // dispatch is a Redux keyword
})

The action to be dispatched by Redux is simply just:

export function feedsFetchSuccess(feed) {
   return {
      type: FEEDS_FETCH_SUCCESS,
      feed  // ES6 shorthand for “feed: feed”
   }
}

So when our API fetch receives a JSON response, we tell Redux to dispatch an action to the store, carrying with it the geolocation feed as the payload. A reducer that’s been programmed to intercept an action with that name will replace the existing store with a new store that contains the feed. One of the main tenets of functional programming is immutability, so Redux demands a new store as opposed to updating the current one.

export function feeds(state = [], action) {  // Initialize the state to an empty array
   switch (action.type) {
      case CLEAR_FEED:  // “Clear” the feed by replacing the state with an empty array
         return [];

      case FEEDS_FETCH_SUCCESS:
         var newState = Object.assign([], state);  // Create a new state from the old state
         newState.push({ action.feed });  // Add to the state the feed attached to the action

         return newState;

      default:
         return state;
   }
}

So yeah, it’s a super roundabout way of updating the geolocation feed, which could be as trivial as clearing an array and adding items to it in Swava. But as someone coming from that world and diving head-first into reactive programming, implementing Redux forced me to adhere to some of the principles of functional programming: immutable data structures and pure functions (that, given the same input, will always return the same output).

 

March 23rd.

Time to show Instagram photos for a Google Places location tapped by the user. Time to yarn add another third-party component. Ended up with RNFetchBlob, which has pretty much the same API as Fetch. RNFetchBlob also comes with a generic directory structure that abstracts away the sandboxed directories your app is allowed to place files into, which differ in iOS and Android. Anyway, every time an image is downloaded, we go through the Redux flow again to add the image’s file path to an array that contains all the file paths to images for the selected location. When a new location marker is tapped on the screen, this array is cleared and repopulated.

At this point, came up with the notion of thinking of this mapping app like a DJ mixer with two channels. Channel One is the feed of locations that fills the map with markers. Channel Two is the set of enrichment data for the location selected in Channel One — in this case, Instagram pictures. Finally, a name for the app. Map Mixer!

And another finally. Actual UI stuff! We have basic functionally in place, and figured it’d be nice to actually see some of it on the screen. Also figured that I didn’t want to spend too much time building custom UI components right now and hoped that someone out there has already put in considerable effort making a library of plug and play buttons and text labels and such. UI elements that are automatically natively styled for the platform on which the app is running, whether iOS or Android. NativeBase does this very well, and even simplifies the separation of elements between the navigation bar and the main layout with straightforward XML:

render() {
   return (
      
         
 {/* Anything within Header is placed in the nav bar! */}
            
               Map Mixer
            
            < Button
               onPress={ () => this.onMenuPress() }
               iconCenter
               light>
               
            
         

          {/* Anything in here is the main layout! */}
                           style={ styles.map }
               initialRegion={ this.state.mapRegion }
               onRegionChangeComplete={ this.onRegionChangeComplete }>
               { this.getFeedMarkers() } {/* Add map markers as child elements of MapView! */}
            
            {/* If the Channel Two array is not empty, display Channel Two on top of the map! */}
            { (this.props.channelTwo.length > 0) && this.renderChannelTwo() }
         
      

   );
}

Also started dabbling with some animations. Wanted to have Channel Two be a GridView of thumbnail images that initially peeks up from the bottom of the screen. When the user taps on the GridView, it would animate up and end up filling just over half the screen, sitting above the map view. Was able to implement the user tap action with a TouchableHighlight component and the restless, nomadic view with an Animated.View component. But wait! TouchableHighlight seems to only work in iOS and not Android. After some Overflowing of Stack, discovered that I needed to use the similar TouchableNativeFeedback if the app detected an Android environment. Weak. Points deducted, React Native.

toggleChannelTwo() {
   var toValue = (screen.height - 250) * -1;  // Negative value moves view up the screen
   if (isChannelTwoExpanded) {
      toValue = 0;  // Return the view to its original starting position
   }

   Animated.spring(
      this.state.bounceValue,
      {
         toValue: toValue,
         velocity: 3,
         tension: 2,
         friction: 8
      }
   ).start();

   isChannelTwoExpanded = !isChannelTwoExpanded;
}

March 24th.

Looking at the project as a whole at this point, I realized that most of the components that make up the app is third-party. Stuff that other people have made. Stuff that is beyond my control in terms of buildability and functionality. This is scary enough for an independent developer publishing production apps. It’s a whole different level of scary for an established company that needs to be accountable not just to individual users but perhaps also to clients. I think if someone’s going the React Native route for their next app, they’ll need to do at least one of two things:

Number A: Create as many React Native components in-house as possible. A company that develops its own products (rather than outsourcing to consultancy firms) and is large enough to dedicate resources to building components (rather than using open-source libraries on Github) can reap the benefits of React Native while also retaining full control of the stability and maintainability of their published apps.

Number B: Limit the usage of third-party components to ones that appear to have large amounts of engagement and frequent, recent updates in their public git repositories. When there’s just not enough time or people to create the custom components that make up the app, a certain level of trust will have to be placed on the libraries that provide your map views and your navigation drawers and your modal dialogs, etc. What’s the testing coverage of the library? How futureproof is it? How many OS versions back does it support? Satisfactory answers to questions like these are more likely to be found in an Airbnb library that’s been forked 1,000 times instead of one that hasn’t merged a pull request since smartphones were able to fit in pants pockets.

In its current state, Map Mixer relies pretty heavily on open-source components. I suppose I could take the time to make my own Navigation Bar that’s interfaced with generically but styled properly for the platform it’s running on, but then I might have to update its code every time a new version of iOS OR Android is released. Take that dilemma and multiply it for all the buttons and list views and image downloaders and thingamajigs and vermicious knids. Do I want to invest that kind of time when NativeBase has 46 contributors in Github? As of the 2.0 version, I can just specify a Header tag in JSX and get back a nice-looking App Bar in Android and an equivalent Navigation Bar in iOS. Instead of worrying about all the lower-level stuff, I can focus on putting all these individual components together to make one kick-ass app.

 

March 27th.

That version is now obsolete.

 


Alfonso Cejudo

About Alfonso Cejudo

Programmist. Humorer. Cash him inside doing mobile app development at ustwo™

Make change happen

Talk to ustwo
hello@ustwo.com