When you have eliminated the impossible, whatever remains, however improbable, must be the truth, Sherlock Holmes.
Now that your Arch Linux environment is fully configured with Node.js, the Android SDK, and a working emulator, it is time to write some code. In this guide, you will set up a brand new React Native project using Expo and TypeScript, understand the project structure, and create your first “Hello, World!” screen.
Why TypeScript and Expo? TypeScript acts like Python’s type hints—catching bugs before you even run the code—while Expo handles the heavy lifting of native configurations, letting you focus on building features rather than wrestling with Android Studio.
We’ll also introduce Expo Router for navigation. Instead of manually linking screens, Expo Router uses a file-based system: it turns files in the app/ folder into screens, making routing incredibly intuitive. By the end of this tutorial, you won’t just have a generic starter app; you’ll have a styled, dark-themed “Prayer Companion” home screen running on your emulator or physical device, serving as the solid foundation for the full devotional app we’ll build in subsequent guides.
Prerequisites

# Create a fresh Expo project using the blank TypeScript template.
# We use --template blank-typescript so we get TypeScript from the start.
# (TypeScript catches bugs before you run the code — like Python's type hints.)
npx create-expo-app@latest prayer-companion --template blank-typescript
cd prayer-companion
After creation, your folder will look like this:
prayer-companion/
├── app/ ← Every file you create here automatically becomes a route (screen) thanks to Expo Router.
│ └── index.tsx ← the first screen (home page), the entry point of your app, equivalent to the “/” route.
├── assets/ ← images and fonts
├── app.json ← app configuration, controls the app’s name, icon, splash screen, and platform-specific settings.
├── package.json ← lists all your project’s dependencies and scripts (like requirements.txt in Python)
├── tsconfig.json ← TypeScript configuration
└── ...
app.jsonOpen app.json. This file tells Expo and the native platforms how to present your app. Replace its entire content with the following:
{
"expo": {
"name": "Prayer Companion",
"slug": "prayer-companion",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/images/icon.png",
"scheme": "prayercompanion",
"userInterfaceStyle": "dark",
"splash": {
"backgroundColor": "#1a1f2e"
},
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.justtothepoint.prayercompanion"
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/images/adaptive-icon.png",
"backgroundColor": "#1a1f2e"
},
"package": "com.yourname.prayercompanion"
},
"web": {
"bundler": "metro",
"output": "static",
"favicon": "./assets/images/favicon.png"
},
"plugins": ["expo-router"],
"experiments": {
"typedRoutes": true
}
}
}
Key fields explained:
name — the human‑readable name that appears under the app icon on the home screen.slug — a URL‑safe identifier used by Expo’s services (builds, updates, etc.).userInterfaceStyle: "dark" — tells iOS/Android that your app uses a dark colour scheme; this influences native UI elements like the status bar.splash.backgroundColor — the background colour shown while the app is loading (we use a dark navy #1a1f2e).scheme — a custom URL scheme that lets you open the app with deep links like prayercompanion://verse.plugins: ["expo-router"] – enables Expo Router, which we’ll set up next.create-expo-app with the blank template does not include Expo Router by default. Expo Router is the navigation system that turns file names into screens.
# Install Expo Router and its peer dependencies
npx expo install expo-router react-native-safe-area-context \
react-native-screens expo-linking expo-constants expo-status-bar
What each package does:
expo-router — converts file names inside app/ into screens. For example, app/index.tsx becomes / and app/settings.tsx becomes /settings.react-native-safe-area-context. Ensures your content doesn’t overlap with the phone’s notch, status bar, or home indicator.react-native-screens. Uses native navigation containers for smoother transitions and better performance.expo-linking. Handles deep links and URLs that open your app.expo-constants. Gives you runtime access to values from app.json (like the app name or version).expo-status-bar. Lets you control the status bar’s appearance (light or dark text).package.json entry pointExpo Router needs to be the first thing that runs, before any of your screens. Open package.json and set the “main” field to “expo-router/entry”. If the field doesn’t exist, add it:
{
"main": "expo-router/entry"
}
Why: Without this, React Native looks for index.js as the entry point. Expo Router needs to be the entry point so it can set up navigation before your first screen renders (it renders your app/ directory as a set of routes).
app/_layout.tsxThis is the root layout — it wraps every screen in the app. Think of it like a base HTML template that provides common elements (like a navigation bar and background colour).
Create a file called app/_layout.tsx (note the leading underscore – this is a layout file, not a screen) and paste the following:
// app/_layout.tsx
//
// The root layout wraps every screen in the app.
// It is the first component React renders when the app starts.
//
// <Stack> is a navigation container from Expo Router.
// Any file inside app/ automatically becomes a route.
// For example:
// app/index.tsx → the "/" route (home screen)
// app/settings.tsx → the "/settings" route
// app/(tabs)/verse.tsx → the "/verse" tab (we'll add this later)
import { Stack } from "expo-router";
import { StatusBar } from "expo-status-bar";
export default function RootLayout() {
return (
<>
{/*
StatusBar controls the top bar on the phone (battery, time, etc.)
style="light" makes the text white — perfect for a dark background.
*/}
<StatusBar style="light" />
{/*
Stack is the navigation container.
screenOptions applies to EVERY screen inside this Stack.
We set the header background to match our dark palette.
It tells every screen inside the stack navigator to use the same header and background styles.
This keeps the UI consistent.
*/}
<Stack
screenOptions={{
headerStyle: {
backgroundColor: "#1a1f2e", // dark navy — same as Python app BG
},
headerTintColor: "#dde4f0", // light text for the back button
headerTitleStyle: {
fontWeight: "bold",
color: "#dde4f0",
},
contentStyle: {
backgroundColor: "#1a1f2e", // screen background
},
}}
/>
</>
);
}
app/index.tsxNow we’ll actually write the first screen that says Hello, World!. By default the project already has app/index.tsx with some placeholder code. Replace its content with the following:
// app/index.tsx
//
// The home screen of Prayer Companion – displays a friendly greeting.
//
// React Native uses a different rendering model than a web browser.
// Instead of HTML tags, we use React Native components:
//
// View is a container – it’s like a div in HTML.
// Text renders text (all text in React Native must be inside a this component).
// It is like a p in HTML.
// Image is like a img in HTML.
//
// StyleSheet.create() is equivalent to CSS.
// It is NOT actual CSS — it is a JavaScript object that React Native
// converts to native layout calls (Flexbox on iOS, Flexbox on Android).
import { View, Text, StyleSheet, TouchableOpacity } from "react-native";
import { router } from "expo-router";
import { StatusBar } from "expo-status-bar";
export default function HomeScreen() {
return (
// The outermost View fills the whole screen.
// style={styles.container} applies the styles defined at the bottom.
<View style={styles.container}>
<StatusBar style="light" />
{/* App icon area — we will replace this with a real icon later on */}
<Text style={styles.icon}>🙏</Text>
{/* App title */}
<Text style={styles.title}>Prayer Companion</Text>
{/* Subtitle */}
<Text style={styles.subtitle}>Begin each day in God's presence</Text>
<Text style={styles.description}>Daily scripture · Prayer library · Diary · Meditation</Text>
{/*
TouchableOpacity is a pressable element. onPress is equivalent to onClick in HTML.
router.push("/verse") navigates to app/(tabs)/verse.tsx (we will create that screen later on).
*/}
<TouchableOpacity style={styles.button} onPress={() => router.push("/(tabs)/verse")} activeOpacity={0.8} >
<Text style={styles.buttonText}>Open Prayer Companion</Text>
</TouchableOpacity>
{/* Version number — useful during development */}
<Text style={styles.version}>v1.0.0</Text>
View>
);
}
React Native uses Flexbox for layout. The default flex direction is COLUMN (top to bottom), unlike CSS where the default is ROW.
//
// Colour palette (same as the Python PySide6 app):
// #1a1f2e BG dark navy background
// #222840 PANEL slightly lighter panels
// #5b8fd4 ACCENT blue accent
// #dde4f0 TEXT light text
// #9ca3af MUTED dimmed text
// #c9a84c GOLD gold highlights
//
// StyleSheet.create defines styles in a clean, optimised way. Here we:
const styles = StyleSheet.create({
container: {
flex: 1, // use flex: 1 to fill the screen.
backgroundColor: "#1a1f2e", // Give the container a dark navy background
alignItems: "center", // horizontal center (cross-axis in column flex)
justifyContent: "center", // vertical center (main axis in column flex)
padding: 32,
},
icon: {
fontSize: 64,
marginBottom: 16,
},
// Style the title in a large bold gold colour.
title: {
fontSize: 32,
fontWeight: "bold",
color: "#c9a84c",
textAlign: "center",
marginBottom: 8,
},
// Style the subtitle in a lighter shade.
subtitle: {
fontSize: 16,
color: "#9ca3af", // muted grey
textAlign: "center",
marginBottom: 24,
fontStyle: "italic",
},
description: {
fontSize: 14,
color: "#9ca3af",
textAlign: "center",
marginBottom: 48,
lineHeight: 22,
},
button: {
backgroundColor: "#5b8fd4", // accent blue
paddingVertical: 14,
paddingHorizontal: 40,
borderRadius: 8,
marginBottom: 32,
},
buttonText: {
color: "#ffffff",
fontSize: 16,
fontWeight: "bold",
textAlign: "center",
},
version: {
fontSize: 11,
color: "#4b5563", // very muted
position: "absolute", // pin to bottom of screen
bottom: 24,
},
});
# Make sure you’re inside the prayer-companion directory.
cd prayer-companion
# Make sure the emulator is running first
emulator -avd MyExpoEmulator &
# Wait for the home screen to appear (takes ~30 seconds the first time)
# Start the development server:
npx expo start
This will open the Expo dev tools in your terminal. You’ll see a QR code.