š Overview
In this final part, we'll:
- Add UI polish (splash screen, empty states)
- Implement error handling
- Add network status indicator
- Configure EAS Build
- Deploy to Google Play Store
šØ Step 1: UI Polish
A. Empty State Component
File: components/EmptyState.tsx
import React from 'react';
import { View, Text } from 'react-native';
import { FileQuestion } from 'lucide-react-native';
interface EmptyStateProps {
icon?: React.ReactNode;
title: string;
message?: string;
}
export default function EmptyState({ icon, title, message }: EmptyStateProps) {
return (
<View className="flex-1 items-center justify-center p-8">
{icon || <FileQuestion size={64} color="#6b7280" />}
<Text className="text-white text-xl font-bold mt-6 text-center">
{title}
</Text>
{message && (
<Text className="text-gray-400 text-center mt-2">
{message}
</Text>
)}
</View>
);
}
B. Error Boundary Component
File: components/ErrorBoundary.tsx
import React, { Component, ReactNode } from 'react';
import { View, Text, TouchableOpacity } from 'react-native';
import { AlertTriangle, RefreshCw } from 'lucide-react-native';
interface Props {
children: ReactNode;
}
interface State {
hasError: boolean;
error?: Error;
}
export default class ErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error };
}
handleReset = () => {
this.setState({ hasError: false, error: undefined });
};
render() {
if (this.state.hasError) {
return (
<View className="flex-1 bg-gray-900 items-center justify-center p-8">
<AlertTriangle size={64} color="#ef4444" />
<Text className="text-white text-xl font-bold mt-6 text-center">
Something went wrong
</Text>
<Text className="text-gray-400 text-center mt-2 mb-6">
{this.state.error?.message || 'An unexpected error occurred'}
</Text>
<TouchableOpacity
className="bg-orange-500 px-6 py-3 rounded-xl flex-row items-center"
onPress={this.handleReset}
>
<RefreshCw size={20} color="white" />
<Text className="text-white font-semibold ml-2">Try Again</Text>
</TouchableOpacity>
</View>
);
}
return this.props.children;
}
}
C. Network Status Indicator
Install netinfo:
npx expo install @react-native-community/netinfo
File: components/NetworkStatus.tsx
import React, { useEffect, useState } from 'react';
import { View, Text, Animated } from 'react-native';
import NetInfo from '@react-native-community/netinfo';
import { WifiOff } from 'lucide-react-native';
export default function NetworkStatus() {
const [isConnected, setIsConnected] = useState(true);
const [slideAnim] = useState(new Animated.Value(-60));
useEffect(() => {
const unsubscribe = NetInfo.addEventListener((state) => {
const connected = state.isConnected ?? true;
setIsConnected(connected);
Animated.timing(slideAnim, {
toValue: connected ? -60 : 0,
duration: 300,
useNativeDriver: true,
}).start();
});
return () => unsubscribe();
}, []);
return (
<Animated.View
style={{ transform: [{ translateY: slideAnim }] }}
className="absolute top-0 left-0 right-0 bg-red-500 px-4 py-3 flex-row items-center justify-center z-50"
>
<WifiOff size={16} color="white" />
<Text className="text-white ml-2 font-medium">No Internet Connection</Text>
</Animated.View>
);
}
š”ļø Step 2: Error Handling Utilities
File: utils/errorHandler.ts
import { Alert } from 'react-native';
export const handleApiError = (error: any, defaultMessage = 'An error occurred') => {
console.error('API Error:', error);
if (error.code === 'ECONNABORTED') {
Alert.alert('Timeout', 'Request timed out. Please check your connection.');
return;
}
if (error.response?.status === 401) {
Alert.alert('Session Expired', 'Please login again.');
return;
}
if (error.response?.status === 403) {
Alert.alert('Access Denied', 'You do not have permission.');
return;
}
if (error.response?.status >= 500) {
Alert.alert('Server Error', 'Server is temporarily unavailable.');
return;
}
Alert.alert('Error', error.response?.data?.error || defaultMessage);
};
export const withRetry = async <T>(
fn: () => Promise<T>,
retries = 3,
delay = 1000
): Promise<T> => {
try {
return await fn();
} catch (error) {
if (retries > 0) {
await new Promise((r) => setTimeout(r, delay));
return withRetry(fn, retries - 1, delay * 2);
}
throw error;
}
};
š¦ Step 3: Configure EAS Build
A. Install EAS CLI
npm install -g eas-cli
eas login
B. Initialize EAS
eas build:configure
C. Configure eas.json
{
"cli": {
"version": ">= 7.0.0"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal"
},
"preview": {
"distribution": "internal",
"android": {
"buildType": "apk"
}
},
"production": {
"android": {
"buildType": "app-bundle"
},
"ios": {
"resourceClass": "m-medium"
}
}
},
"submit": {
"production": {}
}
}
š Step 4: Build for Android
Build APK (for testing)
eas build --platform android --profile preview
Build AAB (for Play Store)
eas build --platform android --profile production
š± Step 5: Deploy to Google Play Store
Prerequisites
- Google Play Developer account ($25 one-time fee)
- App icons and screenshots
- Privacy policy URL
- App description
Steps
-
Create App in Play Console
- Go to play.google.com/console
- Create app ā Fill details
-
Upload AAB
- Production ā Create new release
- Upload the AAB file from EAS
-
Fill Store Listing
- App name, description
- Screenshots (phone, tablet)
- Feature graphic (1024x500)
-
Complete Content Rating
- Answer questionnaire
- Get rating
-
Submit for Review
- Review usually takes 1-3 days
- First submission may take up to 7 days
ā Final Checklist
UI Polish
- Empty state components
- Error boundary
- Network status indicator
- Loading states
Error Handling
- Centralized error handler
- Retry logic
- User-friendly messages
Build & Deploy
- EAS Build configured
- APK tested on device
- AAB uploaded to Play Store
- Store listing complete
š Congratulations!
You've completed the React Native Expo Event Management series! You now have:
- ā A fully functional mobile app
- ā Authentication with secure storage
- ā Event management with real-time data
- ā QR scanner for attendance
- ā Production-ready build
What's Next?
- Add push notifications
- Implement offline mode
- Add analytics
- Create iOS version
ā Part 6: QR Scanner | Back to Part 1 ā
This series was created to support the Casual Meetup #15 at LampungDev. Thank you for following along!
