- Mobile App Development Expertise
- Overview
- A guide for developing mobile apps based on web development experience.
- Develop for iOS and Android simultaneously using cross-platform frameworks.
- Framework Selection Guide
- Framework Selection by Tier (v1.3.0)
- Framework
- Tier
- Recommendation
- Use Case
- React Native (Expo)
- Tier 1
- ⭐ Primary
- TypeScript ecosystem, AI tools
- React Native CLI
- Tier 1
- Recommended
- Native module needs
- Flutter
- Tier 2
- Supported
- Multi-platform (6 OS), performance
- AI-Native Recommendation
-
- React Native with TypeScript
- Full Copilot/Claude support
- Extensive npm ecosystem
- 20:1 developer availability vs Dart
- Performance Recommendation
- Flutter Impeller rendering engine Single codebase for 6 platforms Smaller bundles Level-wise Recommendations Starter → Expo (React Native) [Tier 1] - Simple setup, can leverage web knowledge - Full AI tool support Dynamic → Expo + EAS Build [Tier 1] or Flutter [Tier 2] - Includes server integration, production build support - Choose Flutter for multi-platform needs Enterprise → React Native CLI [Tier 1] or Flutter [Tier 2] - Complex native features, performance optimization needed - Flutter for consistent cross-platform UI Expo (React Native) Guide Project Creation
Install Expo CLI
npm install -g expo-cli
Create new project
npx create-expo-app my-app cd my-app
Start development server
npx expo start Folder Structure my-app/ ├── app/ # Expo Router pages │ ├── (tabs)/ # Tab navigation │ │ ├── index.tsx # Home tab │ │ ├── explore.tsx # Explore tab │ │ └── _layout.tsx # Tab layout │ ├── _layout.tsx # Root layout │ └── +not-found.tsx # 404 page ├── components/ # Reusable components ├── hooks/ # Custom hooks ├── constants/ # Constants ├── assets/ # Images, fonts, etc. ├── app.json # Expo configuration └── package.json Navigation Patterns // app/_layout.tsx - Stack navigation import { Stack } from 'expo-router' ; export default function RootLayout ( ) { return ( < Stack
< Stack . Screen name = "(tabs)" options = { { headerShown : false } } /
< Stack . Screen name = "modal" options = { { presentation : 'modal' } } /
< / Stack
) ; } // app/(tabs)/_layout.tsx - Tab navigation import { Tabs } from 'expo-router' ; import { Ionicons } from '@expo/vector-icons' ; export default function TabLayout ( ) { return ( < Tabs
< Tabs . Screen name = "index" options = { { title : 'Home' , tabBarIcon : ( { color } ) => < Ionicons name = "home" color = { color } size = { 24 } /
, } } /
< Tabs . Screen name = "profile" options = { { title : 'Profile' , tabBarIcon : ( { color } ) => < Ionicons name = "person" color = { color } size = { 24 } /
, } } /
< / Tabs
) ; } Styling Patterns // Basic StyleSheet import { StyleSheet , View , Text } from 'react-native' ; export function MyComponent ( ) { return ( < View style = { styles . container }
< Text style = { styles . title }
Hello < / Text
< / View
) ; } const styles = StyleSheet . create ( { container : { flex : 1 , padding : 16 , backgroundColor : '#fff' , } , title : { fontSize : 24 , fontWeight : 'bold' , } , } ) ; // NativeWind (Tailwind for RN) - Recommended import { View , Text } from 'react-native' ; export function MyComponent ( ) { return ( < View className = "flex-1 p-4 bg-white"
< Text className = "text-2xl font-bold"
Hello < / Text
< / View
) ; } API Integration // hooks/useApi.ts import { useState , useEffect } from 'react' ; export function useApi < T
( endpoint : string ) { const [ data , setData ] = useState < T | null
( null ) ; const [ loading , setLoading ] = useState ( true ) ; const [ error , setError ] = useState < Error | null
( null ) ; useEffect ( ( ) => { const fetchData = async ( ) => { try { const response = await fetch (
${ process . env . EXPO_PUBLIC_API_URL } ${ endpoint }) ; if ( ! response . ok ) throw new Error ( 'API Error' ) ; const json = await response . json ( ) ; setData ( json ) ; } catch ( e ) { setError ( e as Error ) ; } finally { setLoading ( false ) ; } } ; fetchData ( ) ; } , [ endpoint ] ) ; return { data , loading , error } ; } Authentication Pattern // context/AuthContext.tsx import { createContext , useContext , useState , useEffect } from 'react' ; import * as SecureStore from 'expo-secure-store' ; interface AuthContextType { user : User | null ; signIn : ( email : string , password : string ) => Promise < void; signOut : ( ) => Promise < void
; } const AuthContext = createContext < AuthContextType | null
( null ) ; export function AuthProvider ( { children } : { children : React . ReactNode } ) { const [ user , setUser ] = useState < User | null
( null ) ; useEffect ( ( ) => { // Check for stored token on app start const loadToken = async ( ) => { const token = await SecureStore . getItemAsync ( 'authToken' ) ; if ( token ) { // Load user info with token } } ; loadToken ( ) ; } , [ ] ) ; const signIn = async ( email : string , password : string ) => { const response = await fetch ( '/api/auth/login' , { method : 'POST' , body : JSON . stringify ( { email , password } ) , } ) ; const { token , user } = await response . json ( ) ; await SecureStore . setItemAsync ( 'authToken' , token ) ; setUser ( user ) ; } ; const signOut = async ( ) => { await SecureStore . deleteItemAsync ( 'authToken' ) ; setUser ( null ) ; } ; return ( < AuthContext . Provider value = { { user , signIn , signOut } }
{ children } < / AuthContext . Provider
) ; } export const useAuth = ( ) => useContext ( AuthContext ) ! ; Flutter Guide Project Creation
After installing Flutter SDK
flutter create my_app cd my_app
Start development server
flutter run Folder Structure my_app/ ├── lib/ │ ├── main.dart # App entry point │ ├── app/ │ │ ├── app.dart # MaterialApp setup │ │ └── routes.dart # Route definitions │ ├── features/ # Feature-based folders │ │ ├── auth/ │ │ │ ├── screens/ │ │ │ ├── widgets/ │ │ │ └── providers/ │ │ └── home/ │ ├── shared/ │ │ ├── widgets/ # Common widgets │ │ ├── services/ # API services │ │ └── models/ # Data models │ └── core/ │ ├── theme/ # Theme settings │ └── constants/ # Constants ├── assets/ # Images, fonts ├── pubspec.yaml # Dependency management └── android/ & ios/ # Native code Basic Widget Patterns // lib/features/home/screens/home_screen.dart import 'package:flutter/material.dart' ; class HomeScreen extends StatelessWidget { const HomeScreen ( { super . key } ) ; @override Widget build ( BuildContext context ) { return Scaffold ( appBar : AppBar ( title : const Text ( 'Home' ) , ) , body : const Center ( child : Text ( 'Hello, Flutter!' ) , ) , floatingActionButton : FloatingActionButton ( onPressed : ( ) { } , child : const Icon ( Icons . add ) , ) , ) ; } } State Management (Riverpod) // lib/providers/counter_provider.dart import 'package:flutter_riverpod/flutter_riverpod.dart' ; final counterProvider = StateNotifierProvider < CounterNotifier , int
( ( ref ) { return CounterNotifier ( ) ; } ) ; class CounterNotifier extends StateNotifier < int
{ CounterNotifier ( ) : super ( 0 ) ; void increment ( ) =
state ++ ; void decrement ( ) =
state
; } // Usage class CounterScreen extends ConsumerWidget { @override Widget build ( BuildContext context , WidgetRef ref ) { final count = ref . watch ( counterProvider ) ; return Text ( 'Count: $ count ' ) ; } } Web vs Mobile Differences UI/UX Differences Element Web Mobile Click onClick onPress Scroll overflow: scroll ScrollView / FlatList Input input TextInput Links a href Link / navigation Layout div + CSS View + StyleSheet Navigation Differences Web: URL-based (browser back button) Mobile: Stack-based (screen stacking) Web: /users/123 Mobile: navigation.navigate('User', { id: 123 }) Storage Differences Web: localStorage, sessionStorage, Cookie Mobile: AsyncStorage, SecureStore, SQLite ⚠️ SecureStore is required for sensitive info on mobile! Build & Deployment Expo EAS Build
Install EAS CLI
npm install -g eas-cli
Login
eas login
Configure build
eas build:configure
iOS build
eas build --platform ios
Android build
eas build --platform android
Submit to stores
eas submit --platform ios eas submit --platform android Environment Variables // app.json { "expo" : { "extra" : { "apiUrl" : "https://api.example.com" } } } // Usage import Constants from 'expo-constants' ; const apiUrl = Constants . expoConfig ?. extra ?. apiUrl ; Mobile PDCA Checklist Phase 1: Schema □ Identify data that needs offline caching □ Define sync conflict resolution strategy Phase 3: Mockup □ Follow iOS/Android native UX guidelines □ Consider gestures (swipe, pinch, etc.) □ Layout for different screen sizes (phone, tablet) Phase 6: UI □ Keyboard handling (screen adjustment during input) □ Safe Area handling (notch, home button area) □ Handle platform-specific UI differences Phase 7: Security □ Store sensitive info with SecureStore □ Certificate Pinning (if needed) □ App obfuscation settings Phase 9: Deployment □ Follow App Store review guidelines □ Prepare Privacy Policy URL □ Prepare screenshots, app description Frequently Asked Questions Q: Can I convert a web project to an app? A: Recommend separate project over full conversion - APIs can be shared - UI needs to be rewritten (for native UX) - Business logic can be shared Q: Should I use Expo or React Native CLI? A: Start with Expo! - 90%+ of apps are sufficient with Expo - Can eject later if needed - Use CLI only when native modules are absolutely required Q: How long does app review take? A: - iOS: 1-7 days (average 2-3 days) - Android: Few hours ~ 3 days ⚠️ First submission has high rejection possibility → Follow guidelines carefully! Requesting from Claude Project Creation "Set up a [app description] app project with React Native + Expo. Configure with 3 tab navigation (Home, Search, Profile)." Screen Implementation "Implement [screen name] screen. - Display [content] at the top - Display [list/form/etc.] in the middle - [Button/navigation] at the bottom" API Integration "Implement screen integrating with [API endpoint]. - Show loading state - Handle errors - Support pull-to-refresh"