UNPKG

agent-rules-kit

Version:

CLI tool to bootstrap AI agent rules for multiple IDEs and frameworks. Generates optimized rules for Cursor, VS Code, Claude, and 6+ other AI coding assistants.

511 lines (427 loc) 13.4 kB
--- description: Platform-specific code patterns for React Native applications globs: <root>/src/**/*.ts,<root>/src/**/*.tsx,<root>/src/**/*.js,<root>/src/**/*.jsx,<root>/android/**/*,<root>/ios/**/* alwaysApply: false --- # React Native Platform-Specific Code This guide covers platform-specific development patterns for React Native applications in {projectPath}, including iOS and Android differences, native modules, and platform detection. ## Platform Detection ### Basic Platform Detection ```javascript import { Platform } from 'react-native'; // Simple platform check if (Platform.OS === 'ios') { // iOS-specific code } else if (Platform.OS === 'android') { // Android-specific code } // Platform version check if (Platform.OS === 'ios' && Platform.Version >= '14') { // iOS 14+ specific code } if (Platform.OS === 'android' && Platform.Version >= 30) { // Android API 30+ specific code } ``` ### Platform-Specific Styling ```javascript import { Platform, StyleSheet } from 'react-native'; const styles = StyleSheet.create({ container: { flex: 1, paddingTop: Platform.OS === 'ios' ? 44 : 0, // iOS status bar ...Platform.select({ ios: { backgroundColor: '#F2F2F7', shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, }, android: { backgroundColor: '#FAFAFA', elevation: 4, }, }), }, header: { height: Platform.OS === 'ios' ? 44 : 56, backgroundColor: Platform.OS === 'ios' ? '#007AFF' : '#2196F3', }, button: { borderRadius: Platform.OS === 'ios' ? 8 : 4, paddingVertical: Platform.OS === 'ios' ? 12 : 8, }, }); ``` ## Platform-Specific Components ### Conditional Component Rendering ```javascript // components/PlatformButton.tsx import React from 'react'; import { Platform, TouchableOpacity, TouchableNativeFeedback } from 'react-native'; export const PlatformButton = ({ children, onPress, style }) => { if (Platform.OS === 'ios') { return ( <TouchableOpacity style={style} onPress={onPress}> {children} </TouchableOpacity> ); } return ( <TouchableNativeFeedback onPress={onPress}> <View style={style}>{children}</View> </TouchableNativeFeedback> ); }; ``` ### Platform-Specific File Extensions ```javascript // Create separate files for each platform: // Button.ios.tsx // Button.android.tsx // Button.tsx (fallback) // React Native automatically selects the correct file import { Button } from './components/Button'; // iOS implementation - Button.ios.tsx export const Button = ({ title, onPress }) => ( <TouchableOpacity style={iosStyles.button} onPress={onPress}> <Text style={iosStyles.text}>{title}</Text> </TouchableOpacity> ); // Android implementation - Button.android.tsx export const Button = ({ title, onPress }) => ( <TouchableNativeFeedback onPress={onPress}> <View style={androidStyles.button}> <Text style={androidStyles.text}>{title}</Text> </View> </TouchableNativeFeedback> ); ``` ## Native Modules Integration ### Using Native Modules ```javascript // services/BiometricsService.ts import TouchID from 'react-native-touch-id'; import { Platform } from 'react-native'; export class BiometricsService { static async isSupported(): Promise<boolean> { try { const biometryType = await TouchID.isSupported(); return !!biometryType; } catch (error) { return false; } } static async authenticate(): Promise<boolean> { try { const optionalConfigObject = { title: 'Authentication Required', imageColor: '#e00606', imageErrorColor: '#ff0000', sensorDescription: 'Touch sensor', sensorErrorDescription: 'Failed', cancelText: 'Cancel', fallbackLabel: 'Show Passcode', unifiedErrors: false, passcodeFallback: false, }; if (Platform.OS === 'android') { optionalConfigObject.title = 'Authenticate'; optionalConfigObject.subTitle = 'Use your fingerprint or face to authenticate'; } await TouchID.authenticate('Authenticate to access your account', optionalConfigObject); return true; } catch (error) { console.error('Biometric authentication failed:', error); return false; } } } ``` ### Camera Integration ```javascript // services/CameraService.ts import { launchImageLibrary, launchCamera, MediaType } from 'react-native-image-picker'; import { Platform, Alert, Linking } from 'react-native'; import { request, PERMISSIONS, RESULTS } from 'react-native-permissions'; export class CameraService { static async requestCameraPermission(): Promise<boolean> { try { const permission = Platform.OS === 'ios' ? PERMISSIONS.IOS.CAMERA : PERMISSIONS.ANDROID.CAMERA; const result = await request(permission); return result === RESULTS.GRANTED; } catch (error) { console.error('Permission request failed:', error); return false; } } static async openCamera(): Promise<any> { const hasPermission = await this.requestCameraPermission(); if (!hasPermission) { Alert.alert( 'Camera Permission', 'Please enable camera permission in settings to take photos.', [ { text: 'Cancel', style: 'cancel' }, { text: 'Settings', onPress: () => Linking.openSettings() } ] ); return null; } return new Promise((resolve) => { const options = { mediaType: 'photo' as MediaType, quality: 0.8, maxWidth: 1000, maxHeight: 1000, }; launchCamera(options, (response) => { if (response.assets && response.assets[0]) { resolve(response.assets[0]); } else { resolve(null); } }); }); } static async openImageLibrary(): Promise<any> { return new Promise((resolve) => { const options = { mediaType: 'photo' as MediaType, quality: 0.8, maxWidth: 1000, maxHeight: 1000, selectionLimit: 1, }; launchImageLibrary(options, (response) => { if (response.assets && response.assets[0]) { resolve(response.assets[0]); } else { resolve(null); } }); }); } } ``` ## Platform-Specific Navigation ### Status Bar Handling ```javascript // components/StatusBarManager.tsx import React from 'react'; import { StatusBar, Platform } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; export const StatusBarManager = ({ barStyle = 'dark-content', backgroundColor }) => { const insets = useSafeAreaInsets(); if (Platform.OS === 'ios') { return ( <StatusBar barStyle={barStyle} backgroundColor="transparent" translucent /> ); } return ( <StatusBar barStyle={barStyle} backgroundColor={backgroundColor || '#FFFFFF'} translucent={false} /> ); }; ``` ### Safe Area Handling ```javascript // components/SafeScreen.tsx import React from 'react'; import { View, Platform, StatusBar } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; export const SafeScreen = ({ children, style }) => { const insets = useSafeAreaInsets(); const safeAreaStyle = { flex: 1, paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight : insets.top, paddingBottom: insets.bottom, paddingLeft: insets.left, paddingRight: insets.right, }; return ( <View style={[safeAreaStyle, style]}> {children} </View> ); }; ``` ## Device-Specific Features ### Haptic Feedback ```javascript // utils/haptics.ts import { Platform } from 'react-native'; let HapticFeedback: any; if (Platform.OS === 'ios') { HapticFeedback = require('react-native-haptic-feedback').default; } export class HapticsService { static trigger(type: 'selection' | 'impactLight' | 'impactMedium' | 'impactHeavy' | 'notificationSuccess' | 'notificationWarning' | 'notificationError') { if (Platform.OS === 'ios' && HapticFeedback) { const options = { enableVibrateFallback: true, ignoreAndroidSystemSettings: false, }; HapticFeedback.trigger(type, options); } else if (Platform.OS === 'android') { // Android vibration fallback const { Vibration } = require('react-native'); switch (type) { case 'selection': case 'impactLight': Vibration.vibrate(10); break; case 'impactMedium': Vibration.vibrate(20); break; case 'impactHeavy': case 'notificationError': Vibration.vibrate(50); break; case 'notificationSuccess': case 'notificationWarning': Vibration.vibrate([10, 50]); break; } } } } ``` ### Device Info ```javascript // utils/deviceInfo.ts import DeviceInfo from 'react-native-device-info'; import { Platform, Dimensions } from 'react-native'; export class DeviceInfoService { static async getDeviceInfo() { const { width, height } = Dimensions.get('window'); return { platform: Platform.OS, platformVersion: Platform.Version, deviceId: await DeviceInfo.getUniqueId(), deviceName: await DeviceInfo.getDeviceName(), systemName: await DeviceInfo.getSystemName(), systemVersion: await DeviceInfo.getSystemVersion(), appVersion: DeviceInfo.getVersion(), buildNumber: DeviceInfo.getBuildNumber(), bundleId: DeviceInfo.getBundleId(), screenWidth: width, screenHeight: height, isTablet: DeviceInfo.isTablet(), hasNotch: await DeviceInfo.hasNotch(), isEmulator: await DeviceInfo.isEmulator(), }; } static isIPhoneX(): boolean { if (Platform.OS !== 'ios') return false; const { height, width } = Dimensions.get('window'); return ( (height === 812 || width === 812) || // iPhone X, XS (height === 896 || width === 896) || // iPhone XR, XS Max (height === 844 || width === 844) || // iPhone 12, 12 Pro (height === 926 || width === 926) // iPhone 12 Pro Max ); } } ``` ## Platform-Specific Storage ### Secure Storage ```javascript // services/SecureStorageService.ts import { Platform } from 'react-native'; import AsyncStorage from '@react-native-async-storage/async-storage'; let Keychain: any; if (Platform.OS === 'ios') { Keychain = require('react-native-keychain'); } export class SecureStorageService { // For sensitive data (tokens, passwords) static async setSecureItem(key: string, value: string): Promise<void> { if (Platform.OS === 'ios' && Keychain) { await Keychain.setInternetCredentials(key, key, value); } else { // Android fallback - consider using encrypted storage await AsyncStorage.setItem(`secure_${key}`, value); } } static async getSecureItem(key: string): Promise<string | null> { if (Platform.OS === 'ios' && Keychain) { try { const credentials = await Keychain.getInternetCredentials(key); return credentials ? credentials.password : null; } catch (error) { return null; } } else { return AsyncStorage.getItem(`secure_${key}`); } } static async removeSecureItem(key: string): Promise<void> { if (Platform.OS === 'ios' && Keychain) { await Keychain.resetInternetCredentials(key); } else { await AsyncStorage.removeItem(`secure_${key}`); } } // For regular data static async setItem(key: string, value: string): Promise<void> { await AsyncStorage.setItem(key, value); } static async getItem(key: string): Promise<string | null> { return AsyncStorage.getItem(key); } static async removeItem(key: string): Promise<void> { await AsyncStorage.removeItem(key); } } ``` ## Testing Platform-Specific Code ### Platform Mocking ```javascript // __tests__/utils/platformMocks.ts export const mockPlatform = (OS: 'ios' | 'android', Version?: number) => { jest.doMock('react-native/Libraries/Utilities/Platform', () => ({ OS, Version: Version || (OS === 'ios' ? '14.0' : 30), select: jest.fn((obj) => obj[OS]), })); }; // __tests__/components/PlatformButton.test.tsx import { render } from '@testing-library/react-native'; import { PlatformButton } from '../PlatformButton'; import { mockPlatform } from '../utils/platformMocks'; describe('PlatformButton', () => { beforeEach(() => { jest.resetModules(); }); it('renders TouchableOpacity on iOS', () => { mockPlatform('ios'); const { getByTestId } = render( <PlatformButton testID="platform-button" onPress={jest.fn()}> <Text>Button</Text> </PlatformButton> ); // Test iOS-specific behavior }); it('renders TouchableNativeFeedback on Android', () => { mockPlatform('android'); const { getByTestId } = render( <PlatformButton testID="platform-button" onPress={jest.fn()}> <Text>Button</Text> </PlatformButton> ); // Test Android-specific behavior }); }); ``` This guide provides comprehensive patterns for handling platform-specific code in React Native applications, ensuring optimal user experience on both iOS and Android platforms.