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!
- π Quick Start
- π Prerequisites
- π§ Setup
- πΈοΈ Create a Web App
- π± iOS and Android
- β¨ Icons and Info
- π Publish to web - free!
- π² Publish app for testing - free!
- π² Publish to app stores - not free...
- πΎ Save and push changes to GitHub
- π€ What's next?
- π‘ Tips and Tricks
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.
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. π
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.
-
Make sure you have a popular and modern browser set as default
-
Install VSCode
-
Install Node
-
Install Yarn
-
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.
-
Open VSCode
-
Install VSCode extensions (optional, but recommended)
-
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)
-
Setup theme and icons (very optional)
The following only applies if you weren't prompted automatically -
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)
-
Open your terminal with one of the following methods
-
Install Expo, by pasting the following to the terminal. Press Enter to run the command
yarn global add expo-cli
-
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 -
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
-
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.
-
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"
-
Commit your initial files
Go to the Source Control tab
Write
init
as the commit message and press commit (the β-mark or Ctrl + Enter) -
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!
-
Go to your browser and open developer tools
Note: I am using Chrome, so this may differ if you are using something else.
- Windows:
- Mac:
- 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.
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!.
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! πΈ
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 applicationicon.png
Icon used on iOS, Android and on Expoadaptive-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"
}
}
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.
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.
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.
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.
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! π
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:
-
Copy the GitHub-repo URL (the HTTPS one)
-
Open the command palette (Ctrl + Shift + P or F1) and use the command
Git: Clone
-
Paste the URL
-
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.
-
Say yes to open the project in its own workspace