## Intro

A local lab is a safe, repeatable workspace for Expo development where you can test, troubleshoot, and learn without risking production work. In this guide you will:

- Install and pin the core toolchain for Expo development.

- Create a dedicated workspace for clean, isolated experiments.

- Add reproducible scripts for consistent runs across machines.

- Build a small, measurable pilot you can inspect locally.

Why it matters:

- Faster learning loop: smaller scope, quicker feedback.

- Lower risk: isolate changes from main apps and repos.

- Repeatability: scripts and pinned versions reduce drift.

## Workflow Overview

The workflow below separates setup from experiments and makes each outcome easy to inspect locally.

### 0) Goals and Guardrails

- State what you will learn or validate (for example, Android emulator startup with a FlatList backed by a local mock API).

- Define success checks you can observe locally (for example, app reaches first render in under 10s on Android emulator; list renders 3 items from http://10.0.2.2:4000/items with timeToDataMs under 500ms).

### 1) Prerequisites

- Node.js LTS (for example, 18.x or 20.x). Pin it with nvm or asdf.

- Git

- Java JDK 17 (required for Android Gradle builds)

- Android Studio with SDKs and an AVD configured (API 34 recommended)

- Xcode for iOS simulator (macOS only)

- A physical device (optional but useful for LAN testing)

Verify basics:

node -v
java -version
adb --version || echo "ADB not yet in PATH" 

### 2) Create the Lab Workspace

 Use a single directory to keep experiments tidy and versioned.

mkdir expo-lab && cd expo-lab
git init -b main
echo "18.19.0" > .nvmrc 
 Recommended .gitignore additions:

node_modules/
.expo/
.expo-shared/
.DS_Store
android/
ios/
.expo/* 

### 3) Initialize Reference Apps

 Create two minimal apps: one managed, one prepared for a development client.

# Managed workflow example
npx create-expo-app demo-managed --template blank

# Dev-client ready example (we will add expo-dev-client later)
npx create-expo-app demo-dev-client --template blank 

### 4) Managed App Quick Loop

 cd demo-managed
npm install
npm start # or: npx expo start 

- Press a to launch Android emulator, i to launch iOS simulator (macOS), or scan the QR code with a physical device running Expo Go.

- Confirm the app loads and hot reloads UI changes.

 Add basic scripts to package.json for reproducibility:

{
 "scripts": {
 "lab:start": "expo start",
 "lab:android": "expo start --android",
 "lab:ios": "expo start --ios",
 "lab:clean": "rimraf node_modules .expo && npm install",
 "lab:doctor": "expo doctor",
 "lab:inspect:deps": "npm ls --depth=0 || true"
 }
} 
 Install rimraf if you use the clean script on Windows:

npm install --save-dev rimraf 

### 5) Device Matrix

 Aim to validate on three targets early:

- Android emulator (AVD, API 34)

- iOS simulator (macOS, iPhone 15 Pro)

- Physical device with Expo Go on same LAN

This surfaces environment-specific issues (network host resolution, metro bundler connectivity, native module availability) while changes are still small.

### 6) Logging, Metrics, and Snapshots

- Use console.log with clear tags, for example: [LAB] mount , [LAB] fetch ok , [LAB] error: ... .

- Capture a timestamp at app start and after first data render to estimate cold-start time.

- Keep a screenshots/ folder in the repo for before/after proof.

### 7) Local Mock API

A local mock API makes experiments deterministic and network-independent.

# From expo-lab root
npm install --save-dev json-server

# Create a data file
cat > db.json <<'JSON'
{
 "items": [
 { "id": 1, "name": "Alpha" },
 { "id": 2, "name": "Bravo" },
 { "id": 3, "name": "Charlie" }
 ]
}
JSON

# Run the server on port 4000
npx json-server --watch db.json --port 4000 
 Network host tips:

- iOS simulator: http://localhost:4000 works.

- Android emulator: use http://10.0.2.2:4000 (10.0.2.2 maps to host localhost).

- Physical device: use your machine LAN IP, e.g., http://192.168.1.50:4000 (run ifconfig | grep "inet " | grep -v 127.0.0.1 to find it).

### 8) Example Screen with Feature Flag

In demo-managed , create a simple list with a feature flag to toggle sorting.

cd demo-managed
npm install react-native-safe-area-context 
 App.js :

import React, { useEffect, useState, useMemo } from 'react';
import { Platform } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { View, Text, FlatList, Switch, StyleSheet } from 'react-native';

const HOST_ANDROID = 'http://10.0.2.2:4000';
const HOST_IOS_OR_SIM = 'http://localhost:4000';

function hostForPlatform() {
 const isAndroid = Platform.OS === 'android';
 return isAndroid ? HOST_ANDROID : HOST_IOS_OR_SIM;
}

export default function App() {
 const [items, setItems] = useState([]);
 const [sortAsc, setSortAsc] = useState(true);
 const [startedAt] = useState(Date.now());

 useEffect(() => {
 const url = `${hostForPlatform()}/items`;
 console.log('[LAB] fetching', url);
 fetch(url)
 .then(r => r.json())
 .then(json => {
 console.log('[LAB] fetch ok, count=', json.length);
 setItems(json);
 console.log('[LAB] timeToDataMs=', Date.now() - startedAt);
 })
 .catch(e => console.log('[LAB] fetch error', e));
 }, [startedAt]);

 const data = useMemo(() => {
 const copy = [...items];
 copy.sort((a, b) => sortAsc
 ? a.name.localeCompare(b.name)
 : b.name.localeCompare(a.name));
 return copy;
 }, [items, sortAsc]);

 return (
 <SafeAreaView style={styles.root}>
 <View style={styles.row}>
 <Text style={styles.title}>Expo Lab List</Text>
 <View style={styles.switchRow}>
 <Text>Sort A-Z</Text>
 <Switch value={sortAsc} onValueChange={setSortAsc} />
 </View>
 </View>
 <FlatList
 data={data}
 keyExtractor={item => String(item.id)}
 renderItem={({ item }) => (
 <View style={styles.card}><Text>{item.name}</Text></View>
 )}
 />
 </SafeAreaView>
 );
}

const styles = StyleSheet.create({
 root: { flex: 1, padding: 16, backgroundColor: '#fff' },
 row: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' },
 switchRow: { flexDirection: 'row', alignItems: 'center', gap: 8 },
 title: { fontSize: 20, fontWeight: '600' },
 card: { padding: 12, marginVertical: 6, borderWidth: 1, borderColor: '#ddd', borderRadius: 8 }
}); 
 Run it:

npm run lab:android # or: npm run lab:ios 
 Check console tags for timeToDataMs and verify the list toggles as expected.

### 9) Optional: Development Build for Native Modules

If you need native modules not available in Expo Go, create a development build locally.

cd ../demo-dev-client
npm install
npm install expo-dev-client
npx expo prebuild

# Build and install on Android
echo "sdk.dir=$HOME/Library/Android/sdk" >> android/local.properties || true
./android/gradlew -p android assembleDebug
npx expo run:android

# For iOS (macOS)
# npx expo run:ios 
 Keep the dev-client path separate from everyday managed experiments to avoid churn.

### 10) Repro Checklist and Scripts

Add a simple Makefile or npm scripts to standardize the loop. Example Makefile :

.PHONY: start android ios mock clean doctor

start:
 cd demo-managed && npm run lab:start

android:
 cd demo-managed && npm run lab:android

ios:
 cd demo-managed && npm run lab:ios

mock:
 npx json-server --watch db.json --port 4000

clean:
 cd demo-managed && npm run lab:clean

doctor:
 cd demo-managed && npm run lab:doctor 
 A typical session:

make mock # terminal 1: local API
make android # terminal 2: run the app on Android emulator
# Edit code, watch logs, capture screenshots 

### 11) Map to GitLab CI/CD Locally

 When you are ready to mirror parts of your lab in GitLab CI/CD, keep the following consistent with local runs:

- Pin Node and Java versions in .gitlab-ci.yml (match .nvmrc and JDK 17).

- Ensure Android SDK tools are present in the runner image (use android-sdk Docker image or install via sdkmanager ).

- Reuse your lab scripts for install, lint, build, and tests.

The closer your local scripts are to your runner steps, the less rework you face later.

## Local Pilot Plan

Start small and measurable so you can inspect results locally before broadening the scope.

### Pilot Goal

- Render a list from a local mock API in a managed Expo app.

- Toggle sort order with a feature flag.

- Validate on Android emulator, iOS simulator, and a physical device.

### Success Checks

- App launches to first render in under 10 seconds on Android emulator (API 34, cold start).

- timeToDataMs consistently under 500ms with the local mock API.

- No uncaught errors in the console during normal operation.

- Screenshots saved to screenshots/ on each target.

### Plan

- Environment

- Confirm Node LTS and Java 17.

- Create and boot an Android AVD (API 34), confirm Xcode simulator if on macOS.

- App

- Initialize demo-managed and add the example screen above.

- Add scripts: lab:start , lab:android , lab:ios , lab:doctor .

- Mock API

- Run json-server on port 4000.

- Verify host selection for each target ( localhost vs 10.0.2.2 vs LAN IP).

- Run and Measure

- Launch on Android emulator, observe timeToDataMs .

- Launch on iOS simulator, capture a screenshot.

- Launch on device with Expo Go using LAN IP.

- Document and Stabilize

- Record Node, npm, json-server versions in README.md .

- Save commands and timings used to reproduce results.

### Extensions (After Success)

- Add a second screen that writes to AsyncStorage and reads back.

- Introduce a basic test using Jest and React Native Testing Library.

- Create a development build in demo-dev-client if you need native modules.

## Conclusion

You now have a practical local Expo lab and a first pilot that proves the loop end-to-end. The key is clarity: separate setup from experiments, keep the first pilot narrow and measurable, and codify everything with scripts so runs are repeatable.

### Immediate Next Steps

- Pin versions ( .nvmrc , Java 17) and document prerequisites.

- Commit your lab scripts and a short README.md with the repro checklist.

- Capture baseline metrics and screenshots in version control.

- Share the lab template with your team and extend incrementally.

By keeping the scope tight and the steps scripted, your Expo lab will remain fast, safe, and easy to evolve.