Category Archives: Software Principles

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.

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 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 comment your MySQL database

Technically, I’m not a database administrator. Practically, I write SQL statements on a regular basis. I also create new tables and add columns to existing tables with some frequency. And I’ve noticed that most people don’t “comment” their databases. This is probably fine for scratch projects that never make it to production and are only ever maintained by one person. However, if there’s the slightest chance that someone else might be maintaining the database that you created, it’s a good idea to comment your database, just as a developer will comment their code.

Here’s an example of how you do it in MySQL:

CREATE TABLE `prices` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'table index',
  `ticker` varchar(16) NOT NULL COMMENT 'stock ticker symbol, e.g. GOOG',
  `day` date NOT NULL COMMENT 'date of price info for this stock ticker',
  `open` DECIMAL(20, 8) DEFAULT NULL COMMENT 'opening price for this stock',
  `close` DECIMAL(20, 8) DEFAULT NULL COMMENT 'closing price for this stock',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='record of opening and closing prices for stock' AUTO_INCREMENT = 1000;

If you’re adding a column to a table, you can comment it at the same time, like this:

ALTER TABLE prices ADD COLUMN `volume` int(11) DEFAULT 0 COMMENT 'trading volume for this stock' AFTER `close`;

In my example, the columns and their names might seem obvious to people who are remotely savvy with finance. But just because you know something about finance, doesn’t mean the next dev will have this somewhat specialized knowledge. Your comment can help them find the information they need to understand what the column means and how to use it. Furthermore, in many business applications, database schemas may consist mostly of fields which only have meaning within the business itself. Comments on those fields can be a very handy source of documentation when the original developers are long gone.

As an aside, my prices table is only an example table for holding stock prices. You can see I really went overboard in allowing 8 decimal places for a stock price. Let’s just say I “future-proofed” it. Here’s a sample INSERT statement:

INSERT INTO prices (ticker, day, open, close) values ('GOOG', '2059-12-21', 401200022445.09787456, 401200023899.09787456);
SELECT * FROM prices;
+------+--------+------------+-----------------------+-----------------------+
| id   | ticker | day        | open                  | close                 |
+------+--------+------------+-----------------------+-----------------------+
| 1000 | GOOG   | 2059-12-21 | 401200022445.09787456 | 401200023899.09787456 |
+------+--------+------------+-----------------------+-----------------------+
1 row in set (0.00 sec)

And here’s the CREATE TABLE statement as viewed by a developer trying to figure things out:

SHOW CREATE TABLE prices;
...
| prices | CREATE TABLE `prices` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'table index',
  `ticker` varchar(16) NOT NULL COMMENT 'stock ticker symbol, e.g. GOOG',
  `day` date NOT NULL COMMENT 'date of price info for this stock ticker',
  `open` decimal(20,8) DEFAULT NULL COMMENT 'opening price for this stock',
  `close` decimal(20,8) DEFAULT NULL COMMENT 'closing price for this stock',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1001 DEFAULT CHARSET=utf8 COMMENT='record of opening and closing prices for stock' |
1 row in set (0.00 sec)

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

Quirks of HTML forms – can you GET and POST at the same time?

TL;DR: You can effectively GET and POST information using a single form. The trick is to set a query string in your action – the form is still POSTed, but the server will see the query string as GET data. Also, if a user comes to your web page with a query string already set, that query string will be retained when POSTing a form, as long as you use the empty action (action="") for your form.


HTML forms are a rudimentary part of web deveopment, but you can be productive implementing them without a lot of specialized knowledge. That’s a plus and a minus, since there are some “gotchas”. Here is a quick review of some things that might have escaped your notice.

an empty action attribute?

You can add a form to a page simply by enclosing some content with the <form> tag. This tag does not require any attributes. Here’s an example:

<form>
  <div><label>topping: <input></label></div>
</form>

In this example, nether the method nor the action attribute are specified. What happens in this case? Can this form be submitted? Here’s a sample page which has only this form on it so you can experiment with it. (The quick answer is yes, the form submits in Chrome, but no actual data is submitted, so the form is not useful.)

In this next example, the form also has no attributes. However, the input is named – it has a name attribute, like this:

<input name="topping" value="pepperoni">

Take a look to see if this example with a named input works to send request data to the server. (It does! You will have to type something into the form and hit enter for it to work.)

Here’s a page which submits two variables, “topping” and “crust”, using a blank form (<form>), again. Does it work? Try it! (The quick answer is “no!” – even though both inputs are named.) I only tested this in Chrome, and I don’t think the spec is clear about how to deal with this case. It does show you that it’s important to be very clear about the contents of your forms. In this case, adding a submit button fixes the problem. Here’s a demo which fixes the problem by adding the submit button.

be specific

Given the quirkiness of those forms above, usually you will want to specify the form’s method and action attributes, and also name all the inputs, like this:

<form action="topping.php" method="POST">
  <div><label>topping: <input name="topping"></label></div>
</form>

Notes about the action:

  • The action should be a valid URI; the spec doesn’t say what will happen if your URI is not valid.
  • URIs can be relative, like ../pizza.cgi or customer.php.
  • Important: the URI can include a query string, like ../pizza.cgi?topping=pepperoni.
  • If the form’s action is the empty string, then the action defaults to the URL of the document that contains the form – the URL of the page that you’re currently looking at. I couldn’t find anything in the spec that says what happens if the form does not even specify the action. I think most browsers treat this as the empty string.

Here’s a page which contains several different forms, with different actions set. You can play around with that demo to see how the different types of action attributes work.

what about that query string?

If a user comes to a web page that has a query string, and your form’s action is empty, then when you submit the form, the URL for the resulting page will contain the original query string (the address bar will show the URL with the query string). Here’s an example – this link has a query string, and the form on the page has an empty action. Notice that when you submit the form, the server sees both GET data and POST data. Try it!

So this is a quirk when using a form with an empty action attribute. It will retain the query string with which the user came to the page – this can be useful when sharing links. Some users will copy the URL in the address bar for sharing, and if that query string is important for what displays on the page, you want it to remain even after a form submission.

It’s also useful for reloading a page. If a user submits a form using a POST, and then tries to reload the page, they’ll get a question asking them if they want to re-POST the data. If the user doesn’t want to re-POST, but does want to reload the page, then they can copy the URL – including its query string – and paste it into the browser’s address bar, and hit enter to avoid the re-POST.

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

How does S3 generate the URL with putObject method?

Recently, I noticed a question on a forum about the AWS SDK S3Client class.

The person was using the putObject method of S3Client to upload a file to an Amazon S3 bucket.

After that, he needed to figure out the URL which could be used to access that file. He had figured out that uploading a file called cat.gif could be accessed with the URL “https://s3.eu-west-3.amazonaws.com/aws.mybucket.es/mysite/httpdocs/cat.gif”.

The problem was that when he uploaded a file whose name included special characters, such as an accented o – “ó” – he couldn’t figure out a consistent way to construct the URL. A character with an accent got URL encoded, but the parenthesis character in a file name did not!

He was trying to figure out the implementation details for the putObject method, and couldn’t find any documentation about it.

The answer to his question was that he was asking the wrong question! There’s a software principle that you should “write code to the interface, not to the implementation“.

As consumers of the S3Client API, we should not be trying to figure out the URL to an uploaded file. Rather, we should be asking the interface for the URL. If AWS revealed the details of their URL construction scheme, it would be very painful if they ever decided to change it, both for them and for users of S3. Further, programmers everywhere would be forced to implement the algorithm that AWS declared for URL construction in all the different languages that are supported by the AWS SDK. That’s a lot of duplicated effort.

Fortunately, AWS gives us an interface that can be used to obtain the URL after a file is uploaded. The result of S3Client->putObject contains an ObjectURL property. We can use that to get the URL, which we can record however we want for later use. Here’s an example:

...
$result = $s3->putObject(...);
$url = $result['ObjectURL'];
...

The full source code for this example of using the S3Client putObject method is at github.

So you see that there’s no need to figure out how AWS implements the URL for our file. AWS gives us the URL immediately when our file is uploaded.

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