Key Code Examples
1. React Native โ Basic App with Navigation
import React, { useState } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, FlatList } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
const Stack = createNativeStackNavigator();
// Home Screen Component
function HomeScreen({ navigation }) {
const [tasks, setTasks] = useState([
{ id: '1', title: 'Buy groceries', completed: false },
{ id: '2', title: 'Walk the dog', completed: true },
{ id: '3', title: 'Read a book', completed: false },
]);
const toggleTask = (id) => {
setTasks(prev => prev.map(task =>
task.id === id ? { ...task, completed: !task.completed } : task
));
};
return (
My Tasks
item.id}
renderItem={({ item }) => (
toggleTask(item.id)}
>
{item.title}
{item.completed ? 'โ' : 'โ'}
)}
/>
navigation.navigate('AddTask')}
>
+ Add Task
);
}
const styles = StyleSheet.create({
container: { flex: 1, padding: 20, backgroundColor: '#f5f5f5' },
title: { fontSize: 28, fontWeight: 'bold', marginBottom: 20 },
taskItem: {
flexDirection: 'row',
justifyContent: 'space-between',
padding: 15,
backgroundColor: 'white',
borderRadius: 8,
marginBottom: 10,
elevation: 2,
},
completed: { opacity: 0.6 },
taskText: { fontSize: 16 },
button: {
backgroundColor: '#007AFF',
padding: 15,
borderRadius: 8,
alignItems: 'center',
},
buttonText: { color: 'white', fontSize: 18, fontWeight: '600' },
});
export default function App() {
return (
);
}
2. Flutter โ Stateful Widget with API Call
import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Weather App',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
),
home: const WeatherScreen(),
);
}
}
class WeatherScreen extends StatefulWidget {
const WeatherScreen({super.key});
@override
State createState() => _WeatherScreenState();
}
class _WeatherScreenState extends State {
String _temperature = '--';
String _condition = 'Loading...';
String _city = 'London';
bool _isLoading = false;
Future _fetchWeather() async {
setState(() => _isLoading = true);
try {
final response = await http.get(
Uri.parse('https://api.openweathermap.org/data/2.5/'
'weather?q=$_city&appid=YOUR_API_KEY&units=metric'),
);
if (response.statusCode == 200) {
final data = json.decode(response.body);
setState(() {
_temperature = '${data['main']['temp'].round()}ยฐC';
_condition = data['weather'][0]['description'];
});
}
} catch (e) {
setState(() => _condition = 'Error: $e');
}
setState(() => _isLoading = false);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Weather in $_city')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(_getIcon(_condition), size: 80, color: Colors.orange),
const SizedBox(height: 20),
Text(_temperature, style: const TextStyle(fontSize: 48)),
Text(_condition, style: const TextStyle(fontSize: 24)),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _isLoading ? null : _fetchWeather,
child: _isLoading
? const CircularProgressIndicator()
: const Text('Refresh'),
),
],
),
),
);
}
IconData _getIcon(String condition) {
if (condition.contains('cloud')) return Icons.cloud;
if (condition.contains('rain')) return Icons.water_drop;
return Icons.wb_sunny;
}
}
3. Kotlin โ Android Activity with RecyclerView
class MainActivity : AppCompatActivity() {
private lateinit var recyclerView: RecyclerView
private lateinit var adapter: ProductAdapter
private val products = mutableListOf
()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setupRecyclerView()
loadProducts()
setupSwipeRefresh()
}
private fun setupRecyclerView() {
recyclerView = findViewById(R.id.recyclerView)
adapter = ProductAdapter(products) { product ->
onProductClicked(product)
}
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = adapter
// Add dividers
recyclerView.addItemDecoration(
DividerItemDecoration(this, DividerItemDecoration.VERTICAL)
)
}
private fun loadProducts() {
lifecycleScope.launch {
try {
val response = RetrofitClient.apiService.getProducts()
products.clear()
products.addAll(response)
adapter.notifyDataSetChanged()
} catch (e: Exception) {
Toast.makeText(this@MainActivity,
"Error: ${e.message}", Toast.LENGTH_SHORT).show()
}
}
}
private fun setupSwipeRefresh() {
val swipeRefresh: SwipeRefreshLayout = findViewById(R.id.swipeRefresh)
swipeRefresh.setOnRefreshListener { loadProducts() }
}
private fun onProductClicked(product: Product) {
val intent = Intent(this, ProductDetailActivity::class.java).apply {
putExtra("product_id", product.id)
}
startActivity(intent)
}
}
// RecyclerView Adapter
class ProductAdapter(
private val items: List,
private val onClick: (Product) -> Unit
) : RecyclerView.Adapter() {
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val nameText: TextView = view.findViewById(R.id.productName)
val priceText: TextView = view.findViewById(R.id.productPrice)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_product, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = items[position]
holder.nameText.text = item.name
holder.priceText.text = "$${item.price}"
holder.itemView.setOnClickListener { onClick(item) }
}
override fun getItemCount() = items.size
}
4. Swift โ SwiftUI List with Data Persistence
import SwiftUI
struct Todo: Identifiable, Codable {
let id: UUID
var title: String
var isCompleted: Bool
var createdAt: Date
init(title: String) {
self.id = UUID()
self.title = title
self.isCompleted = false
self.createdAt = Date()
}
}
class TodoStore: ObservableObject {
@Published var todos: [Todo] = [] {
didSet { save() }
}
private let key = "SavedTodos"
init() {
load()
}
func add(title: String) {
todos.append(Todo(title: title))
}
func toggle(_ todo: Todo) {
if let index = todos.firstIndex(where: { $0.id == todo.id }) {
todos[index].isCompleted.toggle()
}
}
func delete(at offsets: IndexSet) {
todos.remove(atOffsets: offsets)
}
private func save() {
if let data = try? JSONEncoder().encode(todos) {
UserDefaults.standard.set(data, forKey: key)
}
}
private func load() {
if let data = UserDefaults.standard.data(forKey: key),
let decoded = try? JSONDecoder().decode([Todo].self, from: data) {
todos = decoded
}
}
}
struct ContentView: View {
@StateObject private var store = TodoStore()
@State private var newTodoTitle = ""
var body: some View {
NavigationView {
List {
ForEach(store.todos) { todo in
HStack {
Image(systemName: todo.isCompleted ? "checkmark.circle.fill" : "circle")
.foregroundColor(todo.isCompleted ? .green : .gray)
.onTapGesture { store.toggle(todo) }
Text(todo.title)
.strikethrough(todo.isCompleted)
Spacer()
}
}
.onDelete(perform: store.delete)
}
.navigationTitle("Todos")
.toolbar {
HStack {
TextField("New todo", text: $newTodoTitle)
Button("Add") {
if !newTodoTitle.isEmpty {
store.add(title: newTodoTitle)
newTodoTitle = ""
}
}
}
}
}
}
}
5. React Native โ Offline Storage with AsyncStorage
import React, { useState, useEffect } from 'react';
import { View, Text, TextInput, Button, FlatList, StyleSheet } from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
const STORAGE_KEY = '@notes_app';
function NotesApp() {
const [notes, setNotes] = useState([]);
const [title, setTitle] = useState('');
const [body, setBody] = useState('');
const [isLoading, setIsLoading] = useState(true);
// Load notes on app start
useEffect(() => {
loadNotes();
}, []);
const loadNotes = async () => {
try {
const stored = await AsyncStorage.getItem(STORAGE_KEY);
if (stored) {
setNotes(JSON.parse(stored));
}
} catch (error) {
console.error('Failed to load notes:', error);
}
setIsLoading(false);
};
const saveNotes = async (updatedNotes) => {
try {
await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(updatedNotes));
} catch (error) {
console.error('Failed to save notes:', error);
}
};
const addNote = () => {
if (!title.trim()) return;
const newNote = {
id: Date.now().toString(),
title: title.trim(),
body: body.trim(),
createdAt: new Date().toISOString(),
};
const updated = [newNote, ...notes];
setNotes(updated);
saveNotes(updated);
setTitle('');
setBody('');
};
const deleteNote = (id) => {
const updated = notes.filter(n => n.id !== id);
setNotes(updated);
saveNotes(updated);
};
if (isLoading) return Loading... ;
return (
Offline Notes
item.id}
renderItem={({ item }) => (
{item.title}
{item.body}
{new Date(item.createdAt).toLocaleDateString()}
deleteNote(item.id)} />
)}
/>
);
}
const styles = StyleSheet.create({
container: { flex: 1, padding: 16, backgroundColor: '#f8f9fa' },
heading: { fontSize: 24, fontWeight: 'bold', marginBottom: 16 },
input: {
borderWidth: 1, borderColor: '#ddd', borderRadius: 8,
padding: 12, marginBottom: 12, backgroundColor: 'white',
},
multiline: { height: 100, textAlignVertical: 'top' },
noteCard: {
backgroundColor: 'white', padding: 16, borderRadius: 8,
marginBottom: 12, elevation: 2,
},
noteTitle: { fontSize: 18, fontWeight: '600' },
noteBody: { fontSize: 14, color: '#666', marginTop: 4 },
noteDate: { fontSize: 12, color: '#999', marginTop: 8 },
});
export default NotesApp;
6. Firebase โ Authentication & Firestore
// Firebase config (firebase.js)
import { initializeApp } from 'firebase/app';
import { getAuth, createUserWithEmailAndPassword, signInWithEmailAndPassword, signOut } from 'firebase/auth';
import { getFirestore, collection, addDoc, getDocs, query, where, orderBy, serverTimestamp } from 'firebase/firestore';
const firebaseConfig = {
apiKey: "YOUR_API_KEY",
authDomain: "your-app.firebaseapp.com",
projectId: "your-app",
appId: "YOUR_APP_ID"
};
const app = initializeApp(firebaseConfig);
export const auth = getAuth(app);
export const db = getFirestore(app);
// Auth Service
export async function registerUser(email, password, displayName) {
const cred = await createUserWithEmailAndPassword(auth, email, password);
await addDoc(collection(db, 'users'), {
uid: cred.user.uid,
email,
displayName,
createdAt: serverTimestamp(),
});
return cred.user;
}
export async function loginUser(email, password) {
const cred = await signInWithEmailAndPassword(auth, email, password);
return cred.user;
}
// Firestore Service
export async function getUserTasks(userId) {
const q = query(
collection(db, 'tasks'),
where('userId', '==', userId),
orderBy('createdAt', 'desc')
);
const snapshot = await getDocs(q);
return snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
}
export async function addTask(userId, title, description) {
return await addDoc(collection(db, 'tasks'), {
userId,
title,
description,
completed: false,
createdAt: serverTimestamp(),
});
}
// Auth Hook (React)
import { useState, useEffect } from 'react';
import { onAuthStateChanged } from 'firebase/auth';
export function useAuth() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
setUser(user);
setLoading(false);
});
return unsubscribe;
}, []);
return { user, loading };
}
7. Flutter โ BLoC Pattern for State Management
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:equatable/equatable.dart';
// ===== Events =====
abstract class CounterEvent extends Equatable {
@override
List get props => [];
}
class CounterIncrement extends CounterEvent {}
class CounterDecrement extends CounterEvent {}
class CounterReset extends CounterEvent {}
// ===== State =====
class CounterState extends Equatable {
final int value;
const CounterState({this.value = 0});
@override
List get props => [value];
}
// ===== BLoC =====
class CounterBloc extends Bloc {
CounterBloc() : super(const CounterState()) {
on((event, emit) {
emit(CounterState(value: state.value + 1));
});
on((event, emit) {
emit(CounterState(value: state.value - 1));
});
on((event, emit) {
emit(const CounterState(value: 0));
});
}
}
// ===== UI =====
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => CounterBloc(),
child: Scaffold(
appBar: AppBar(title: const Text('BLoC Counter')),
body: BlocBuilder(
builder: (context, state) {
return Center(
child: Text(
'${state.value}',
style: const TextStyle(fontSize: 72),
),
);
},
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
onPressed: () => context.read().add(CounterIncrement()),
child: const Icon(Icons.add),
),
const SizedBox(height: 8),
FloatingActionButton(
onPressed: () => context.read().add(CounterDecrement()),
child: const Icon(Icons.remove),
),
const SizedBox(height: 8),
FloatingActionButton(
onPressed: () => context.read().add(CounterReset()),
child: const Icon(Icons.refresh),
),
],
),
),
);
}
}
8. React Native โ Push Notifications
import React, { useEffect, useState } from 'react';
import { View, Text, Platform, Alert } from 'react-native';
import messaging from '@react-native-firebase/messaging';
import notifee, { AndroidImportance } from '@notifee/react-native';
// Request notification permission (iOS)
async function requestPermission() {
const authStatus = await messaging().requestPermission();
const enabled =
authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
authStatus === messaging.AuthorizationStatus.PROVISIONAL;
if (enabled) {
console.log('Notification permission granted');
const token = await messaging().getToken();
console.log('FCM Token:', token);
// Send token to your server
}
}
// Create notification channel (Android)
async function createChannel() {
await notifee.createChannel({
id: 'default',
name: 'Default Channel',
importance: AndroidImportance.HIGH,
});
}
// Foreground message handler
function setupMessageHandler() {
messaging().onMessage(async remoteMessage => {
console.log('Foreground message:', remoteMessage);
// Display local notification
await notifee.displayNotification({
title: remoteMessage.notification?.title,
body: remoteMessage.notification?.body,
android: {
channelId: 'default',
importance: AndroidImportance.HIGH,
pressAction: { id: 'default' },
},
});
});
// Background/quit state handler
messaging().setBackgroundMessageHandler(async remoteMessage => {
console.log('Background message:', remoteMessage);
});
// Notification opened app
messaging().onNotificationOpenedApp(remoteMessage => {
console.log('Notification opened app:', remoteMessage.notification);
});
}
function App() {
useEffect(() => {
requestPermission();
createChannel();
setupMessageHandler();
}, []);
return (
Push Notification Demo
);
}
export default App;
9. Kotlin โ Coroutines & Flow for Network Calls
// Repository pattern with Coroutines and Flow
class WeatherRepository(private val api: WeatherApi) {
fun getWeatherStream(city: String): Flow> = flow {
emit(Result.Loading)
try {
val response = api.getWeather(city)
emit(Result.Success(response))
} catch (e: Exception) {
emit(Result.Error(e.message ?: "Unknown error"))
}
}.flowOn(Dispatchers.IO) // Run on IO thread
.catch { e ->
emit(Result.Error(e.message ?: "Network error"))
}
}
// ViewModel
class WeatherViewModel(
private val repository: WeatherRepository
) : ViewModel() {
private val _weather = MutableStateFlow?>(null)
val weather: StateFlow?> = _weather.asStateFlow()
fun loadWeather(city: String) {
viewModelScope.launch {
repository.getWeatherStream(city).collect { result ->
_weather.value = result
}
}
}
}
// UI (Jetpack Compose)
@Composable
fun WeatherScreen(viewModel: WeatherViewModel) {
val weatherState by viewModel.weather.collectAsState()
Column(modifier = Modifier.fillMaxSize().padding(16)) {
when (val state = weatherState) {
is Result.Loading -> CircularProgressIndicator()
is Result.Success -> {
Text("Temp: ${state.data.temp}ยฐC",
style = MaterialTheme.typography.headlineMedium)
Text("Condition: ${state.data.condition}")
}
is Result.Error -> Text("Error: ${state.message}",
color = Color.Red)
null -> Text("Enter a city to get weather")
}
}
}
// Retrofit API Interface
interface WeatherApi {
@GET("weather")
suspend fun getWeather(
@Query("q") city: String,
@Query("appid") apiKey: String,
@Query("units") units: String = "metric"
): Weather
}
10. React Native โ Animations with Reanimated
import React, { useState } from 'react';
import { View, StyleSheet, Button } from 'react-native';
import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring,
withTiming,
withRepeat,
withSequence,
Easing,
} from 'react-native-reanimated';
function AnimatedCard() {
const [expanded, setExpanded] = useState(false);
const scale = useSharedValue(1);
const rotation = useSharedValue(0);
const opacity = useSharedValue(1);
const offsetX = useSharedValue(0);
const animatedStyle = useAnimatedStyle(() => ({
transform: [
{ scale: scale.value },
{ translateX: offsetX.value },
{ rotate: `${rotation.value}deg` },
],
opacity: opacity.value,
}));
const handleTap = () => {
scale.value = withSpring(scale.value === 1 ? 1.2 : 1);
rotation.value = withSequence(
withTiming(10, { duration: 100 }),
withTiming(-10, { duration: 100 }),
withTiming(0, { duration: 100 })
);
};
const handleShake = () => {
offsetX.value = withRepeat(
withSequence(
withTiming(10, { duration: 50 }),
withTiming(-10, { duration: 50 })
),
3, true
);
};
const handlePulse = () => {
opacity.value = withRepeat(
withSequence(
withTiming(0.3, { duration: 500 }),
withTiming(1, { duration: 500 })
),
-1, true // Infinite
);
};
return (
);
}
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
card: {
width: 200, height: 200, backgroundColor: '#6200EE',
borderRadius: 16, justifyContent: 'center',
elevation: 8,
},
});
export default AnimatedCard;
Core Concepts
Native vs. Hybrid vs. Cross-Platform
Native: Built specifically for one platform (iOS Swift/Obj-C, Android Kotlin/Java). Best performance and UI consistency.
Hybrid: Web app wrapped in native container (Cordova, Ionic). Single codebase, WebView-based.
Cross-Platform: Single codebase compiles to native (React Native, Flutter). Near-native performance.
Approach Performance Dev Speed UI Quality
Native โ
โ
โ
โ
โ
โ
โ
โ
โ
โ
โ
โ
Cross-Platform โ
โ
โ
โ
โ
โ
โ
โ
โ
โ
โ
โ
Hybrid โ
โ
โ
โ
โ
โ
โ
โ
โ
โ
Mobile App Architecture Patterns
MVC (Model-View-Controller): Traditional pattern; Controller mediates between Model and View. Common in iOS (UIKit).
MVVM (Model-View-ViewModel): ViewModel exposes data streams to View; binding connects them. Popular in Android and SwiftUI.
BLoC (Business Logic Component): Events in โ BLoC โ States out. Flutter's recommended pattern.
Redux: Single state tree, reducers handle actions. Popular in React Native.
Mobile UI/UX Principles
Touch Targets: Minimum 44ร44pt (iOS) / 48ร48dp (Android) for tappable elements
Thumb Zone: Place primary actions within easy thumb reach
Responsive Layouts: Use constraints, not fixed positions
Platform Guidelines: Follow Material Design (Android) and Human Interface Guidelines (iOS)
Navigation: Tab bars, stack navigation, drawer menus
Accessibility: VoiceOver/TalkBack support, dynamic text sizing
State Management
Definition: Managing the data (state) that drives the UI of an application.
Local State: useState (React), State (Flutter) โ component-level
App State: Redux, MobX, Riverpod, BLoC โ global/shared state
Server State: React Query, SWR โ cached server data with sync
Persistence: SharedPreferences, UserDefaults, AsyncStorage โ local storage
Mobile Networking
REST APIs: HTTP/JSON communication with backend services
GraphQL: Query language for APIs โ fetch exactly what you need
WebSockets: Persistent two-way connection for real-time data
Offline-First: Local database (SQLite, Realm, Hive) with sync when online
Security: Certificate pinning, OAuth 2.0, token refresh, encryption at rest
Mobile App Performance
Startup Time: Cold start < 2s, warm start < 1s
Frame Rate: Maintain 60fps (16ms per frame) for smooth scrolling
Memory: Avoid leaks; use profiling tools (Instruments, Android Profiler)
Battery: Minimize network calls, use batch processing, reduce wake locks
App Size: Use ProGuard/R8 (Android), App Thinning (iOS), code splitting
Lists: Use virtualization (FlatList, RecyclerView) for large datasets
App Store Deployment
iOS (App Store):
Apple Developer account ($99/year)
Archive build in Xcode โ Upload to App Store Connect
App review process (24-48 hours)
TestFlight for beta testing
Android (Google Play):
Google Play Developer account ($25 one-time)
Generate signed AAB (Android App Bundle)
Upload to Google Play Console
Automated review (hours to days)
Push Notifications
Definition: Messages sent from a server to a user's device even when the app is not active.
FCM (Firebase Cloud Messaging): Cross-platform (Android + iOS), free
APNs (Apple Push Notification service): Apple-only, requires certificate
Local Notifications: Scheduled by the app, no server needed
Deep Linking: Notification opens specific app screen
Best Practices: Personalize, allow opt-out, respect quiet hours
Worked Examples
Example 1: Build a Complete React Native Screen
Problem: Create a profile screen with avatar, name, bio, and edit button.
function ProfileScreen({ navigation }) {
const [profile, setProfile] = useState({
name: 'Jane Doe',
email: 'jane@example.com',
bio: 'Mobile developer & coffee lover',
});
return (
{profile.name}
{profile.email}
About
{profile.bio}
navigation.navigate('EditProfile', {
profile,
onSave: (updated) => setProfile(updated),
})}
>
Edit Profile
);
}
Example 2: Flutter Form Validation
Problem: Create a login form with email and password validation.
class LoginForm extends StatefulWidget {
@override
_LoginFormState createState() => _LoginFormState();
}
class _LoginFormState extends State {
final _formKey = GlobalKey();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
bool _isLoading = false;
String? validateEmail(String? value) {
if (value == null || value.isEmpty) return 'Email is required';
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) {
return 'Enter a valid email';
}
return null;
}
String? validatePassword(String? value) {
if (value == null || value.isEmpty) return 'Password is required';
if (value.length < 8) return 'Password must be 8+ characters';
return null;
}
Future _submit() async {
if (!_formKey.currentState!.validate()) return;
setState(() => _isLoading = true);
await Future.delayed(Duration(seconds: 2)); // API call
setState(() => _isLoading = false);
Navigator.pushReplacementNamed(context, '/home');
}
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(children: [
TextFormField(
controller: _emailController,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(labelText: 'Email'),
validator: validateEmail,
),
TextFormField(
controller: _passwordController,
obscureText: true,
decoration: InputDecoration(labelText: 'Password'),
validator: validatePassword,
),
ElevatedButton(
onPressed: _isLoading ? null : _submit,
child: _isLoading
? CircularProgressIndicator()
: Text('Login'),
),
]),
);
}
}
Example 3: Implement Offline-First Sync
Problem: Build a notes app that works offline and syncs when online.
// Sync manager using React Native + NetInfo
import NetInfo from '@react-native-community/netinfo';
import AsyncStorage from '@react-native-async-storage/async-storage';
class SyncManager {
private pendingChanges = [];
private isOnline = true;
constructor() {
NetInfo.addEventListener(state => {
this.isOnline = state.isConnected;
if (this.isOnline) this.flushChanges();
});
}
async saveNote(note) {
// Always save locally first
await this.saveLocally(note);
if (this.isOnline) {
try {
await api.post('/notes', note);
await this.markAsSynced(note.id);
} catch (e) {
this.pendingChanges.push({ type: 'create', note });
}
} else {
this.pendingChanges.push({ type: 'create', note });
await this.savePendingChanges();
}
}
async flushChanges() {
const pending = await this.getPendingChanges();
for (const change of pending) {
try {
await api[change.type](change.note);
await this.removePending(change.id);
} catch (e) {
console.error('Sync failed:', e);
break;
}
}
}
private async saveLocally(note) {
const notes = await this.getLocalNotes();
notes.unshift(note);
await AsyncStorage.setItem('notes', JSON.stringify(notes));
}
}
Example 4: Kotlin โ Custom View with Canvas
Problem: Create a custom circular progress indicator in Android.
class CircularProgressView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private var progress = 0f
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
private val rect = RectF()
init {
paint.style = Paint.Style.STROKE
paint.strokeWidth = 20f
paint.strokeCap = Paint.Cap.ROUND
}
fun setProgress(value: Float) {
progress = value.coerceIn(0f, 100f)
invalidate() // Trigger redraw
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val padding = 20f
rect.set(padding, padding, width - padding, height - padding)
// Background circle
paint.color = Color.LTGRAY
canvas.drawArc(rect, 0f, 360f, false, paint)
// Progress arc
paint.color = Color.parseColor("#4CAF50")
val sweepAngle = (progress / 100f) * 360f
canvas.drawArc(rect, -90f, sweepAngle, false, paint)
// Center text
paint.style = Paint.Style.FILL
paint.textSize = 48f
paint.textAlign = Paint.Align.CENTER
paint.color = Color.BLACK
val text = "${progress.toInt()}%"
val y = height / 2f - (paint.descent() + paint.ascent()) / 2f
canvas.drawText(text, width / 2f, y, paint)
}
}
Example 5: App Deep Linking Configuration
Problem: Configure deep links so myapp://product/123 opens the product detail screen.
<!-- Android Manifest -->
<activity android:name=".ProductDetailActivity">
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="myapp" android:host="product" />
</intent-filter>
</activity>
// Handle deep link in Activity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val data: Uri? = intent?.data
if (data != null && data.scheme == "myapp") {
val segments = data.pathSegments
if (segments[0] == "product") {
val productId = segments[1]
loadProduct(productId)
}
}
}
Practice Problems
Problem 1: Implement Infinite Scroll
Create a React Native FlatList that loads more items when reaching the end.
function InfiniteScrollList() {
const [data, setData] = useState([]);
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);
const [hasMore, setHasMore] = useState(true);
const loadData = async (pageNum) => {
if (loading || !hasMore) return;
setLoading(true);
const response = await fetch(`/api/items?page=${pageNum}&limit=20`);
const newData = await response.json();
setData(prev => [...prev, ...newData.items]);
setHasMore(newData.hasMore);
setLoading(false);
};
return (
}
keyExtractor={item => item.id}
onEndReached={() => {
const nextPage = page + 1;
setPage(nextPage);
loadData(nextPage);
}}
onEndReachedThreshold={0.5}
ListFooterComponent={
loading ? : null
}
/>
);
}
Problem 2: Flutter Responsive Layout
Create a layout that adapts to different screen sizes (phone vs tablet).
class ResponsiveLayout extends StatelessWidget {
@override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
final isTablet = screenWidth > 600;
return Scaffold(
body: isTablet
? Row(
children: [
Expanded(flex: 2, child: Sidebar()),
Expanded(flex: 3, child: MainContent()),
],
)
: Column(
children: [
Expanded(child: MainContent()),
BottomNavBar(),
],
),
);
}
}
Problem 3: Implement Pull-to-Refresh
Add pull-to-refresh functionality to a React Native list.
function RefreshableList() {
const [refreshing, setRefreshing] = useState(false);
const [data, setData] = useState([]);
const onRefresh = async () => {
setRefreshing(true);
try {
const response = await fetch('/api/data');
const newData = await response.json();
setData(newData);
} finally {
setRefreshing(false);
}
};
return (
}
keyExtractor={item => item.id}
refreshControl={
}
/>
);
}
Problem 4: Kotlin โ ViewBinding Migration
Convert findViewById usage to ViewBinding in an Android Activity.
// Before (findViewById)
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val textView = findViewById(R.id.textView)
val button = findViewById(R.id.button)
button.setOnClickListener { textView.text = "Clicked!" }
}
}
// After (ViewBinding)
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.button.setOnClickListener {
binding.textView.text = "Clicked!"
}
}
}
Problem 5: Implement Biometric Authentication
Add fingerprint/face ID authentication to a React Native app.
import ReactNativeBiometrics from 'react-native-biometrics';
async function authenticateWithBiometrics() {
try {
const { available, biometryType } =
await ReactNativeBiometrics.isSensorAvailable();
if (!available) {
Alert.alert('Biometrics not available');
return;
}
const { success } = await ReactNativeBiometrics.simplePrompt({
promptMessage: 'Authenticate to continue',
});
if (success) {
console.log('Authentication successful');
// Proceed with app
}
} catch (error) {
console.error('Biometric error:', error);
}
}
Problem 6: Custom Transition Animation
Create a shared element transition between two screens in React Native.
import { createSharedElementStackNavigator } from 'react-navigation-shared-element';
const Stack = createSharedElementStackNavigator();
function App() {
return (
{
const { item } = route.params;
return [`item.${item.id}.image`];
}}
options={{
transitionSpec: {
open: { animation: 'timing', duration: 300 },
close: { animation: 'timing', duration: 300 },
},
}}
/>
);
}
// In both screens:
//
//
//
Problem 7: Implement Dark Mode
Add dark mode support to a React Native app with system preference detection.
import { useColorScheme } from 'react-native';
import { ThemeProvider } from './ThemeContext';
const lightTheme = {
background: '#FFFFFF',
text: '#1a1a1a',
card: '#F5F5F5',
primary: '#007AFF',
};
const darkTheme = {
background: '#1a1a1a',
text: '#FFFFFF',
card: '#2a2a2a',
primary: '#0A84FF',
};
function ThemeProvider({ children }) {
const systemScheme = useColorScheme();
const [isDark, setIsDark] = useState(systemScheme === 'dark');
const theme = isDark ? darkTheme : lightTheme;
const toggleTheme = () => setIsDark(prev => !prev);
return (
{children}
);
}
// Usage
function MyComponent() {
const { theme } = useContext(ThemeContext);
return (
Hello
);
}
Problem 8: Unit Test a React Native Component
Write tests for a simple component using Jest and React Testing Library.
import { render, fireEvent, screen } from '@testing-library/react-native';
import Counter from '../Counter';
describe('Counter Component', () => {
it('renders with initial count of 0', () => {
render( );
expect(screen.getByText('Count: 0')).toBeTruthy();
});
it('increments count when button pressed', () => {
render( );
fireEvent.press(screen.getByText('+'));
expect(screen.getByText('Count: 1')).toBeTruthy();
});
it('decrements count when minus pressed', () => {
render( );
fireEvent.press(screen.getByText('-'));
expect(screen.getByText('Count: -1')).toBeTruthy();
});
});
Problem 9: Flutter โ Pagination with Infinite Scroll
Implement an infinite scrolling list in Flutter with loading indicator.
class InfiniteScrollView extends StatefulWidget {
@override
_InfiniteScrollViewState createState() => _InfiniteScrollViewState();
}
class _InfiniteScrollViewState extends State {
final ScrollController _scrollController = ScrollController();
final List- _items = [];
int _page = 1;
bool _isLoading = false;
bool _hasMore = true;
@override
void initState() {
super.initState();
_loadMore();
_scrollController.addListener(() {
if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent - 200) {
_loadMore();
}
});
}
Future
_loadMore() async {
if (_isLoading || !_hasMore) return;
setState(() => _isLoading = true);
final newItems = await api.getItems(page: _page);
setState(() {
_items.addAll(newItems);
_page++;
_hasMore = newItems.length == 20;
_isLoading = false;
});
}
@override
Widget build(BuildContext context) {
return ListView.builder(
controller: _scrollController,
itemCount: _items.length + (_isLoading ? 1 : 0),
itemBuilder: (context, index) {
if (index == _items.length) {
return Center(child: CircularProgressIndicator());
}
return ListTile(title: Text(_items[index].title));
},
);
}
}
Problem 10: Implement App Analytics
Set up Firebase Analytics to track screen views and custom events.
// Analytics service
import analytics from '@react-native-firebase/analytics';
class AnalyticsService {
static async trackScreen(screenName) {
await analytics().logScreenView({
screen_name: screenName,
screen_class: screenName,
});
}
static async trackEvent(eventName, params = {}) {
await analytics().logEvent(eventName, params);
}
static async trackPurchase(amount, currency, itemId) {
await analytics().logPurchase({
value: amount,
currency: currency,
items: [{ item_id: itemId }],
});
}
static async trackSearch(searchTerm) {
await analytics().logSearch({ search_term: searchTerm });
}
}
// Usage in navigation
navigation.addListener('state', () => {
const currentRoute = navigation.getCurrentRoute();
AnalyticsService.trackScreen(currentRoute.name);
});