š Overview
Di bagian ini, kita akan membuat project Expo baru dan melakukan konfigurasi awal termasuk routing, styling, dan folder structure.
š Step 1: Create Expo Project
# Create new Expo project dengan TypeScript template
npx create-expo-app@latest lampung-dev-mobile --template tabs
# Masuk ke folder project
cd lampung-dev-mobile
Template tabs sudah menyertakan:
- TypeScript configuration
- Expo Router (file-based routing)
- Tab navigation contoh
- Basic folder structure
š¦ Step 2: Install Dependencies
# Core dependencies
npm install axios zustand
# Expo packages
npx expo install expo-secure-store expo-camera
# Styling (NativeWind - Tailwind untuk React Native)
npm install nativewind
npm install --save-dev [email protected]
# Icons
npm install lucide-react-native
npm install react-native-svg
Penjelasan Dependencies
| Package | Fungsi |
|---|---|
axios | HTTP client untuk API calls |
zustand | State management ringan |
expo-secure-store | Penyimpanan token yang aman |
expo-camera | Akses kamera untuk QR scan |
nativewind | Tailwind CSS untuk React Native |
lucide-react-native | Icon library |
šØ Step 3: Konfigurasi NativeWind (Tailwind CSS)
A. Initialize Tailwind
npx tailwindcss init
B. Edit tailwind.config.js
// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./app/**/*.{js,jsx,ts,tsx}",
"./components/**/*.{js,jsx,ts,tsx}"
],
presets: [require("nativewind/preset")],
theme: {
extend: {
colors: {
primary: '#f0880a', // Warna brand LampungDev
secondary: '#1a1a1a', // Dark mode background
},
},
},
plugins: [],
}
C. Buat global.css
/* global.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
D. Edit babel.config.js
// babel.config.js
module.exports = function (api) {
api.cache(true);
return {
presets: [
["babel-preset-expo", { jsxImportSource: "nativewind" }],
"nativewind/babel",
],
};
};
E. Buat nativewind-env.d.ts
// nativewind-env.d.ts
/// <reference types="nativewind/types" />
F. Edit metro.config.js
// metro.config.js
const { getDefaultConfig } = require("expo/metro-config");
const { withNativeWind } = require('nativewind/metro');
const config = getDefaultConfig(__dirname);
module.exports = withNativeWind(config, { input: './global.css' });
š Step 4: Buat Folder Structure
# Buat folders yang dibutuhkan
mkdir -p services store components constants utils
Struktur akhir:
lampung-dev-mobile/
āāā app/ # Screens (Expo Router)
ā āāā (tabs)/ # Tab navigation
ā āāā event/ # Event screens
ā āāā login.tsx # Login screen
ā āāā _layout.tsx # Root layout
ā
āāā components/ # Komponen reusable
āāā services/ # API services
āāā store/ # State management
āāā constants/ # App constants
āāā utils/ # Utilities
āāā assets/ # Images, fonts
š§ Step 5: Konfigurasi app.json
Edit file app.json:
{
"expo": {
"name": "Lampung Dev",
"slug": "lampung-dev",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/images/icon.png",
"scheme": "lampungdev",
"userInterfaceStyle": "automatic",
"splash": {
"image": "./assets/images/splash-icon.png",
"resizeMode": "contain",
"backgroundColor": "#1a1a1a"
},
"ios": {
"supportsTablet": true,
"bundleIdentifier": "org.lampungdev.mobile"
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/images/adaptive-icon.png",
"backgroundColor": "#1a1a1a"
},
"package": "org.lampungdev.mobile",
"permissions": ["CAMERA"]
},
"plugins": [
"expo-router",
[
"expo-camera",
{
"cameraPermission": "Izinkan aplikasi mengakses kamera untuk scan QR"
}
]
]
}
}
š Step 6: Setup API Service
A. Buat Base API
File: services/api.ts
import axios from 'axios';
import * as SecureStore from 'expo-secure-store';
// Development: Gunakan IP lokal kamu
// Production: Gunakan domain API
export const BASE_URL = __DEV__
? 'http://192.168.1.100:3000' // Ganti dengan IP kamu
: 'https://lampungdev.org';
export const API_BASE_URL = `${BASE_URL}/api`;
export const api = axios.create({
baseURL: API_BASE_URL,
headers: {
'Content-Type': 'application/json',
},
timeout: 10000, // 10 seconds timeout
});
// Request interceptor - tambahkan auth token
api.interceptors.request.use(async (config) => {
try {
const token = await SecureStore.getItemAsync('auth_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
} catch (error) {
console.error('Error getting token:', error);
}
return config;
});
// Response interceptor - handle errors
api.interceptors.response.use(
(response) => response,
async (error) => {
if (error.response?.status === 401) {
// Token expired - clear auth
await SecureStore.deleteItemAsync('auth_token');
await SecureStore.deleteItemAsync('auth_user');
// Redirect ke login ditangani oleh store
}
return Promise.reject(error);
}
);
B. Buat Auth Service
File: services/auth.ts
import { api } from './api';
interface LoginResponse {
success: boolean;
data?: {
token: string;
user: {
id: string;
name: string;
email: string;
role: string;
picture?: string;
};
};
error?: string;
}
export const loginAdmin = async (
email: string,
password: string
): Promise<LoginResponse> => {
const response = await api.post('/admin/auth', { email, password });
return response.data;
};
C. Buat Events Service
File: services/events.ts
import { api } from './api';
export interface Event {
id: string;
title: string;
description: string;
date: string;
location: string;
imageUrl?: string;
registrationCount: number;
attendanceCount: number;
}
export interface EventsResponse {
success: boolean;
data: {
events: Event[];
pagination: {
page: number;
limit: number;
total: number;
totalPages: number;
};
};
}
export const getAdminEvents = async (
page = 1,
limit = 20
): Promise<EventsResponse> => {
const response = await api.get('/admin/events', {
params: { page, limit }
});
return response.data;
};
export const getEventDetail = async (id: string) => {
const response = await api.get(`/admin/events/${id}`);
return response.data;
};
D. Buat Attendance Service
File: services/attendance.ts
import { api } from './api';
interface AttendanceResponse {
success: boolean;
message?: string;
data?: {
registration: {
id: string;
name: string;
email: string;
checkedInAt: string;
};
};
error?: string;
}
export const recordAttendance = async (
eventId: string,
uniqueCode: string
): Promise<AttendanceResponse> => {
const response = await api.post('/attendance', { eventId, uniqueCode });
return response.data;
};
E. Buat Index Export
File: services/index.ts
export * from './api';
export * from './auth';
export * from './events';
export * from './attendance';
šÆ Step 7: Setup Constants
File: constants/Colors.ts
const tintColorLight = '#f0880a';
const tintColorDark = '#f0880a';
export const Colors = {
light: {
text: '#11181C',
background: '#fff',
tint: tintColorLight,
icon: '#687076',
tabIconDefault: '#687076',
tabIconSelected: tintColorLight,
card: '#f5f5f5',
border: '#e0e0e0',
},
dark: {
text: '#ECEDEE',
background: '#151718',
tint: tintColorDark,
icon: '#9BA1A6',
tabIconDefault: '#9BA1A6',
tabIconSelected: tintColorDark,
card: '#1a1a1a',
border: '#2a2a2a',
},
};
š± Step 8: Run Development
# Start Expo development server
npm start
# Atau dengan platform specific
npm run android
npm run ios
Testing di Device
- Install Expo Go app di smartphone
- Scan QR code dari terminal
- Pastikan HP dan komputer di jaringan WiFi yang sama
Mencari IP Address
# macOS/Linux
ifconfig | grep "inet "
# Windows
ipconfig
Update BASE_URL di services/api.ts dengan IP kamu.
ā Project Structure Check
Setelah setup selesai, struktur project harusnya seperti ini:
lampung-dev-mobile/
āāā app/
ā āāā (tabs)/
ā ā āāā index.tsx
ā ā āāā scanner.tsx # Akan dibuat nanti
ā ā āāā account.tsx # Akan dibuat nanti
ā āāā event/
ā ā āāā [id].tsx # Akan dibuat nanti
ā āāā login.tsx # Akan dibuat nanti
ā āāā _layout.tsx
ā āāā +not-found.tsx
āāā components/
āāā services/
ā āāā api.ts ā
ā āāā auth.ts ā
ā āāā events.ts ā
ā āāā attendance.ts ā
ā āāā index.ts ā
āāā store/
āāā constants/
ā āāā Colors.ts ā
āāā utils/
āāā assets/
āāā app.json ā
āāā babel.config.js ā
āāā metro.config.js ā
āāā tailwind.config.js ā
āāā global.css ā
āāā nativewind-env.d.ts ā
āāā package.json
ā Checklist
- Project dibuat dengan
create-expo-app - Dependencies terinstall (axios, zustand, nativewind)
- NativeWind terkonfigurasi
- Folder structure dibuat
- API services di-setup
- Constants terkonfigurasi
- App running di Expo Go
š Selanjutnya
Di Part 4, kita akan membuat sistem autentikasi termasuk login screen, state management dengan Zustand, dan token storage dengan SecureStore.
ā Part 2: Instalasi | Part 4: Autentikasi Admin ā
Series ini dibuat untuk mendukung materi Casual Meetup #15 di LampungDev.
