$ yuktics v0.1

T3 — Build Things People See module 03.6 ~8–12 hrs

Mobile — where it actually matters

When a mobile app beats a responsive site. React Native + Expo for the cases that need it, the case for not bothering for the cases that don't.

Prerequisites

  • 03.2

Stack

  • Expo SDK (latest stable)
  • React Native
  • EAS Build
  • iOS Simulator (or Android emulator)
  • TypeScript

By the end of this module

  • Decide whether a mobile app is the right tool for a given problem, before writing any code.
  • Stand up a React Native app with Expo, navigate between screens, and call your own backend.
  • Use camera, location, push notifications, and persistent storage from JS without dropping into native code.
  • Build for the App Store and Play Store via EAS without owning a Mac, and ship a build to TestFlight or Internal Testing.

The default answer to “should I build a mobile app?” in 2026 is no. A responsive website covers most of what people think they need an app for, runs on everything, ships in seconds, and avoids two app store reviews. The default is so strong that the rest of this module is mostly about recognizing the cases where the default is wrong, and what to actually do when it is.

The case where mobile native genuinely wins: hardware access (camera, microphone, sensors, accelerometer), backgrounding (a workout tracker, a meditation timer, a navigation app), offline-first behavior (a field-work app in a basement), push notifications you actually rely on (iOS web push exists but is roughly half the experience), and app store distribution as a marketing channel. If your project has none of those, you almost certainly want a great responsive site, not a mobile app.

The opinionated take: PWAs are great on Android and a half-finished compromise on iOS in 2026. Push notifications work, install prompts mostly work, but the App Store still has the audience and the trust badge. If “users find me through the App Store” is part of your distribution story, you need a real app. If it isn’t, ship a website.

Set up

You’ll write React Native through Expo. Skip raw React Native unless you have a specific reason to bring your own native modules — Expo solves the build, the native APIs, the OTA updates, and the App Store submission flow at the cost of nothing material for 95 percent of apps.

pnpm create expo-app@latest mobile-todo --template default
cd mobile-todo
pnpm add @react-navigation/native @react-navigation/native-stack \
  react-native-mmkv zustand
pnpm dlx expo install expo-secure-store expo-camera expo-location \
  expo-notifications

# Run it
pnpm dlx expo start

Press i for iOS Simulator (Mac only), a for Android emulator (anywhere), or scan the QR code with the Expo Go app on a real phone. The simulator is faster to develop against; the real device is faster to find bugs you would not have noticed on the simulator.

Read these first

Four references, in order, then stop:

  1. Expo — Tutorial. docs · 90 min · the canonical guided walk through a real app.
  2. React Navigation — Getting started. docs · 30 min · the routing library every Expo app uses.
  3. Apple — Human Interface Guidelines. site · 60 min skim · so your app does not look like a port of a website.
  4. Material Design 3 · site · 30 min skim · the same, for Android.

You’ll see “React Native vs Flutter 2026” content. Skip it. Pick one, ship one app, then form an opinion. For a CS student already deep in React, the answer is React Native every time.

Step 1 — When mobile actually wins

Use this table before writing a single component. If the answer is “no” in every column, you do not need a mobile app.

CapabilityWeb (modern)Mobile (RN/native)Verdict
Photos via cameraworks (getUserMedia)works, much better UXties on most projects
Background workmostly noyes (timers, fetch, geofencing)mobile wins
Push notificationspartial on iOSreliable on both platformsmobile wins
Offline-firstpossible (SW + IndexedDB), painfulclean, native APIsmobile wins for field-work
App Store as a discovery channelimpossibleyesmobile wins
Hardware access (Bluetooth, NFC)partial / blockedfullmobile wins
One codebase, all usersyesno — two stores plus possibly a websiteweb wins
Cost to ship a fixsecondshours to days plus a reviewweb wins
Cost of a redesigna deploya redesign plus a reviewweb wins

Most “I want to build an app” projects from CS students fail this test. A response page on a phone covers the goal. The exception — the project where the answer to the table really is mobile — is the one worth building this module against.

Step 2 — Navigation

Every Expo app needs a navigation library. Use @react-navigation/native with the native stack — it uses the actual platform navigator under the hood (UINavigationController on iOS, Fragment on Android), so transitions feel right.

// app/_layout.tsx — Expo Router (file-based) is also fine; this is the manual setup
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';

import HomeScreen from './screens/Home';
import TodoDetailScreen from './screens/TodoDetail';

const Stack = createNativeStackNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="Todo" component={TodoDetailScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

If you start a new project today, prefer Expo Router — same idea as Next.js’s App Router but for React Native. File-based routes, layout files, deep linking handled.

Step 3 — Calling your backend

Reuse the API from 03.3. The fetch shape is identical to web React; the differences are auth and storage.

// lib/api.ts
import * as SecureStore from 'expo-secure-store';

const BASE = 'https://api.yourname.com';

async function getToken() {
  return SecureStore.getItemAsync('session_token');
}

export async function api<T>(path: string, init?: RequestInit): Promise<T> {
  const token = await getToken();
  const res = await fetch(`${BASE}${path}`, {
    ...init,
    headers: {
      'Content-Type': 'application/json',
      ...(token ? { Authorization: `Bearer ${token}` } : {}),
      ...(init?.headers ?? {}),
    },
  });
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json();
}

Two specific differences from web:

  • Storage. localStorage does not exist. Use expo-secure-store for tokens (Keychain on iOS, encrypted SharedPreferences on Android), react-native-mmkv for fast, non-secret app state.
  • Auth shape. Cookies work but are awkward across the WebView/native boundary. For mobile-talking-to-its-own-backend, a bearer token in Authorization header is the simplest correct setup.

Step 4 — Native APIs the easy way

Expo wraps the painful platform APIs in JS. The four you’ll reach for in most apps:

// Camera
import { CameraView, useCameraPermissions } from 'expo-camera';

const [permission, request] = useCameraPermissions();
if (!permission?.granted) return <Button title="Grant camera" onPress={request} />;
return <CameraView style={{ flex: 1 }} />;
// Location
import * as Location from 'expo-location';

await Location.requestForegroundPermissionsAsync();
const { coords } = await Location.getCurrentPositionAsync({});
// Push notifications
import * as Notifications from 'expo-notifications';

const { status } = await Notifications.requestPermissionsAsync();
const token = await Notifications.getExpoPushTokenAsync();
// send token to your backend, store it against the user
// Local notifications
await Notifications.scheduleNotificationAsync({
  content: { title: 'Pick up bread', body: 'Due in 10 minutes' },
  trigger: { seconds: 600 },
});

Always request permissions in the moment they make sense — when the user taps “Take photo,” not in the splash screen. Refused permissions on iOS are sticky; if you ask in the wrong moment and they say no, recovery is a settings-app round trip.

Step 5 — State and storage

Same pattern as web React, with one tweak: use react-native-mmkv instead of AsyncStorage. MMKV is roughly 30x faster, synchronous, and the React Native team will eventually deprecate AsyncStorage anyway.

// store/todos.ts
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import { MMKV } from 'react-native-mmkv';

const storage = new MMKV();

const mmkvAdapter = {
  getItem: (k: string) => storage.getString(k) ?? null,
  setItem: (k: string, v: string) => storage.set(k, v),
  removeItem: (k: string) => storage.delete(k),
};

type Todo = { id: number; title: string; done: boolean };

export const useTodos = create<{ todos: Todo[]; toggle: (id: number) => void }>()(
  persist(
    (set) => ({
      todos: [],
      toggle: (id) =>
        set((s) => ({
          todos: s.todos.map((t) => (t.id === id ? { ...t, done: !t.done } : t)),
        })),
    }),
    { name: 'todos', storage: createJSONStorage(() => mmkvAdapter) }
  )
);

For server data, TanStack Query works in React Native unchanged. It also caches gracefully across screen mounts, which is valuable on mobile where users navigate back and forth more than on web.

Step 6 — Releasing without a Mac

The historical pain point of iOS development was needing a Mac to build the IPA. EAS Build runs the build in Expo’s cloud, on real Apple hardware, so you can ship from any machine.

pnpm dlx expo install eas-cli
eas login
eas build:configure

# Build for both platforms
eas build --platform ios --profile preview
eas build --platform android --profile preview

# Submit to stores
eas submit --platform ios
eas submit --platform android

You still need accounts:

  • Apple Developer Program ($99/year) for the App Store and TestFlight.
  • Google Play Console ($25 one-time) for the Play Store.

For a portfolio project, ship to TestFlight (iOS) and Internal Testing (Android) — both let you share with up to 100 testers without a full store review. That’s enough to put a real “scan this QR code to install” link on a resume.

OTA updates via eas update let you ship JS-only changes (UI, copy, business logic) without going through review. Native code changes (a new permission, a new SDK) still require a full build. That distinction is most of what makes Expo worth using.

Step 7 — The build

Take the TODO API from 03.3 and build a small mobile client:

  • Login screen, store token in SecureStore.
  • List screen, fetched from /todos, pull-to-refresh.
  • Detail screen with edit and delete.
  • Camera capture for an attachment, uploaded to your API.
  • Local notifications for due dates.
  • A hosted internal-testing build with a TestFlight link or APK download.

If the app has a real reason to exist (one of the columns in step 1’s table answered “mobile wins”), publish it for real. If it doesn’t, ship the TestFlight build and call it the portfolio piece.

Going deeper

  1. Expo docs · the canon. Use as a reference, not a tutorial, after the official tutorial.
  2. React Native Express · concise tour of every concept.
  3. Reanimated · for animation that doesn’t drop frames. Worth a Saturday once you ship one app.
  4. Mobile A/B by NN/g · how mobile users actually behave, compared with desktop.
  5. App Store Review Guidelines · read once, before you submit. Reading them after a rejection is too late.

Skip “Flutter is going to overtake React Native this year” content. Both are fine. Pick one and ship.

Checkpoints

  1. Walk through the table in step 1 for an idea you actually have. For each row that answers “mobile wins,” explain in one sentence why a website would not cover it.
  2. Why is expo-secure-store the right place for a session token, and react-native-mmkv wrong?
  3. Why should you request the camera permission inside the camera-button handler, and not on app launch?
  4. What does eas build actually do that you cannot do locally without a Mac, and why does that matter for a CS student without one?
  5. Name two specific things eas update cannot ship without a full build, and explain why each requires a fresh App Store binary.

When you have a TestFlight or Internal Testing build that calls your real backend, move to 03.7 Ship a real full-stack project, where you’ll combine everything from this track into one resume-grade piece of work.