Skip to content

Beginner's guide to development for iOS, Android and Web with Expo (React Native). Made in collaboration with Eik Lab and NMBU.

Notifications You must be signed in to change notification settings

VidunderGunder/expo-prototype-tutorial

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

41 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

App and Web with Expo

Let's create an application prototype from start to finish!

What we'll make:

  • A calculator that calculates how much you love cats 🐱
    In other words...
    a Catculatorβ„’

What we'll support:

  • Web (all platforms)
  • App (iOS and Android)

This is all made possible by React Native and Expo.

React Native is a framework for writing applications for iOS and Android. It is native, meaning your app is developed specifically for one platform and can take full advantage of all the devices features.

Expo is a tool to make React Native easier - it makes it possible to write code that can be used both on iOS, Android and even on the web (thanks to React Native for Web). You will lose some advanced functionalities with Expo, but for the average developer the trade-offs are absolutely worth it - and you can always eject your project, if you ever need to!

React Native is working on native support for Windows and Mac, and Expo already supports wrapping your application with Electron (although experimentally), if you want native desktop support down the line.

For simplicity's sake, we will only support Web, iOS and Android here, as that is sufficient in most cases.

Huge thanks to Astrid Lye Moum for valuable feedback!

Table of contents

πŸš€ Quick Start

If you are a proficient developer already, clone this repo and start developing, or take a look at Expo's documentation.

Otherwise, follow the guide below.

πŸŽ“ Prerequisites

This guide assumes no prior knowledge, but if this is going a bit fast, feel free to check out the links below. You won't actually code as much as copy-paste from the guide. The goal here is to show you how to get a functional prototype out there - you can fill in the gaps later.


Warning: If you are new to all of this, don't spend too much time on the tutorials below; just skip through to get a feeling for what they do - you can look them up later when you need to. πŸ”

I see too many new developers get stuck in tutorial land, and I would recommend you to learn as you work towards a goal, as it's often the most effective way of learning for most people. You may be different, so do what you feel is right, but that's my two cents. πŸ‘›


πŸ”§ Setup

We will need an integrated development environment (IDE), and I will use VSCode. I recommend you do the same, as it will be easier to follow along, but if you are familiar with another IDE I won't stop you.

Note: If you know your way around an IDE, Git, GitHub and developer tools, you only need to follow the mandatory steps in bold text.

  1. Make sure you have a popular and modern browser set as default

  2. Install VSCode

  3. Install Node

  4. Install Yarn

  5. Install Git

    Set the Git's default editor to Visual Studio Code

    I usually leave all other options to default

Now might be a good time to restart your computer. You may not need to, but it is always a good idea after installing so many tools.

  1. Create a GitHub user

  2. Open VSCode

  3. Install VSCode extensions (optional, but recommended)

    1. Open extensions

    2. Install these extensions

      • Prettier - Code formatter
      • React Native Tools
      • ES7 React/Redux/GraphQL/React-Native snippets
      • Bracket Pair Colorizer 2
      • Community Material Theme (very optional)
      • Material Icon Theme (very optional)
    3. Setup theme and icons (very optional)
      The following only applies if you weren't prompted automatically

      • Open the command palette (Ctrl + Shift + P or F1)
      • Run the command Preferences: Color Theme
      • Choose a Community Material Theme of your liking
      • Open the command palette (Ctrl + Shift + P or F1)
      • Run the command Preferences: File Icon Theme
      • Choose Material Icons: Activate Icon Theme
    4. Reload VSCode

      Run Developer: Reload Window with the command palette (Ctrl + Shift + P or F1)
      (You can also close and reopen VSCode, if you prefer)

  4. Open your terminal with one of the following methods

    • NO: Ctrl + Ø
    • US/UK: Ctrl + `
    • Click View, Terminal in the toolbar
  5. Install Expo, by pasting the following to the terminal. Press Enter to run the command

    yarn global add expo-cli
  6. Initialize a expo project

    Note: The project will be saved where your terminal is located, so change this if you have a preferred place to store coding projects.

    The last argument, catculator , is the project name - feel free to change it.

    expo init catculator

    Choose blank as your template when prompted (it should be the default option), by pressing Enter

  7. Open your newly created project

    Click on File, Open Folder... or use your OS' shortcut
    (Ctrl + K followed by Ctrl + O on Windows)

    Select the folder with your project and press Select Folder

    You should see the project files in the explorer tab if done correctly

  8. Start your app in your browser

    Open up a terminal (if the old one closed on you) and start Expo with a web client

    yarn web

    If everything worked correctly you should be met with Open up App.js to start working on your app! in a web browser after Expo has started up.

    So let's do just that, but first - we want to store our code in the cloud and open up some developer tools.

  9. Configure username and email in Git

    git config --global user.email "you@example.com"
    git config --global user.name "Your Name"

    For me, that will be

    git config --global user.email "kristiankramas@outlook.com"
    git config --global user.name "VidunderGunder"
  10. Commit your initial files

    Go to the Source Control tab

    Write init as the commit message and press commit (the βœ”-mark or Ctrl + Enter)

  11. Publish your repository

    Open the command palette (Ctrl + Shift + P or F1)

    Use the command Publish to GitHub

    Accept all prompts

    Confirm the repo name and choose if you want to have it public or private

    It is now hosted on your GitHub!

  12. Go to your browser and open developer tools

    Note: I am using Chrome, so this may differ if you are using something else.

    • Windows:
      • Press Ctrl + Shift + C
      • ... or press F12
      • ... or right click the web page and choose inspect
    • Mac:
      • Press Command + Option + J
      • ... or right click the web page and choose inspect
    • Linux:
      • Press Ctrl + Shift + C

    Click on the console-tab, as that's where we want to be most of the time. It should look something like this:

    If something goes wrong during development, you should get some kind of explanation or pointers here.

πŸ•ΈοΈ Create a Web App

Your app starts inn App.js*,

*I will rename the file to App.jsx, because of personal preference. Feel free to do the same - just remember to restart Expo, by terminating it in the console (Click on the terminal, Ctrl + C) and running yarn web afterwards.

The file will look something like this:

// App.jsx
import { StatusBar } from 'expo-status-bar';
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';

export default function App() {
  return (
    <View style={styles.container}>
      <Text>Open up App.js to start working on your app!</Text>
      <StatusBar style="auto" />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

Let's reduce this as much as possible.

// App.jsx
import React from "react";

export default () => {
  return null;
}

Note: Remember to save for each change we do in the code!

First we want a nice background. Go to PixaBay and find yourself a nice one. They are open source and free for commercial use.

Drag and drop it to your πŸ“‚ assets-folder.
If that didn't work, right click the folder and pick Reveal in File Explorer (or equivalent, e.g. Finder), and put the image there.

Then, we import it and use it as source in an ImageBackground-tag.

// App.jsx
import React from "react";
import { ImageBackground } from "react-native";
import background from "./assets/kitten.jpg";

export default () => {
  return (
    <ImageBackground
      source={background}
      style={{ height: "100%" }}
    ></ImageBackground>
  );
};

Make sure this line is actually pointing to your image, and that the image exists in assets.

// App.jsx, line 3
import background from "./assets/kitten.jpg";

Wow. Amazing.

Let's hide that cute cat behind a card where we can sketch out our actual app.

We could reinvent the wheel here, and design all our components ourselves - but I would almost exclusively recommend using a library or framework for your UI, as you will save an enormous amount of time and effort.

I will use UI Kitten, for obvious reasons. 🐈

First, we need to install it, so open up a new terminal.

And run these commands.

yarn add @ui-kitten/components @eva-design/eva
expo install react-native-svg@9.13.6 @expo/webpack-config

UI Kitten has some problems running with Expo out of the box at the moment, so we will have to show them how to be friends first.

Create a new file webpack.config.js in the root of your project (make sure it's not in a folder)...


...and paste this into it:

// webpack.config.js
const createExpoWebpackConfigAsync = require("@expo/webpack-config");

module.exports = async function (env, argv) {
  const config = await createExpoWebpackConfigAsync(
    {
      ...env,
      babel: {
        dangerouslyAddModulePathsToTranspile: ["@ui-kitten/components"],
      },
    },
    argv
  );
  return config;
};

Note: Remember to save.

Make sure you are in the node terminal running Expo.

Restart Expo by terminating it in the console (Click on the terminal, Ctrl + C) and running yarn web when done.

Wrap our app with UI Kittens <ApplicationProvider /> and add a card with some text from their library.

// App.jsx
import React from "react";
import { ImageBackground } from "react-native";
import background from "./assets/kitten.jpg";
import * as eva from "@eva-design/eva";
import { ApplicationProvider, Card, Text } from "@ui-kitten/components";

export default () => {
  return (
    <ApplicationProvider {...eva} theme={eva.dark}>
      <ImageBackground source={background} style={{ height: "100%" }}>
        <Card
          style={{
            margin: "auto",
            width: 500,
            maxWidth: "100%",
          }}
          disabled
        >
          <Text>App</Text>
        </Card>
      </ImageBackground>
    </ApplicationProvider>
  );
};

Add a header to your card, so you can display the app name and optionally a tagline or something of the sort.

// App.jsx
import React from "react";
import { ImageBackground, View } from "react-native";
import background from "./assets/kitten.jpg";
import * as eva from "@eva-design/eva";
import { ApplicationProvider, Card, Text } from "@ui-kitten/components";

export default () => {
  return (
    <ApplicationProvider {...eva} theme={eva.dark}>
      <ImageBackground source={background} style={{ height: "100%" }}>
        <Card
          header={Header}
          style={{
            margin: "auto",
            width: 500,
            maxWidth: "100%",
          }}
          disabled
        >
          <Text>App</Text>
        </Card>
      </ImageBackground>
    </ApplicationProvider>
  );
};

const Header = (props) => (
  <View {...props}>
    <Text category="h6">Catculator</Text>
    <Text category="s1">Purr purr</Text>
  </View>
);

Yes, I know it says Kitten App in the pictures. I didn't get the very official trademark Catculatorβ„’ certified by a qualified lawyer before the tutorial was complete. Will I redo all the pictures? Nopetiy-nope.

Most often an app needs input from a user, so let's add a tiny form.

// App.jsx
import React from "react";
import { ImageBackground, View } from "react-native";
import background from "./assets/kitten.jpg";
import * as eva from "@eva-design/eva";
import {
  ApplicationProvider,
  Button,
  Card,
  Input,
  Text,
} from "@ui-kitten/components";

export default () => {
  return (
    <ApplicationProvider {...eva} theme={eva.dark}>
      <ImageBackground source={background} style={{ height: "100%" }}>
        <Card
          header={Header}
          style={{
            margin: "auto",
            width: 500,
            maxWidth: "100%",
          }}
          disabled
        >
          <Input
            label="Describe your favorite cat"
            placeholder="Type here..."
          />
          <Separator />
          <Input
            label="How many cats do you want?"
            placeholder="Give a number..."
          />
          <Separator />
          <Button>Calculate your love for Cats</Button>
        </Card>
      </ImageBackground>
    </ApplicationProvider>
  );
};

const Separator = () => {
  return <View style={{ margin: 8 }}></View>;
};

const Header = (props) => (
  <View {...props}>
    <Text category="h6">Catculator</Text>
    <Text category="s1">Purr purr</Text>
  </View>
);

The form is now just visual elements on the card, but we have to add some functionality to call it an app.

There are endless ways of doing this, so I will make it as simple as possible and only use React components and functions here. If you want to learn ways of doing stuff like this more efficiently and scalable, take a look at my playlist: Zero to Fullstack Hero.

If you are not familiar with what's going on below, just copy-paste the whole thing and learn those concepts later by watching the playlist.

// App.jsx
import React, { useState } from "react";
import { ImageBackground, View } from "react-native";
import background from "./assets/kitten.jpg";
import * as eva from "@eva-design/eva";
import {
  ApplicationProvider,
  Button,
  Card,
  Input,
  Text,
} from "@ui-kitten/components";

export default () => {
  const [fav, setFav] = useState("");
  const [num, setNum] = useState("");
  const [luv, setLuv] = useState("");

  return (
    <ApplicationProvider {...eva} theme={eva.dark}>
      <ImageBackground source={background} style={{ height: "100%" }}>
        <Card
          header={Header}
          style={{
            margin: "auto",
            width: 500,
            maxWidth: "100%",
          }}
          disabled
        >
          <Input
            label="Describe your favorite cat"
            placeholder="Type here..."
            value={fav}
            onChangeText={(text) => {
              setFav(text);
            }}
          />
          <Separator />
          <Input
            label="How many cats do you want?"
            placeholder="Give a number..."
            value={num}
            onChangeText={(text) => {
              setNum(text.replace(/[^0-9]/g, ""));
            }}
            number-pad="numeric"
          />
          <Separator />
          <Button
            onPress={() => {
              if (fav !== "" && num !== "") {
                setLuv(fav.length * Number(num));
              } else {
                setLuv("Fill in the important form above first.");
              }
            }}
          >
            Calculate your love for Cats
          </Button>
          {!!luv && (
            <>
              <Separator />
              <Text category="s1" style={{ margin: "auto" }}>
                {luv}
              </Text>
            </>
          )}
        </Card>
      </ImageBackground>
    </ApplicationProvider>
  );
};

const Separator = () => {
  return <View style={{ margin: 8 }}></View>;
};

const Header = (props) => (
  <View {...props}>
    <Text category="h6">Catculator</Text>
    <Text category="s1">Purr purr</Text>
  </View>
);

Congrats - we now have a functioning web app! 😻

This level is sufficient for most prototypes, and you can be safe knowing that your code will be useable (with some tweaks) on mobile as well.

If you don't need an app yet, skip ahead to 🌐 Publish to web - for free!.

πŸ“± iOS and Android

Install the Expo Development Client on your mobile device of choice.

Connect the device to the same network as your development environment (this probably won't work on a school- or work network - more on that below*).

Open up the Expo app

Press Scan QR Code

Scan the QR code on your Expo server

You should connect automatically

*IF you can't connect your device on the same network, switch to Tunnel instead of LAN and try again.


If you can't connect directly, you will have to install an emulator for iOS (Mac only) or Android. We won't go into detail about that here, but check out these guides if you had no luck with the above method:

If nothing works, just read on and try again on another network.


Now onto testing the app!

I'll just press the calculate button...

Uh-oh πŸ˜“

That text is supposed to be center aligned. That probably means that margin: "auto" doesn't center things on the app. That can be fixed by using textAlign: "center" instead.

Thinking about it, the card was supposed to be vertically centered on the screen as well. We'll have to fix that by using Flexbox.

Describe your favorite cat seems to work ok.

How many cats do you want? does work, but should really have a number pad as the default input. Seems like I didn't do it right when writing the web app. I wrote number-pad="numeric", but it should really be keyboardType="number-pad" - woopsies! πŸ€·β€β™‚οΈ

// App.jsx
import React, { useState } from "react";
import { ImageBackground, View } from "react-native";
import background from "./assets/kitten.jpg";
import * as eva from "@eva-design/eva";
import {
  ApplicationProvider,
  Button,
  Card,
  Input,
  Text,
} from "@ui-kitten/components";

export default () => {
  const [fav, setFav] = useState("");
  const [num, setNum] = useState("");
  const [luv, setLuv] = useState("");

  return (
    <ApplicationProvider {...eva} theme={eva.dark}>
      <ImageBackground
        source={background}
        style={{
          height: "100%",
          display: "flex",
          justifyContent: "center",
          alignItems: "center",
        }}
      >
        <Card
          header={Header}
          style={{
            width: 500,
            maxWidth: "100%",
          }}
          disabled
        >
          <Input
            label="Describe your favorite cat"
            placeholder="Type here..."
            value={fav}
            onChangeText={(text) => {
              setFav(text);
            }}
          />
          <Separator />
          <Input
            label="How many cats do you want?"
            placeholder="Give a number..."
            value={num}
            onChangeText={(text) => {
              setNum(text.replace(/[^0-9]/g, ""));
            }}
            keyboardType="number-pad"
          />
          <Separator />
          <Button
            onPress={() => {
              if (fav !== "" && num !== "") {
                setLuv(fav.length * Number(num));
              } else {
                setLuv("Fill in the important form above first.");
              }
            }}
          >
            Calculate your love for Cats
          </Button>
          {!!luv && (
            <>
              <Separator />
              <Text
                category="s1"
                style={{
                  textAlign: "center",
                }}
              >
                {luv}
              </Text>
            </>
          )}
        </Card>
      </ImageBackground>
    </ApplicationProvider>
  );
};

const Separator = () => {
  return <View style={{ margin: 8 }} />;
};

const Header = (props) => (
  <View {...props}>
    <Text category="h6">Catculator</Text>
    <Text category="s1">Purr purr</Text>
  </View>
);

That's better! 😸

✨ Icons and Info

In your files there is a folder named πŸ“ assets, where you'll find some images other than our background:

  • splash.png
    The opening/loading screen of your application
  • icon.png
    Icon used on iOS, Android and on Expo
  • adaptive-icon.png
    Two layered icon used on Android, which will be used by the OS for things like animations.
  • favicon.png
    Icon shown on the web

Note that there are guidelines if you want to publish your app to a store, but we won't dive into that.

Delete splash.png, icon.png, adaptive-icon.png and favicon.png.

To simplify and save some time, we will use the same image for all of them.

Some requirements for the icon:

  • It must be 1:1 aspect ratio, so you might need to crop
  • Expo wants PNG-files, so if you find a JPG that you really want to use, you can convert it with JPGtoPNG

You can copy my icon, or find a new one on PixaBay (the vector search is probably your best bet there).

Put the image in the πŸ“ assets-folder, and call it logo.png.

Open up the file app.json, so we can tell Expo we have a new image for everything. Change every image from πŸ“ assets to our new image.

{
  "expo": {
    "name": "catculator",
    "slug": "catculator",
    "version": "1.0.0",
    "orientation": "portrait",
    "icon": "./assets/logo.png",
    "splash": {
      "image": "./assets/logo.png",
      "resizeMode": "contain",
      "backgroundColor": "#ffffff"
    },
    "updates": {
      "fallbackToCacheTimeout": 0
    },
    "assetBundlePatterns": ["**/*"],
    "ios": {
      "supportsTablet": true
    },
    "android": {
      "adaptiveIcon": {
        "foregroundImage": "./assets/logo.png",
        "backgroundColor": "#FFFFFF"
      }
    },
    "web": {
      "favicon": "./assets/logo.png"
    },
    "description": ""
  }
}

Here you can also add information about your app. Almost everything I want is here, except for a description - so I'll add that.

{
  "expo": {
    "name": "catculator",
    "slug": "catculator",
    "version": "1.0.0",
    "orientation": "portrait",
    "icon": "./assets/logo.png",
    "splash": {
      "image": "./assets/logo.png",
      "resizeMode": "contain",
      "backgroundColor": "#ffffff"
    },
    "updates": {
      "fallbackToCacheTimeout": 0
    },
    "assetBundlePatterns": ["**/*"],
    "ios": {
      "supportsTablet": true
    },
    "android": {
      "adaptiveIcon": {
        "foregroundImage": "./assets/logo.png",
        "backgroundColor": "#FFFFFF"
      }
    },
    "web": {
      "favicon": "./assets/logo.png"
    },
    "description": "A kitty love calculator for educational purposes"
  }
}

🌐 Publish to web - free!

Connect your GitHub-user to Vercel

Install Vercel CLI (using the terminal)

yarn global add vercel

Optimize assets

yarn global add sharp-cli
npx expo-optimize

Build the web application

expo build:web

Go into the web build

cd web-build

Deploy to Vercel

vercel

Follow the instructions. If it's the first time you use Vercel, you'll have to run the command vercel again after you're logged in.

Press enter for all prompts. You don't have to input anything.

The final command should give you the url to visit your webpage when it's done.

If it doesn't work or you want to adjust hosting settings, visit your Vercel dashboard. You'll find everything you need to administer your web page there.

πŸ“² Publish app for testing - free!

Create an Expo account.

Run the following in the terminal:

expo publish

Login when prompted

Go to Expo

Login (if you haven't already)

Go to your projects

Click on your project name

There you should be presented with the information needed to test your app on mobile. Send the link in the address bar to your testers.

πŸ’² Publish to app stores - not free...

This is outside the scope of this tutorial, as we are only creating a prototype.

Expo offers documentation on how you can upload apps to the official app stores, when you and your app is ready.

πŸ’Ύ Save and push changes to GitHub

Great work! Now you should make sure everything is saved and push the changes to your repository.

Enter the source control tab

Enter a short description of what you did

Stage all your changes

Commit all your staged changes

Push your changes

There are shortcuts for doing all of this, but as there are so many different ways and operating systems, I leave it up to you to figure out faster ways of doing this.

πŸ€” What's next?

If you liked this and want to learn more about web development, check out the Zero to Fullstack Hero.

Other than that, you can go ahead and make an app. Remember that Google is your friend, as am I, so ask either one of us if you need any help.

If you need some more advanced functionality, like users and data storage, you might want to check out Google's Firebase. It's by far the most powerful backend tool for small startups in my opinion, and will scale like a singing reptile on a diet.

I'm available for help at Facebook.

Good luck! πŸŽ‰

πŸ’‘ Tips and Tricks

We did web first in this example to make it easier to follow along, but when you're developing, it may be a good idea to have a live version of your app open on iOS or Android (both preferably), so that you catch any errors and special cases as they pop up.

An emulator is great, if you want to set that up for Android or iOS (Mac only πŸ’”), but it's not the same as having a device in your hand.

If something technically works on mobile and looks good, it will usually work on the web (except native features obviously) and scale great up to a bigger screen. If you want to save time, it's often a good idea to design your app mobile first.

If you want to clone this repo, this is how to do so:

  1. Copy the GitHub-repo URL (the HTTPS one)

  2. Open the command palette (Ctrl + Shift + P or F1) and use the command Git: Clone

  3. Paste the URL

  4. Choose where you want to store it Note: Cloning it will create a new folder with the project name where you choose to place it, so there is no need to make a new dedicated folder for it manually.

  5. Say yes to open the project in its own workspace