Category Archives: Android

Adding a map to React Native

This is part of a series on building a movie locator app in React Native. The first post is available here.

In my last post, I’d given the user the ability to navigate to a new page in the app. In this post, I’ll add a map on that new page. It will display the location of the theater that the user picked.

This is the first part of my app build which requires that you do something different depending on whether you’re working with Android or iOS. The installation instructions are here (as of 2020/03/23). I will be following the build configuration on Android, because I’m using an Android emulator (and a Linux OS, not a Mac, for my builds).

There are 6 steps given in the “build config for Android” section. I went through each of them one by one. I created a separate page which describes exactly what I did to prepare to add a map to my React Native app. Take a look there if you want to know about it in more detail.

In my previous post, I listed the code in MapsScreen.js, which is the page where my map should be displayed:

import React, { Component } from 'react';
import { Text, View } from 'react-native';

export default class MapsScreen extends Component {
    render() {
        return (<View style={[{ flex: 1, justifyContent: 'center' },
        { flexDirection: 'row', justifyContent: 'space-around', padding: 10 }]}>
            <Text>A map goes here</Text>
        </View>);
    }
}

But there’s no map there, yet! I need to add the code that will display a map.

Fortunately, the React Native maps project has an example app on GitHub. I took the time to download this example project, and create an entirely separate React Native project which uses its code. This helped me get my feet wet, and I knew where to look for the code that I needed to add a map: StaticMap.js is a page which displays a simple map. I’m going to take some of that code and add it to my own, as follows:

import React, { Component } from 'react';
import { View, Text } from 'react-native';
import MapView from 'react-native-maps';

export default class MapsScreen extends Component {
    render() {
        return (<View style={[{ flex: 1, justifyContent: 'center' },
        { flexDirection: 'row', justifyContent: 'space-around', padding: 10 }]}>
            <MapView
                initialRegion={{
                    latitude: 37.78825,
                    longitude: -122.4324,
                    latitudeDelta: 0.0922,
                    longitudeDelta: 0.0421,
                }}
            />
        </View>);
    }
}

This is almost the same as my previous render method. The only difference is that I’ve added a MapView component. It includes a mocked initialRegion property (latitude and longitude are static). I just want to get maps working, before adding a real initialRegion.

Unfortunately, when I tested this, I just saw an empty display, aside from the Maps title bar. Ugh! No error messages – just nothing! It was frustrating! Since there were no error messages, I wondered if there was something wrong with the display. The StaticMap example uses a MapView that is styled. So I copied that style, which just specified a width and height, and pasted it into my MapView, like this:

<MapView
    style={{width: 250, height: 250}}
    initialRegion={{
        ...

It worked!! I saw a map, but it looked cramped. I changed the style to {{flex:1}}, and the map filled the page! Excellent! Here’s the current behavior:

map of San Francisco in React Native app

The source code for this example – App.js, MoviesScreen.js, and MapsScreen.js – is available online.

If you found this interesting, click the subscribe button below! I write a new post about once a week.

Adding a simple navigation system to React Native – Part II

In my last post, I started the process of adding a navigation system to my app. The idea is to make it easy for the developer to add more pages to the app, and easy for the user to move around from page to page. I’m using the React Navigation community project. I’ve got the right packages installed and imported into my app. It’s clear to me from the documentation that I need to add a new component as a new page.

So I add a rudimentary, blank component. I’ve decided that this page will show a map of the theater that the user picks on the first page, so I name the file MapsScreen.js, with this source:

import React, { Component } from 'react';
import { Text, View } from 'react-native';

export default class MapsScreen extends Component {
    render() {
        return (<View style={[{ flex: 1, justifyContent: 'center' },
        { flexDirection: 'row', justifyContent: 'space-around', padding: 10 }]}>
            <Text>A map goes here</Text>
        </View>);
    }
}

Then I edit App.js to include a route to this component. It looks like this:

import { createAppContainer } from 'react-navigation';
import { createStackNavigator } from 'react-navigation-stack';
import MoviesScreen from './MoviesScreen';
import MapsScreen from './MapsScreen';

const MainNavigator = createStackNavigator({
    Movies: { screen: MoviesScreen },
    Maps: { screen: MapsScreen }
});

const App = createAppContainer(MainNavigator);

export default App;

It was more trouble than I’d like to add the React Navigation packages, but now that they’re in place, it’s super easy to add a new page. According to the documentation, I just need to call a function called navigate in order for a button click to take me to a new page.

I’ve already attached a method handleShowTheaterClick to my “GO” button. Here’s the short navigation code that needs to be added:

handleShowTheaterClick() {
    const { navigate } = this.props.navigation;
    navigate('Maps', {});
}

This is all it takes to make the “GO” button functional. I click the button, and I’m taken to my “Maps” screen, which looks like this:

current app functionality selecting movie and theater

Notice there’s a back arrow in the title bar. I can click that to get back to the previous screen. I hardly needed to do any work at all to get built-in navigation functionality. This is the beauty of code reuse.

The source code for this example – App.js, MoviesScreen.js, and MapsScreen.js – is available online.

If you found this interesting, click the subscribe button below! I write a new post about once a week.

Adding a simple navigation system to React Native – Part I

In previous posts (start here), I’ve written about building a movie app that a person can use to pick a movie from a list, and then click a button to display theaters where the movie is being shown.

Let’s add some code which allows our user to select a specific theater. Upon doing that, a button will appear which will take them to a new page in the app.

I only want the new button to appear when the list of theaters is populated. Here’s how I ensure that:

render() {
    if (this.state && !this.state.isLoading) {
        ...
        var theatersPicker = null;
        var goButton = null;
        if (this.state.nearbyTheaters) {
            ...
            theatersPicker = <Picker>{theaters}</Picker>
            goButton = <Button onPress={this.handleShowTheaterClick.bind(this)} title="Go"></Button>;
        }
        return <View>
        ...
            <Button onPress={this.handleClick.bind(this)} title="Find Movie Near Me"></Button>
            {theatersPicker}
            {goButton}
        </View>;

The goButton has been initialized to null. It will only be set to a Button if the theatersPicker has a list of theaters. I also added a new, empty method called handleShowTheaterClick (not shown here). The new button will call handleShowTheaterClick when clicked, but since it’s empty, it won’t do anything yet. We’ll get to that later.

You’ll find that the {goButton}line doesn’t show anything if goButton is null. That’s the UX that we want.

At this point, I want handleShowTheaterClick to cause the app to display a new page. This behavior is new! So far, everything has been happening in a single page. We’re getting into navigation, also known as “routing”. It’s how users get around the app from one page to another.

Navigation is not built into the default React Native install. You have to install it as a separate package. I followed the instructions for doing this as given in the documentation, but I got a lot of warnings. It took me about 30 minutes to resolve them, ugh! I kept trying and retrying… One of the errors that I saw in the Metro server terminal window looked like this:

error: bundling failed: Error: Unable to resolve module `react-navigation-stack` from `App.js`: react-navigation-stack could not be found within the project.

If you are sure the module exists, try these steps:
 1. Clear watchman watches: watchman watch-del-all
 2. Delete node_modules: rm -rf node_modules and run yarn install
 3. Reset Metro's cache: yarn start --reset-cache
 4. Remove the cache: rm -rf /tmp/metro-*

This turned out to be misleading. I thought that I need to use yarn, so I installed it, and started using it according the instructions. It could be that this works for some setups, but I’m running Ubuntu 16.04, and I’ve been using npm throughout this exercise. I should have stuck with npm. I learned that if a message mentioned yarn, I should just use npm instead. I suggest you do the same, unless you’re already using yarn to run other commands.

When I finally switched back to using npm, I found there were some warnings about “peer dependencies”. Here is one example:

...
npm WARN @typescript-eslint/eslint-plugin@1.13.0 requires a peer of eslint@^5.0.0 but none is installed. You must install peer dependencies yourself.

I wound up installing each peer dependency when I saw that one was missing. Then I had to go back and install the package which required the dependency again (so it seemed). In the end, I did all these installs in my project root (where App.js lives), in the order given:

npm install eslint@^5.0.0
npm install typescript@>=3.7.0-dev
npm install react-native-gesture-handler@*
npm install @react-native-community/masked-view@^0.1.1
npm install react-native-safe-area-context@^0.6.0
npm install react-navigation-stack
npm install react-navigation

Installing all that stuff was kind of a headache, and I was happy when it was finally done.

A quick read of the navigation documentation told me that I should only have navigation code in my App.js file. My existing application logic should go into a separate “screen” page. I’ll call this new page MoviesScreen.

To do this, I copied all the source of App.js into a new file, which I named MoviesScreen.js. Then I replaced HelloWorldApp in that source with MoviesScreen. Really, just replace

export default class HelloWorldApp extends Component {

with

export default class MoviesScreen extends Component {

Next, I replaced all the code in App.js with this simple navigation code:

import { createAppContainer } from 'react-navigation';
import { createStackNavigator } from 'react-navigation-stack';
import MoviesScreen from './MoviesScreen';

const MainNavigator = createStackNavigator({
    Movies: { screen: MoviesScreen }
});

const App = createAppContainer(MainNavigator);

export default App;

After doing this, I ran npx react-native run-android in my project root. I’ve learned that running npx react-native run-android sometimes clears up errors, although I’m not exactly sure when or if it’s required (might be a superstition type thing!).

The net result was that my app looked almost exactly the same as it did before! That’s expected, and good, because I only made infrastructure changes, and no functional ones. The only difference now is that there’s a title bar at the top of the display. It reads “Movies”. It’s not beautiful, but I’ll figure out how to deal with that later.

In testing the latest version of my app, I notice there’s one problem: when I select a theater from my “theaters” Picker, the value doesn’t stay selected. I know how to fix that – the same way that I did it for the “movies” Picker. I add similar code for that in MoviesScreen‘s render method:

...
theatersPicker = <Picker selectedValue={this.state.theater}
    onValueChange={(itemValue, itemIndex) => this.setState({ theater: itemValue })
    }>{theaters}</Picker>
current app functionality selecting movie and theater

Now, my app just works, except for one little problem. The GO button does nothing! Clicking on it should take the user to a new page. I’ll work that out in my next post.

The source code for this new version of App.js and for MoviesScreen are available online.

If you found this interesting, click the subscribe button below! I write a new post about once a week.

How to handle button clicks in React Native – Part II

Now that my Button responds to clicks (see previous post), I can program my React Native app to do something interesting in response to the click. Remember that my user is going to select a movie title from a dropdown list of movies, and then click a button to get a list of nearby movie theaters where the movie is showing.

There are REST APIs that will return the information we want. However, I’m not going to use them – at least not yet. Why not? Because I don’t want to go down a rabbit hole researching all the possible REST APIs. Even if it wouldn’t take very long, I don’t want to get sidetracked right now.

It’s important to note that if you’re building an app for business purposes, you should not lift one finger to code up your app until you’ve figured out what 3rd party services you need, and whether there is one available which will let you access it within your budget constraints. Suppose you’ve decided that you want to build an app which supplies turn-by-turn directions to every independent coffee shop across the world. This sounds kind of like a niche app, “Google Maps for coffee”. Well, just because the Google Maps app can do something like this, doesn’t mean that a REST service exists which will be available to you. Don’t make that kind of assumption! And even if the service does exist, it may not be affordable. Suppose it exists but there’s a charge for each API call… will your business model be profitable if you take into account the cost?

Since we’re not building a real app, but just messing around to become familiar with React Native, we don’t have to worry about the above business realities. Instead, I’m going to build a very simple REST API which returns a list of movie locations. By mocking the API, I can keep my flow of building the app working, and worry about third-party features later.

I can easily mock the API because I’ve got a domain name and a shared server. All I have to do is upload a PHP file with the following content:

<?php
$theaters = array();
$theaters[] = array( "name" => "Paris Theatre", "phone" => "(212)688-3800", "address1" => "4 West 58th Street", "city" => "New York", "state" => "NY", "zip" => "10019");
$theaters[] = array( "name" => "French Institute Alliance Francaise", "phone" => "(212)355-6160", "address1" => "55 East 59th Street", "city" => "New York", "state" => "NY", "zip" => "10022");
$theaters[] = array( "name" => "Roxy Cinema Tribeca", "phone" => "(212)519-6820", "address1" => "2 Avenue of the Americas", "city" => "New York", "state" => "NY", "zip" => "10013");

$out = json_encode($theaters);
echo $out;
?>

The list of movie theaters gives this static output:

[{"name":"Paris Theatre","phone":"(212)688-3800","address1":"4 West 58th Street","city":"New York","state":"NY","zip":"10019"},
{"name":"French Institute Alliance Francaise","phone":"(212)355-6160","address1":"55 East 59th Street","city":"New York","state":"NY","zip":"10022"},
{"name":"Roxy Cinema Tribeca","phone":"(212)519-6820","address1":"2 Avenue of the Americas","city":"New York","state":"NY","zip":"10013"}]

You can do that with a static HTML page if you want, it doesn’t matter. If you don’t have a server and don’t want to spend time setting one up, just reuse my URL.

I’m going to do a little copy/paste here. People rant against copying and pasting code, and there are reasons not to do it, but it’s actually pretty reasonable to do this in rapid application development of prototypes. The idea is “let’s just get this done, since the code might be thrown out tomorrow.” Yes, doing this will lead to technical debt. However, you may never have to pay off the debt, if the application is just an experiment. Further, you’re not at a stage where refactoring for reusability makes sense, yet. So I just copy the code that was used to fetch in componentDidMount, paste it, and make some small changes. Ditto for my setMovieState and handleMoviesResponse methods. Here’s what I wind up with:

...
setNearbyTheatersState(theaters) {
    this.setState({
        isLoading: false,
        nearbyTheaters: theaters,
    });
}
...
handleNearbyTheatersResponse(theaters, delay) {
    if (delay && delay > 0) {
        const timer = setTimeout(function () {
            this.setNearbyTheatersState(theaters);
        }.bind(this), delay);
    } else {
        this.setNearbyTheatersState(theaters);
    }
}
...
handleClick() {
    console.log("handleClick");
    return fetch('https://www.fullstackoasis.com/rnb/theaters.php')
        .then((response) => response.json())
        .then((responseJson) => {
            // TODO FIXME handle timeout / delay
            this.handleNearbyTheatersResponse(responseJson);
        })
        .catch((error) => {
            // TODO FIXME replace the red screen with something informative.
            console.error(error);
        });
}
...

This is pretty much a repeat of the REST call to get the list of movies, only now I’m calling my own mocked API, and setting a state parameter named “nearbyTheaters”. Looks good! But when I click the button, I see a RedBox which reads "TypeError: _this2.handleNearbyTheatersResponse is not a function. (In '_this2.handleNearbyTheatersResponse(responseJson)', '_this2.handleNearbyTheatersResponse' is undefined)".

You might think that copy/paste didn’t work so well for us… but I’d disagree. It got us to an error faster than if we wrote all the code from scratch. Now it’s just a matter of figuring out what’s gone wrong.

To help debug this problem, I add a comment just before the call to this.handleNearbyTheatersResponse:

handleClick() {
    console.log("handleClick");
    return fetch('https://www.fullstackoasis.com/rnb/theaters.php')
        .then((response) => response.json())
        .then((responseJson) => {
            // TODO FIXME handle timeout / delay
            console.log(this);
            this.handleNearbyTheatersResponse(responseJson);
        })
        .catch((error) => {
            // TODO FIXME replace the red screen with something informative.
            console.error(error);
        });
}

Then I click the button again. I see the following logged in my terminal:

{"accessibilityLabel": undefined, "accessibilityRole": "button", "accessibilityStates": [], "background": {"attribute": "selectableItemBackground", "type": "ThemeAttrAndroid"}, "children": <RCTView style={[[Object]]}><ForwardRef(Text) style={[Array]}>FIND MOVIE NEAR ME</ForwardRef(Text)></RCTView>, "disabled": undefined, "hasTVPreferredFocus": undefined, "nextFocusDown": undefined, "nextFocusForward": undefined, "nextFocusLeft": undefined, "nextFocusRight": undefined, "nextFocusUp": undefined, "onPress": [Function handleClick], "testID": undefined, "touchSoundDisabled": undefined}

At this point I was a little puzzled; it looked to me like this was a reference to my Button, and not my app. I expected the latter. Here’s the relevant part of the render method, again:

<Button onPress={this.handleClick} title="Find Movie Near Me"></Button>

In fact, JavaScript’s correct behavior is a little hazy to me. I could find discussions that made claims about the standard behavior, but no references to the specification, and I didn’t go hunting for the official answer. Let’s not waste time tracking this down right now. Let’s assume this refers to the Button being pressed. That may be useful in some cases, but in my case, I want this to reference the app itself so that I can call handleNearbyTheatersResponse within the handleClick method.

I took a stab at fixing the problem by binding the Button‘s onPress method to this:

<Button onPress={this.handleClick.bind(this)} title="Find Movie Near Me"></Button>

That worked! Once I do that, clicking the button does not produce an error. The call to setState in setNearbyTheatersState is made. I verified this by adding a new Picker which is populated with theater listings if this.state.nearbyTheaters exists.

Here’s the new code in my render method:

if (this.state && !this.state.isLoading) {
    let items = [];
    var length = this.state.dataSource.length;
    for (var i = 0; i < length; i++) {
        var item = this.state.dataSource[i];
        // Really, really important to not put quotes around these braces:
        items.push(<Picker.Item label={item.title} value={item.title} key={item.id} />);
    }
    var theatersPicker = null;
    if (this.state.nearbyTheaters) {
        let theaters = this.state.nearbyTheaters.map(function (t, i) {
            return <Picker.Item label={t.name} value={t.name} key={i} />;
        });
        theatersPicker = <Picker>{theaters}</Picker>
    }
    return <View>
        <Picker selectedValue={this.state.movie}
            onValueChange={(itemValue, itemIndex) =>
                this.setState({ movie: itemValue })
            }>
            {items}
        </Picker>
        <Button onPress={this.handleClick.bind(this)} title="Find Movie Near Me"></Button>
        {theatersPicker}
    </View>;
    ...

The source code for this experiment is available online.

If you found this interesting, click the subscribe button below! I write a new post about once a week.

How to handle Button clicks in React Native – Part I

I’ve been building an app which lets me find movies “near me” using mocked data. In my last post, I added a progress loader to display when there’s networking latency. I have a dropdown with a list of movie names in my app, and the user can select any movie in the list. Let’s suppose the user wants to see one of these movies, and would like to see a list of closest locations where the movie is showing. We’re going to need a button so that the user can click it to get results.

You might wonder why you shouldn’t just load results when the user selects a movie name from the Picker. Well, it depends on how you feel about UX. Some apps will immediately give you results when an element is selected from a dropdown. However, just because a user selects an item doesn’t mean that is their final choice. Maybe they lifted their finger too soon, and wound up selecting something that they didn’t want. Or, maybe they’re just thinking about their different options.

My personal preference for dealing with a select is to add a confirmation button when the result of selecting an item from the Picker is a resource intensive task, like hitting a database or making a network call. You don’t want to waste resources every time a user selects an item from a dropdown.

So in this example, making a network call should only happen if the user clicks a button. Let’s add a button to our view – just below the Picker:

import { View, Picker, ActivityIndicator, Button } from 'react-native';
...
render() {
    ...
    return <View>
        <Picker...
        </Picker>
        <Button title="Find Movie Near Me"></Button>
    </View>;
    ...
}

You can’t add a Button without a title; you’ll see a warning if you do.

The button is probably the easiest control to add in React Native! Here’s what I see after I add just a couple of lines:

However, clicking the button is a different story. If you’re used to web development, you might expect an “onClick” property. Nope, it’s onPress. And you can’t just add a method like this:

...
handleClick() {
    console.log("Handled");
}
...
    <Button onPress="handleClick" title="Find Movie Near Me"></Button>
...

The app won’t complain if you do this, but if you click the button you’ll see an error: "TypeError: this.props.onPress is not a function. (In 'this.props.onPress(e);, 'this.props.onPress' is "handleClick")".

The method handleClick is actually a function of “this”, the app. So you have to reference it correctly. It doesn’t help to replace onPress="handleClick" with onPress="this.handleClick" because anything in quotes is a “literal”, treated as a string, in JSX. Instead, you have to do as follows:

...
handleClick() {
    console.log("handleClick");
}
...
    <Button onPress={this.handleClick} title="Find Movie Near Me"></Button>
...

Now the button responds to clicks, as you can see below!

If you found this interesting, click the subscribe button below! I write a new post about once a week.

How to add a progress loader in React Native

In my last post, I took a look at how network latency might affect my React Native app, and I didn’t like what I saw: a pure white screen for several seconds. How is a user supposed to “react” to that? (Pun intended!)

It was pretty clear what was happening. My app’s render method just shows a blank view if it has no state. Here’s the render method:

render() {
    if (this.state && !this.state.isLoading) {
        ...
    } else {
        return (<View></View>);
    }
}

That’s my white screen. There’s a super simple fix:

render() {
    if (this.state && !this.state.isLoading) {
        ...
    } else {
        return (<View><Text>waiting...</Text></View>);
    }
}

Now there’s text which reads “waiting…” at the top. That’s okay for a hobby app. You’re probably going to want that to look a little nicer, however. How about a progress indicator? Something that shows “we’re working on this!”. React Native comes with an ActivityIndicator which does what we want. I’m going to grab some of the code from their documentation and add it to my app. In the render method below, I’ve replaced my “waiting” text with an ActivityIndicator (and I’ve shown the import statement to remind you to add that as well).

import { View, Text, Picker, ActivityIndicator } from 'react-native';
...
render() {
    if (this.state && !this.state.isLoading) {
        ...
    } else {
        return (<View><ActivityIndicator size="large" color="#0000ff" /></View>);
    }
}

That helps some, but the progress indicator still doesn’t look very nice. It’s placed at the very top of the screen, like this:

It turns out to be pretty easy to move the ActivityIndicator to the center of the screen. You just have to add some style components to its container View, like this:

render() {
    if (this.state && !this.state.isLoading) {
        ...
    } else {
        return (<View style={[{ flex: 1, justifyContent: 'center' },
            { flexDirection: 'row', justifyContent: 'space-around', padding: 10 }]}>
            <ActivityIndicator size="large" color="#0000ff" />
        </View>);
    }
}

Here’s the result:

With very little trouble at all, I’ve now got a slick little widget to let people know that something is going on. It’s not really enough, though. What if the network request times out, and I get an error? I’ll have to delve into that more deeply later. To remind me to do this, I’ll add a TODO/FIXME comment in the code, and open an issue in my issue tracker.

If you found this interesting, click the subscribe button below! I write a new post about once a week.

How to test networking latency in React Native

In my previous post, I tested my React Native app to see what would happen if a fetch to get data from a URL resulted in an error. In my experience, URLs don’t just fail with a 404 error, however. Sometimes there’s a time delay in getting a response from a URL. This is sometimes called latency or a lag.

I wanted to see how my app would behave if the URL that it fetched had a long delay in responding. But I didn’t have a URL that would reliably take a long time to respond! How could I mimic this situation? That’s what I’ll address in this blog post.

My app does the fetch in the componentDidMount method, and then sets state to update the display, like this:

componentDidMount() {
    return fetch('https://facebook.github.io/react-native/movies.json')
        .then((response) => response.json())
        .then((responseJson) => {
            this.setState({
                isLoading: false,
                dataSource: responseJson.movies,
            });
        })
        .catch((error) => {
            // TODO FIXME replace the red screen with something informative.
            console.error(error);
        });
}

My initial attempt at adding a delay was to set a timeout around the setState call, but this resulted in an error. To simplify the code and make it easier to test, I moved the call to setState into a separate, reusable chunk of code, like this:

setMovieState(movies) {
    this.setState({
        isLoading: false,
        dataSource: movies,
    });
}

So then my componentDidMount becomes:

componentDidMount() {
    return fetch('https://facebook.github.io/react-native/movies.json')
        .then((response) => response.json())
        .then((responseJson) => {
            this.setMovieState(responseJson.movies);
        })
        .catch((error) => {
            // TODO FIXME replace the red screen with something informative.
            console.error(error);
        });
}

That looks a little cleaner, but I still need to do something to introduce a lag.

Let me write a new little piece of code which calls the method setMovieState after a specified delay:

handleMoviesResponse(movies, delay) {
    if (delay && delay > 0) {
        const timer = setTimeout(function () {
            this.setMovieState(movies);
        }.bind(this), delay);
    } else {
        this.setMovieState(movies);
    }
}

If there’s no delay, the state will be set immediately. If the method is called with a delay, then the state is not updated until after the input delay.

If you’re wondering what bind(this) is about, you may not be too familiar with JavaScript. A short answer is that the anonymous function that is passed to setTimeout uses this inside it, and that function needs to know what this is (this is my app which contains the method setMovieState.)

Finally, instead of calling setMovieState from componentDidMount, I call handleMoviesResponse, like this:

this.handleMoviesResponse(responseJson.movies, 5000);

Now that I’ve done this, I can see that when my app opens, I just see a plain white screen for 5 seconds, and then I see my movie titles dropdown (Picker) appear at the top of the page. It’s not a crash, but it seems like a bad user experience. In my next post, I’ll look at how to fix that.

blank screen when there’s network latency

If you found this interesting, click the subscribe button below! I write a new post about once a week.

What causes “SyntaxError: JSON Parse error: Unrecognized token ‘<'" in React Native?

TL;DR: In React Native, you will get “SyntaxError: JSON Parse error: Unrecognized token ‘<‘” if your URL returns a 404 error, or in general if the content is not a JSON string.

In a recent post, I showed how to display a list of movies that had been fetched from a REST API. It worked great, but I wondered what would happen to my app’s user if their device was offline, or if the REST API ever went down. To mimic this behavior, I changed the URL by adding the number 1 at the end of it, like this: “https://facebook.github.io/react-native/movies.json1”.

And here’s what I saw in the emulator:

SyntaxError: JSON Parse error: Unrecognized token ‘<‘

The red screen says “SyntaxError: JSON Parse error: Unrecognized token ‘<‘”. That may be confusing, although if you work with REST APIs for any time, you’ll soon come to recognize what it means. Meantime, how do we investigate this?

When I load up this test URL in a web browser, I see content which looks like this:

<!DOCTYPE html>
<html>
  <head>
  ...Page not found...
</html>

It’s a fancy 404 error page. That explains why response.json barfs on this; it’s not JSON. Your app expected a JSON string. It tried to parse the string into a JavaScript object, and couldn’t handle a non-JSON string. As a reminder, here’s that fetch call:

componentDidMount() {
    return fetch('https://facebook.github.io/react-native/movies.json1')
        .then((response) => response.json())
        .then((responseJson) => {
...
        })
        .catch((error) => {
            // TODO FIXME replace the red screen with something informative.
            console.error(error);
        });
}

In the longer term, I will want to replace that red screen of death with a nice error page which instructs the user what to do. I’m still developing my application, however, and as a dev, I’d rather see the stack trace for errors like this when they occur.

So to deal with this, I’ll do two things: 1) I’ll add a “TODO FIXME” note in my code. When I’m cleaning up code in the end stages of development, I know to look for these types of comments which indicate work still needs to be done. 2) I’ll open an issue in my issue tracker which will let everyone on my team know that there’s something that still has to be handled in building the application. I’ll bring this to the attention of anyone who needs to know (a project manager, perhaps). The project manager may assign a designer to build a page with some graphics or specific text to display to the user in case of this error.

If you found this interesting, click the subscribe button below! I write a new post about once a week.

How to add a select element in React Native

TL;DR: The React Native Picker component is the equivalent of the HTML select element.

In my last post, I used fetch to download a list of movie titles. However, I only displayed the list by using a debugging tool. My app user will want to see this list, and even better, may want to select something off the list. The commonly used web widget which does this is the HTML <select> element. In React Native, the corresponding widget is a Picker.

You can add a Picker to your render method very easily, like this:

import { Picker } from 'react-native';
...
render() {
    return <View>
        <Picker>
        </Picker>
    </View>;
}

That’s a start, but it doesn’t display my data, and it doesn’t look very interesting. We’ve got a picker with no functionality which looks like this:

Notice the little upside-down triangle in the upper right corner, which indicates the Picker has loaded.

In order to populate the Picker with data, we need to get access to the list of movies inside the render method. Fortunately, I already used setState to make the movie data persist in my app’s state (see previous post). That means I can use it in render, like this:

render() {
    let items = [];
    var length = this.state.dataSource.length;
    for (var i = 0; i < length; i++) {
        var item = this.state.dataSource[i];
        // Really, really important to not put quotes around these braces:
        items.push(<Picker.Item label={item.title} value={item.title} key={item.id} />);
    }
    return <View>
        <Picker>
        </Picker>
    </View>;
}

Uh oh, wait, as soon as I added this code, I saw the red screen of death in the emulator. The error message read “TypeError: null is not an object (evaluating ‘this.state.dataSource’). This error is located at: in HelloWorldApp (at renderApplication.js:40)…”

The problem is that I tried to reference this.state.dataSource before it had been set. The render method might be called before fetch runs. We don’t know when our fetch statement will be called, or even if it is successful. We need to add code in render which handles the case where this data is not yet available.

Here’s the new render code which skips doing anything with dataSource if it is not found:

render() {
    if (this.state && !this.state.isLoading) {
        let items = [];
        var length = this.state.dataSource.length;
        for (var i = 0; i < length; i++) {
            var item = this.state.dataSource[i];
            items.push(<Picker.Item label={item.title} value={item.title} key={item.id} />);
        }
        return <View>
            <Picker>
            </Picker>
        </View>;
    } else {
        return ( <View></View>);
    }
}

That fixes the error, but the Picker is still empty. But in anticipation of needing a list of movies, I’ve already got a list of Picker.Item objects loaded into an array using JSX (items.push(<Picker.Item....). It turns out to be very easy to add these to the Picker. Just add items in braces inside the Picker tags, like this:

<Picker>
{items}
</Picker>

As soon as this is done, like magic, the Picker reflects the changes. A dropdown appears with the text Star Wars selected by default.

Now you can click the dropdown, and try to select a different movie, but what you’ll find is that the dropdown is stuck at Star Wars – you can’t select anything else!

For a finishing touch, let’s make it possible for the user to select a different movie. The Picker documentation tells us how to do this by adding the selectedValue attribute and the onValueChange methods like this:

selectedValue={this.state.movie}
onValueChange={(itemValue, itemIndex) =>
    this.setState({ movie: itemValue })
}

After adding this I can select any movie, and that movie stays selected! Here’s my final code:

import React, { Component } from 'react';
import { View, Picker } from 'react-native';

export default class HelloWorldApp extends Component {
    componentDidMount() {
        return fetch('https://facebook.github.io/react-native/movies.json')
            .then((response) => response.json())
            .then((responseJson) => {
                console.log(responseJson);
                this.setState({
                    isLoading: false,
                    dataSource: responseJson.movies,
                }, function () {

                });

            })
            .catch((error) => {
                console.error(error);
            });
    }

    render() {
        if (this.state && !this.state.isLoading) {
            let items = [];
            var length = this.state.dataSource.length;
            for (var i = 0; i < length; i++) {
                var item = this.state.dataSource[i];
                // Really, really important to not put quotes around these braces:
                items.push(<Picker.Item label={item.title} value={item.title} key={item.id} />);
            }
            return <View>
                <Picker selectedValue={this.state.movie}
                    onValueChange={(itemValue, itemIndex) =>
                        this.setState({ movie: itemValue })
                    }>
                    {items}
                </Picker>
            </View>;
        } else {
            return (<View></View>);
        }
    }
}

If you found this interesting, click the subscribe button below! I write a new post about once a week.

Simple debugging tool in React Native

TL;DR: If you want to debug React Native code really quickly, console.log and console.warn can help.

In my previous post, I described how I ported the React Clock app to React Native. This is the code for my simple app:

import React, { Component } from 'react';
import { Text, View, Button } from 'react-native';
import Clock from './Clock';

export default class HelloWorldApp extends Component {
    render() {
        return (
            <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
                <Text style={{ fontWeight: 'bold', padding: 10 }}>Hello, world!</Text>
                {/* padding does not work with Button!! */}
                <Button style={{ fontWeight: 'bold', padding: 40 }} title="Click me" >Click Me!</Button>
                {/* Since padding does not work with Button, we add an empty text area */}
                <Text>{""}</Text>
                <Clock />
            </View>
        );
    }
}

For my next project, I decided to do something more realistic. I wanted to figure out how to fetch data from a REST API.

I already had the Android emulator started (see previous post). A quick look at the React-Native networking documentation told me that doing a fetch should be a piece of cake. Because I didn’t want to copy their entire sample app, but just reuse their fetch call, I copied the componentDidMount method, and pasted it into my application above the render method:

componentDidMount(){
return fetch('https://facebook.github.io/react-native/movies.json')
    .then((response) => response.json())
    .then((responseJson) => {

    this.setState({
        isLoading: false,
        dataSource: responseJson.movies,
    }, function(){

    });

    })
    .catch((error) =>{
    console.error(error);
    });
}

(The componentDidMount method may be familiar to you from React.js development.)

I didn’t see any errors when I did this, but I also couldn’t tell whether the fetch method had worked! If I had been building this app using JavaScript in a web browser, I could have quickly checked the results by adding a console.log statement to print out responseJson. I tried this, in fact, but nothing noticeable happened onscreen when I made my change. It took a little while, but I finally noticed that my statements were being logged in the terminal window that was running the Metro server (where I’d run the npm start command)! It took me a while before I noticed this because I’m not usually looking at the terminal unless I’m trying to debug a problem.

A quick search also told me that I could use console.warn to display text on the emulator’s screen. I added console.warn(responseJson); just above the setState call, and I could see that the method had succeeded, and I could also see part of the responseJson content in the YellowBox which appeared. Clicking on this YellowBox warning gave me a fullscreen view of the JSON.

Probably it’s a bad idea to display debug messages using console.warn, but if I were debugging on a device without the help of Metro server, I think console.warn would come in handy.

If you found this interesting, click the subscribe button below! I write a new post about once a week.