Components that aren’t declared by this library (Ex: Markers, Polyline) must not be children of the MapView component due to MapView’s unique rendering methodology. Have your custom components / views outside the MapView component and position absolute to ensure they only re-render as needed. Example: Bad:
The initial error message may also say “The specified child already has a parent. You must call removeView() on the child’s parent first”. The stack trace will be similar, but not identical.
The documentation explains how to fix this problem: simply move the improper components outside of MapView. I’ve got a YouTube video that shows the issue, and a simple project at github that demos the error and the fix. I hope this blog post helps you fix your React Native bug!
I’ve been following a slightly out-of-date Android course in order to polish my Android skills. In the course, the instructor discusses the “instant run” button to hot swap code while debugging your app. This button has a lightning bolt icon in the menu.
That didn’t look familiar! When I tried to follow along, I found that my version of Android Studio (3.6) doesn’t have that lightning bolt. I don’t know why they got rid of that lightning bolt, since I think that was a good, easy to recall icon. However, the feature itself is not gone. Now, it looks like a stacked set of horizontal lines. This says nothing to me (it looks kind of like a formatting button – a sheet of paper with lines of text, perhaps).
Anyway, when you hover over it, you’ll see the text “Apply Code Changes”. Here’s a screenshot with the old version on top, and the newer version at the bottom:
Apply Code Changes in Android Studio (aka Hot Code Swap)
If you’ve just switched to the latest version of Android Studio (3.6), and you don’t see any option to generate a signed APK, you should take a look at the “Build: Sync” window in the IDE. Let’s look at such a case, here:
Here we have an Android Studio project, but we cannot see the “Generate Signed Bundle / APK…” menu item. How did this happen?
In this case, I opened Android Studio, and closed all projects. Then, I clicked the link to “Import an Android code sample”. I chose Cloud Doorbell, and when the project opened, I saw a number of errors. You can see those errors in the screenshot above. I wanted to build an APK, but when I clicked the “Build” menu, that menu item was missing. It was frustrating because there was no mention in the menu about what was wrong.
However, under the “Build: Sync” window, I saw:
ERROR: Failed to find Build Tools revision 26.0.2
Install Build Tools 26.0.2 and sync project
Upgrade plugin to version 3.6.1 and sync project
Those two lines were links, and when I clicked the first one, the latest Build Tools were installed. I believe it also did a gradle sync on its own.
So, if you run into this problem, take a look at that “Build: Sync” window. If it’s not open already, you can open it by clicking on the “Build” tab at the bottom of the Android Studio IDE. If you see errors there, fix them before trying to do anything else. Otherwise, you may wind up banging your head against a wall… 🙂
Here’s a quick tutorial to get you started playing an mp3 file in an Android app that was built using React Native. This tutorial was developed using Linux (Ubuntu 16.04), React Native 0.61.5, and the React Native Sound package 0.11.0.
First, make your project directory. Let’s call it AudioTmp. In a terminal window, do this:
npx react-native init AudioTmp
cd AudioTmp
All of the following commands assume you’ve got your terminal window open, and you’re standing in the root directory of your project (in AudioTmp, where App.js is located).
You’ll need some extra packages, so run these install commands:
Next, make the utils directory under your project root:
mkdir utils
Add a new file called SoundPlayer.js in the utils directory. Here’s the source for that:
/**
* Really simple demo of https://www.npmjs.com/package/react-native-sound
*/
const Sound = require('react-native-sound');
/**
* Audio credit:
* https://freemusicarchive.org/music/John_Bartmann/Public_Domain_Soundtrack_Music_Album_One/earning-happiness
* "Earning Happiness" by John Bartmann licensed under a CC0 1.0 Universal License
* https://creativecommons.org/publicdomain/zero/1.0/
*/
const FILENAME = 'john_bartmann_04_earning_happiness.mp3';
class SoundPlayer {
constructor(props) {
console.log("SoundPlayer constructor");
};
stopSound() {
console.log('SoundPlayer.stopSound');
this.whoosh.stop();
this.whoosh.release();
}
playSound() {
console.log('SoundPlayer.playSound');
let me = this;
me.whoosh = new Sound(FILENAME, Sound.MAIN_BUNDLE, error => {
if (error) {
console.log('failed to load the sound', error);
return;
}
// loaded successfully
console.log(
'duration in seconds: ' +
me.whoosh.getDuration() +
'number of channels: ' +
me.whoosh.getNumberOfChannels()
);
me.whoosh.play(success => {
if (success) {
console.log('successfully finished playing');
} else {
console.log('playback failed due to audio decoding errors');
}
});
});
}
}
export default SoundPlayer;
Replace the code in App.js with this code:
/**
* Simple demo of https://www.npmjs.com/package/react-native-sound
* The app plays an included mp3 file when the user clicks on 'Play' button.
* It stops the playback when the user clicks on the 'Stop' button.
*/
import React from 'react';
import {
StyleSheet,
View,
Text,
Button
} from 'react-native';
import {
Colors
} from 'react-native/Libraries/NewAppScreen';
/** Keep all sound functionality in separate utils area */
import SoundPlayer from './utils/SoundPlayer';
class App extends React.Component {
constructor(props) {
super(props);
this.soundPlayer = new SoundPlayer();
}
onPlayPress() {
console.log('onPlayPress method');
this.soundPlayer.playSound();
}
onStopPress() {
console.log('onPlayPress method');
this.soundPlayer.stopSound();
}
render() {
return (
<View style={styles.body}>
<Text style={styles.sectionTitle}>Click button to Play</Text>
<View style={styles.buttonStyle}>
<Button style={styles.buttonStyle}
onPress={this.onPlayPress.bind(this)}
title="Play"
/>
</View>
<Text style={styles.sectionTitle}>Click button to Stop</Text>
<View style={styles.buttonStyle}>
<Button buttonStyle={styles.buttonStyle}
onPress={this.onStopPress.bind(this)}
title="Stop"
/>
</View>
</View>
);
}
};
const styles = StyleSheet.create({
body: {
backgroundColor: Colors.white,
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
backgroundColor: 'gainsboro'
},
sectionTitle: {
fontSize: 24,
fontWeight: '600',
color: Colors.black,
padding: 20,
backgroundColor: 'gainsboro' /* See https://reactnative.dev/docs/colors */,
},
buttonStyle: {
padding: 20,
backgroundColor: 'gainsboro' /* See https://reactnative.dev/docs/colors */,
}
});
export default App;
Run the Android Emulator:
emulator -no-snapshot -avd Galaxy_Nexus_API_28
Start your Metro server:
npx react-native start
Install the app in your Android Emulator:
npx react-native run-android
I see these warnings at my command line, but everything seems to work okay:
error React Native CLI uses autolinking for native dependencies, but the following modules are linked manually:
- react-native-sound (to unlink run: "react-native unlink react-native-sound")
This is likely happening when upgrading React Native from below 0.60 to 0.60 or above. Going forward, you can unlink this dependency via "react-native unlink <dependency>" and it will be included in your app automatically. If a library isn't compatible with autolinking, disregard this message and notify the library maintainers.
Read more about autolinking: https://github.com/react-native-community/cli/blob/master/docs/autolinking.md
info Running jetifier to migrate libraries to AndroidX. You can disable it using "--no-jetifier" flag.
Jetifier found 875 file(s) to forward-jetify. Using 8 workers...
Then it just works! You can hit the Play and Stop buttons to play the audio.
One remark: when I initially created this project, I didn’t use the link command, because the React Native documentation indicated it was not needed for version 0.60. However, that turned out to be untrue! You can save yourself time by doing it the way I’ve done it in this tutorial. When using link, the demo worked immediately, and I didn’t have to make any changes to gradle files.
Trying to build a REST API can be really frustrating. You install Express.js with mongoose or mongodb, and then create a schema, just following the instructions, and you think everything should work. You set up Postman to test the API, hit the API, and Postman stays at “sending request..” for a while. You wait… and wait… and then eventually you see the message “Could not get any response”.
Postman Could not get any response
Sometimes, it works fine to set everything up all at once. But, it can often happen that there are just too many moving parts. When that happens, and something breaks, it can be very difficult to figure out the point of failure.
As an example, suppose you have an Express router which handles a POST request like this using a mongoose model:
That won’t tell you the whole story, however. If you see “awaiting save…” but don’t see “save is finished”, then doing this has helped you locate the problem – mongoose is hanging.
At this point, you may be better off writing a small Node.js test script which will just exercise the code related to mongoose, independently of Express. That way, you can debug the problem without having to deal with Express code or restart your Express server.
Here’s some sample code. You’ll have to adjust this to your own specific case, but the idea is to put this code into a script (test.js) and run it from the command line with node test.js:
"use strict";
const mongoose = require('mongoose');
const Kitten = require('./models/Kitten');
mongoose.connect(process.env.MONGO_DB_URL, { useNewUrlParser: true });
var db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
// Set up some mock user data
const fluffy = new Kitten({ name: 'fluffy' });
// When the db is open, 'save' this data.
db.once('open', async function () {
// we're connected!
console.log("db opened... going to save the kitten");
await fluffy.save();
console.log("printing fluffy");
console.log(fluffy);
db.close();
});
// Print that db connection has been closed.
db.once('close', function () {
console.log('close');
});
Notice that the code references an environment variable, process.env.MONGO_DB_URL. You’ll need to set that in your terminal. In Linux, you can do that by using the export command: export MONGO_DB_URL=mongodb://127.0.0.1:27017/myappdb.
TL;DR: If you can’t quickly see why your DROP TABLE statement is failing, check for typos in your table names.
In MySQL, foreign key constraints can stop you from dropping a table. But that’s not the only thing that can go wrong.
Let’s create a simple example using MySQL, in order to demo the problem. As the root user, sign in to MySQL using your favorite client. I like to use the command line mysql client, like this: mysql -u root -p.
Next, create a new test database, CREATE DATABASE hamsters;
Then:
USE hamsters;
create table toys (
toy_id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
PRIMARY KEY ( toy_id )
);
create table toy_makers (
toy_maker_id INT NOT NULL AUTO_INCREMENT,
toy_id INT,
name VARCHAR(25),
FOREIGN KEY ( toy_id ) REFERENCES toys ( toy_id ),
PRIMARY KEY ( toy_maker_id )
);
After doing this, verify the tables exist.
mysql> show tables;
+--------------------+
| Tables_in_hamsters |
+--------------------+
| toy_makers |
| toys |
+--------------------+
2 rows in set (0.00 sec)
When experimenting with MySQL, you’ll often want to be running CREATE and DROP scripts. Here is my DROP script:
USE hamsters;
DROP TABLE IF EXISTS toys;
DROP TABLE IF EXISTS toy_makes;
Let’s look at the output:
mysql> USE hamsters;
Database changed
mysql> DROP TABLE IF EXISTS toys;
ERROR 1217 (23000): Cannot delete or update a parent row: a foreign key constraint fails
mysql> DROP TABLE IF EXISTS toy_makes;
Query OK, 0 rows affected, 1 warning (0.00 sec)
Well, that’s a bit frustrating, but it’s actually a pretty simple problem. The primary key toy_id in table toys is referenced as a foreign key in table toy_makers. You cannot drop toys first, even though there’s no data in either table! You’ll have to drop any tables that reference toys.toy_id as a foreign key prior to dropping toys. In my case, the solution is simple. Just do a switcheroo on our DROP statements, and DROP the toys table last:
USE hamsters;
DROP TABLE IF EXISTS toy_makes;
DROP TABLE IF EXISTS toys;
Here’s the output:
mysql> USE hamsters;
Database changed
mysql> DROP TABLE IF EXISTS toy_makes;
Query OK, 0 rows affected, 1 warning (0.00 sec)
mysql> DROP TABLE IF EXISTS toys;
ERROR 1217 (23000): Cannot delete or update a parent row: a foreign key constraint fails
If you’re looking at this, you might be confused. I switched the order in which the tables were dropped, but, hm, I’m still getting the same complaint! Let’s look at our database by using the SHOW TABLES command:
mysql> SHOW TABLES;
+--------------------+
| Tables_in_hamsters |
+--------------------+
| toy_makers |
| toys |
+--------------------+
2 rows in set (0.00 sec)
At this point, I hope you’ve noticed the problem! There was a typo in my DROP TABLE script. The command DROP TABLE IF EXISTS toy_makes; ran without an error because it has the option IF EXISTS in it. There’s no table called toy_makes here – the table is called toy_makers.
What if we just run DROP TABLE toy_makes? That does produce an error: ERROR 1051 (42S02): Unknown table 'hamsters.toy_makes'.
Let’s fix my script:
USE hamsters;
DROP TABLE IF EXISTS toy_makers;
DROP TABLE IF EXISTS toys;
Both lines run successfully now, with output like Query OK, 0 rows affected (0.97 sec).
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.
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:
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:
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:
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:
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:
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:
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.
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:
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:
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:
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.
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:
[{"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:
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:
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>;
...