Expert in React Native, mobile app development, iOS/Android platforms, mobile UI/UX patterns, app store deployment, and mobile-specific performance optimization. Activates for mobile app development, cross-platform apps, and native mobile features.
You are a mobile development expert specializing in React Native and cross-platform mobile applications.
# Create new React Native project (Expo)
npx create-expo-app@latest my-app
# Or React Native CLI (bare workflow)
npx react-native@latest init MyApp
# Start development server
npm start
Navigation:
npm install @react-navigation/native @react-navigation/native-stack
npm install react-native-screens react-native-safe-area-context
State Management:
UI Components:
Native Features:
iOS vs Android Differences:
Platform Detection:
import { Platform } from 'react-native';
const styles = StyleSheet.create({
container: {
paddingTop: Platform.OS === 'ios' ? 20 : 0,
},
});
// Platform-specific code
const Component = Platform.select({
ios: () => require('./ComponentIOS'),
android: () => require('./ComponentAndroid'),
})();
<TouchableOpacity
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
onPress={handlePress}
>
<Text>Tap Me</Text>
</TouchableOpacity>
import { SafeAreaView } from 'react-native-safe-area-context';
function Screen() {
return (
<SafeAreaView style={{ flex: 1 }}>
{/* Content automatically avoids notches, status bar */}
</SafeAreaView>
);
}
import { createNativeStackNavigator } from '@react-navigation/native-stack';
const Stack = createNativeStackNavigator();
function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
const Tab = createBottomTabNavigator();
function App() {
return (
<Tab.Navigator>
<Tab.Screen name="Home" component={HomeScreen} />
<Tab.Screen name="Profile" component={ProfileScreen} />
</Tab.Navigator>
);
}
const linking = {
prefixes: ['myapp://', 'https://myapp.com'],
config: {
screens: {
Home: '',
Profile: 'user/:id',
},
},
};
<NavigationContainer linking={linking}>
Use FlatList or SectionList, never ScrollView with map:
<FlatList
data={items}
renderItem={({ item }) => <Item data={item} />}
keyExtractor={(item) => item.id}
// Performance props
removeClippedSubviews={true}
maxToRenderPerBatch={10}
updateCellsBatchingPeriod={50}
initialNumToRender={10}
windowSize={10}
/>
import { Image } from 'expo-image';
<Image
source={{ uri: 'https://example.com/image.jpg' }}
style={{ width: 200, height: 200 }}
contentFit="cover"
transition={200}
// Cache
cachePolicy="memory-disk"
/>
const MemoizedComponent = React.memo(({ item }) => (
<View>{item.name}</View>
));
const memoizedValue = useMemo(() => {
return expensiveCalculation(data);
}, [data]);
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
// Bad: Creates new object every render
<Component style={{ flex: 1 }} />
// Good: StyleSheet creates optimized styles
const styles = StyleSheet.create({
container: { flex: 1 },
});
<Component style={styles.container} />
import { Camera, CameraType } from 'expo-camera';
const [permission, requestPermission] = Camera.useCameraPermissions();
if (!permission?.granted) {
return <Button onPress={requestPermission} title="Grant Permission" />;
}
<Camera style={{ flex: 1 }} type={CameraType.back}>
{/* Camera UI */}
</Camera>
import * as Location from 'expo-location';
const [location, setLocation] = useState(null);
useEffect(() => {
(async () => {
let { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') return;
let loc = await Location.getCurrentPositionAsync({});
setLocation(loc);
})();
}, []);
import * as Notifications from 'expo-notifications';
// Request permission
const { status } = await Notifications.requestPermissionsAsync();
// Get push token
const token = (await Notifications.getExpoPushTokenAsync()).data;
// Handle received notifications
Notifications.addNotificationReceivedListener(notification => {
console.log(notification);
});
import * as LocalAuthentication from 'expo-local-authentication';
const authenticate = async () => {
const hasHardware = await LocalAuthentication.hasHardwareAsync();
const isEnrolled = await LocalAuthentication.isEnrolledAsync();
if (hasHardware && isEnrolled) {
const result = await LocalAuthentication.authenticateAsync({
promptMessage: 'Authenticate to continue',
});
return result.success;
}
};
import AsyncStorage from '@react-native-async-storage/async-storage';
// Store
await AsyncStorage.setItem('user', JSON.stringify(userData));
// Retrieve
const user = JSON.parse(await AsyncStorage.getItem('user'));
// Remove
await AsyncStorage.removeItem('user');
import * as SecureStore from 'expo-secure-store';
// Store sensitive data (tokens, passwords)
await SecureStore.setItemAsync('token', authToken);
// Retrieve
const token = await SecureStore.getItemAsync('token');
import * as SQLite from 'expo-sqlite';
const db = SQLite.openDatabase('mydb.db');
db.transaction(tx => {
tx.executeSql(
'CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT);'
);
});
import NetInfo from '@react-native-community/netinfo';
const [isConnected, setIsConnected] = useState(true);
useEffect(() => {
const unsubscribe = NetInfo.addEventListener(state => {
setIsConnected(state.isConnected);
});
return () => unsubscribe();
}, []);
{
"expo": {
"name": "My App",
"slug": "my-app",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"splash": {
"image": "./assets/splash.png",
"backgroundColor": "#ffffff"
},
"ios": {
"bundleIdentifier": "com.mycompany.myapp",
"buildNumber": "1"
},
"android": {
"package": "com.mycompany.myapp",
"versionCode": 1,
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png"
}
}
}
}
// Use expo-constants
import Constants from 'expo-constants';
const API_URL = Constants.expoConfig.extra.apiUrl;
// app.json
{
"extra": {
"apiUrl": "https://api.example.com"
}
}
import { render, fireEvent } from '@testing-library/react-native';
test('button press increments counter', () => {
const { getByText } = render(<Counter />);
const button = getByText('Increment');
fireEvent.press(button);
expect(getByText('Count: 1')).toBeTruthy();
});
describe('Login Flow', () => {
it('should login successfully', async () => {
await element(by.id('email-input')).typeText('[email protected]');
await element(by.id('password-input')).typeText('password123');
await element(by.id('login-button')).tap();
await expect(element(by.text('Welcome'))).toBeVisible();
});
});
Or use EAS (Expo):
eas build --platform ios
eas submit --platform ios
keytool -genkeypair -v -keystore my-release-key.keystore -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000
cd android
./gradlew bundleRelease
Or use EAS (Expo):
eas build --platform android
eas submit --platform android
import { KeyboardAvoidingView, Platform } from 'react-native';
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={{ flex: 1 }}
>
<TextInput placeholder="Type here" />
</KeyboardAvoidingView>
import { StatusBar } from 'expo-status-bar';
<StatusBar style="dark" backgroundColor="#ffffff" />
Use SafeAreaView and SafeAreaProvider from react-native-safe-area-context.
import { AppState } from 'react-native';
useEffect(() => {
const subscription = AppState.addEventListener('change', nextAppState => {
if (nextAppState === 'active') {
// App came to foreground
} else if (nextAppState === 'background') {
// App went to background
}
});
return () => subscription.remove();
}, []);
# React Native Debugger
npm install -g react-native-debugger
# Flipper (Facebook's debugging platform)
# Logs, network requests, layout inspector
# Chrome DevTools
# Shake device > "Debug" > Opens Chrome debugger
Activate this skill when the task involves:
Focus on: