📚 BusinessExplain 🏠 Home 📊 Analytics
← Back to Skills

Mobile Mastery

💻 Coding & Engineering 10 min read v1.0.0

Mobile Mastery

Skill ID: coding/mobile-mastery
Domain: Mobile Development (iOS, Android, Cross-Platform)

Comprehensive mobile development covering Flutter, React Native, mobile security, cross-platform patterns, and build/deploy pipelines.


1. Flutter

Widget Tree Architecture

graph TD
    A[MaterialApp] --> B[Scaffold]
    B --> C[AppBar]
    B --> D[Body: Column]
    B --> E[FloatingActionButton]
    D --> F[Consumer<UserModel>]
    D --> G[Expanded]
    F --> H[ListView.builder]
    G --> I[Text]
    H --> J[ListTile]
    C --> L[IconButton]
    style A fill:#027DFD,color:#fff
    style F fill:#FF9800,color:#000

Widget categories: Structural (Scaffold, AppBar), Layout (Column, Row, Stack, Expanded), Content (Text, Image), Interaction (GestureDetector, TextField), Animation (AnimatedContainer, Hero), Painting (DecoratedBox, Transform).

State Management: Provider & Riverpod

// Provider (classic)
class CartModel extends ChangeNotifier {
  final List _items = [];
  List get items => UnmodifiableListView(_items);
  int get totalPrice => _items.fold(0, (sum, i) => sum + i.price);
  void add(Item item) { _items.add(item); notifyListeners(); }
  void clear() { _items.clear(); notifyListeners(); }
}

void main() => runApp(
ChangeNotifierProvider(create: (_) => CartModel(), child: const MyApp()),
);

class CartTotal extends StatelessWidget {
@override Widget build(BuildContext context) => Consumer(
builder: (_, cart, __) => Text('Total: \$${cart.totalPrice}'),
);
}

// Riverpod (modern, compile-safe)
final cartProvider = NotifierProvider>(CartNotifier.new);
final totalPriceProvider = Provider((ref) =>
ref.watch(cartProvider).fold(0, (sum, i) => sum + i.price));

class CartNotifier extends Notifier> {
@override List build() => [];
void add(Item item) => state = [...state, item];
void clear() => state = [];
}

class CartTotal extends ConsumerWidget {
@override Widget build(_, WidgetRef ref) =>
Text('Total: \$${ref.watch(totalPriceProvider)}');
}

Platform Channels

// Dart side
class NativeApi {
  static const _channel = MethodChannel('com.app/native_api');
  static Future getDeviceId() async {
    try { return await _channel.invokeMethod('getDeviceId'); }
    on PlatformException catch (e) { debugPrint('Error: ${e.message}'); return null; }
  }
  static Stream observeNetwork() =>
    const EventChannel('com.app/network').receiveBroadcastStream().cast();
}
// Android (Kotlin) — MethodChannel handler
class MainActivity : FlutterActivity() {
  override fun configureFlutterEngine(engine: FlutterEngine) {
    super.configureFlutterEngine(engine)
    MethodChannel(engine.dartExecutor.binaryMessenger, "com.app/native_api")
      .setMethodCallHandler { call, result ->
        when (call.method) {
          "getDeviceId" -> result.success(
            Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID))
          else -> result.notImplemented()
        }
      }
  }
}
// iOS (Swift) — MethodChannel handler
@main class AppDelegate: FlutterAppDelegate {
  override func application(_ app: UIApplication,
      didFinishLaunchingWithOptions opts: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    let ctrl = window?.rootViewController as? FlutterViewController
    FlutterMethodChannel(name: "com.app/native_api", binaryMessenger: ctrl!.binaryMessenger)
      .setMethodCallHandler { call, result in
        if call.method == "getDeviceId" { result(UIDevice.current.identifierForVendor?.uuidString ?? "") }
        else { result(FlutterMethodNotImplemented) }
      }
    return super.application(app, didFinishLaunchingWithOptions: opts)
  }
}

FFI for performance-critical code:

import 'dart:ffi';
final DynamicLibrary _lib = DynamicLibrary.open('libprocessing.so');
final int Function(Pointer, int) _process =
    _lib.lookupFunction, Int32)>,
        int Function(Pointer, int)>('process_image');

2. React Native

Hooks Pattern

import {useState, useCallback, useEffect} from 'react';
import TouchID from 'react-native-touch-id';
import * as Keychain from 'react-native-keychain';

export function useBiometricAuth() {
const [state, setState] = useState({available: false, loading: false, error: null as string|null});
useEffect(() => {
TouchID.isSupported()
.then(() => setState(s => ({...s, available: true})))
.catch(() => setState(s => ({...s, available: false})));
}, []);
const authenticate = useCallback(async () => {
setState(s => ({...s, loading: true, error: null}));
try {
await TouchID.authenticate('Verify identity');
const creds = await Keychain.getGenericPassword({service: 'auth_token'});
setState(s => ({...s, loading: false}));
return creds;
} catch (err: any) {
setState(s => ({...s, loading: false, error: err.message}));
return null;
}
}, []);
return {...state, authenticate};
}

import {NavigationContainer} from '@react-navigation/native';
import {createNativeStackNavigator} from '@react-navigation/native-stack';

type RootStackParamList = { Home: undefined; Profile: {userId: string} };
const Stack = createNativeStackNavigator();

export default function App() {
const linking = {
prefixes: ['myapp://', 'https://myapp.com'],
config: { screens: { Home: 'home', Profile: 'profile/:userId' } },
};
return (






);
}

Native Modules & Bridge Architecture

graph LR
    subgraph "JavaScript Realm"
        A[React Component] --> B[JS Bridge Module]
    end
    subgraph "Bridge — Async Message Queue"
        C[JSON-serialized Messages]
    end
    subgraph "Native Realm"
        D[iOS: Swift / ObjC]
        E[Android: Kotlin / Java]
    end
    B -->|serialize + queue| C
    C -->|dispatch| D
    C -->|dispatch| E
    D -->|callback/promise| C
    E -->|callback/promise| C
    C -->|resolve| B
    style C fill:#FFD600,color:#000
    style A fill:#61DAFB,color:#000
    style D fill:#FC3D39,color:#fff
    style E fill:#3DDC84,color:#000
// Android native module
class NativePaymentsModule(ctx: ReactApplicationContext) : ReactContextBaseJavaModule(ctx) {
  override fun getName() = "NativePayments"
  @ReactMethod
  fun processPayment(amount: Double, currency: String, promise: Promise) {
    try { promise.resolve(PaymentGateway.charge(amount, currency).toJson()) }
    catch (e: Exception) { promise.reject("PAYMENT_FAILED", e.message, e) }
  }
}

TurboModule (New Architecture):

import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport';
import {TurboModuleRegistry} from 'react-native';
export interface Spec extends TurboModule {
  processPayment(amount: number, currency: string): Promise;
}
export default TurboModuleRegistry.getEnforcing('NativePayments');

Expo vs Bare Workflow

| Feature | Expo Managed | Expo Bare | Bare RN |
|---|---|---|---|
| Native code editing | ❌ | ✅ | ✅ |
| Custom native modules | Config plugins | ✅ | ✅ |
| OTA updates (EAS) | ✅ | ✅ | Manual |
| Cloud build (EAS) | ✅ | ✅ | Manual |
| App size | Larger | Optimized | Optimized |


3. Mobile Security

Secure Storage

// Flutter — flutter_secure_storage
class SecureStore {
  static const _storage = FlutterSecureStorage(
    aOptions: AndroidOptions(encryptedSharedPreferences: true),
    iOptions: IOSOptions(accessibility: KeychainAccessible.firstUnlockThisDeviceOnly),
  );
  static Future save(String k, String v) => _storage.write(key: k, value: v);
  static Future read(String k) => _storage.read(key: k);
  static Future delete(String k) => _storage.delete(key: k);
}
// React Native — react-native-keychain
export async function storeToken(token: string) {
  await Keychain.setGenericPassword('auth', token, {
    service: 'com.app.auth',
    accessControl: Keychain.ACCESS_CONTROL.BIOMETRY_ANY,
    accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
  });
}
export async function retrieveToken() {
  const c = await Keychain.getGenericPassword({service: 'com.app.auth'});
  return c?.password ?? null;
}

Certificate Pinning

// Flutter — dio + custom validation
final dio = Dio()
  ..httpClientAdapter = IOHttpClientAdapter(
    createHttpClient: () => HttpClient(context: SecurityContext.defaultContext),
    validateCertificate: (cert, host, port) {
      if (cert == null) return false;
      final hash = sha256.convert(cert.der).toString();
      return hash == 'expectedSha256Fingerprint';
    },
  );
// Android — OkHttp CertificatePinner
val pinner = CertificatePinner.Builder()
  .add("api.myapp.com", "sha256/ABC123...")
  .add("api.myapp.com", "sha256/DEF456...")  // backup pin
  .build()
val client = OkHttpClient.Builder().certificatePinner(pinner).build()

Biometric Authentication

// Flutter — local_auth
class BiometricAuth {
  static final _auth = LocalAuthentication();
  static Future isAvailable() async =>
    await _auth.isDeviceSupported() && await _auth.canCheckBiometrics;
  static Future authenticate({String reason = 'Verify identity'}) =>
    _auth.authenticate(localizedReason: reason,
      options: const AuthenticationOptions(stickyAuth: true, biometricOnly: true));
  static Future> enrolledTypes() =>
    _auth.getAvailableBiometrics();
}
class DeepLinkHandler {
  static const _allowedHosts = {'myapp.com', 'app.myapp.com'};
  static const _allowedSchemes = {'myapp', 'https'};
  static Uri? validate(String url) {
    final uri = Uri.tryParse(url);
    if (uri == null || !_allowedSchemes.contains(uri.scheme) ||
        !_allowedHosts.contains(uri.host)) return null;
    if (!RegExp(r'^/(product/\d+|profile/\w+|reset/[a-f0-9]{32})$')
        .hasMatch(uri.path)) return null;
    final allowedParams = {'ref', 'utm_source', 'token'};
    if (uri.queryParameters.keys.any((k) => !allowedParams.contains(k))) return null;
    return uri;
  }
}
// React Native deep link validation
const ALLOWED = [/^\/product\/\d+$/, /^\/profile\/[a-zA-Z0-9_-]+$/];
export function validateDeepLink(url: string): URL | null {
  try {
    const p = new URL(url);
    if (!['myapp','https'].includes(p.protocol.replace(':',''))) return null;
    if (!['myapp.com','app.myapp.com'].includes(p.hostname)) return null;
    return ALLOWED.some(r => r.test(p.pathname)) ? p : null;
  } catch { return null; }
}

OWASP Mobile Top 10 (2024)

| # | Risk | Mitigation |
|---|---|---|
| M1 | Improper Platform Usage | Keychain/Keystore; validate all intent/URL inputs |
| M2 | Insecure Data Storage | Encrypt at rest; never log PII; wipe temp files |
| M3 | Insecure Communication | TLS 1.3; certificate pinning; no cleartext |
| M4 | Insecure Authentication | Biometric + refresh-token rotation; no stored passwords |
| M5 | Insufficient Cryptography | AES-256-GCM; ECDSA-P256; never MD5/SHA1 |
| M6 | Insecure Authorization | Server-side authz; role-based; no client-only checks |
| M7 | Client Code Quality | ProGuard/R8 obfuscation; static analysis in CI |
| M8 | Code Tampering | Bundle integrity checks; root/jailbreak detection |
| M9 | Reverse Engineering | Obfuscation; symbol stripping; anti-debug flags |
| M10 | Extraneous Functionality | Remove debug logs; no hardcoded keys; strip backdoors |


4. Cross-Platform Patterns

Shared Logic, Platform-Specific UI

lib/
├── core/           # Shared business logic (models, repos, usecases)
├── platform/       # Platform abstraction (interface + mobile/web impls)
├── features/       # Shared VM + platform-specific screens per feature
└── di/             # Dependency injection (get_it / injectable)
abstract class PlatformAdapter {
  String get platformName;
  bool get hasBiometrics;
  Future openAppSettings();
}

class MobileAdapter implements PlatformAdapter {
@override String get platformName => Platform.isIOS ? 'iOS' : 'Android';
@override bool get hasBiometrics => true;
@override Future openAppSettings() => OpenAppSettings.openAppSettings();
}

Responsive Layout

class ResponsiveBuilder extends StatelessWidget {
  final Widget mobile; final Widget? tablet; final Widget? desktop;
  const ResponsiveBuilder({super.key, required this.mobile, this.tablet, this.desktop});

@override Widget build(BuildContext context) => LayoutBuilder(builder: (_, c) {
if (c.maxWidth >= 1024 && desktop != null) return desktop!;
if (c.maxWidth >= 600 && tablet != null) return tablet!;
return mobile;
});
}

// React Native responsive hook
export function useBreakpoint(): 'mobile' | 'tablet' | 'desktop' {
  const {width} = useWindowDimensions();
  return width >= 1024 ? 'desktop' : width >= 600 ? 'tablet' : 'mobile';
}

Accessibility

// Flutter — semantic labeling
Semantics(label: 'Add to cart', button: true,
  hint: 'Adds product to your shopping cart',
  child: IconButton(icon: const Icon(Icons.add_shopping_cart), onPressed: _addToCart))
// React Native accessibility

  Add to Cart


5. Build & Deploy

Deployment Pipeline

graph LR
    A[Developer Push] --> B[CI Trigger]
    B --> C[Lint + Unit Tests]
    C --> D[Build iOS .ipa]
    C --> E[Build Android .aab]
    D --> F[iOS Test Suite]
    E --> G[Android Test Suite]
    F --> H[Code Signing]
    G --> I[Code Signing + Align]
    H --> J[App Store Connect]
    I --> K[Play Console]
    J --> L[TestFlight]
    K --> M[Internal Track]
    L --> N[Phased Release]
    M --> N
    style A fill:#4CAF50,color:#fff
    style B fill:#2196F3,color:#fff
    style H fill:#FF9800,color:#000
    style I fill:#FF9800,color:#000
    style N fill:#9C27B0,color:#fff

Fastlane Automation

default_platform(:ios)

platform :ios do
lane :release do
ensure_git_status_clean
increment_build_number(build_number: ENV["CI_BUILD_NUMBER"])
run_tests(scheme: "MyApp", devices: ["iPhone 15"])
build_app(workspace: "MyApp.xcworkspace", scheme: "MyApp",
export_method: "app-store",
export_options: { provisioningProfiles: { "com.myapp.prod" => "MyApp_AppStore" } })
upload_to_app_store(skip_metadata: true, distribute_external: true, groups: ["beta"])
end
end

platform :android do
lane :release do
ensure_git_status_clean
gradle(task: "testProdRelease")
gradle(task: "bundleProdRelease", properties: {
"android.injected.signing.store.file" => ENV["KEYSTORE_PATH"],
"android.injected.signing.store.password" => ENV["KEYSTORE_PASSWORD"],
"android.injected.signing.key.alias" => ENV["KEY_ALIAS"],
"android.injected.signing.key.password" => ENV["KEY_PASSWORD"] })
upload_to_play_store(track: "internal",
aab: "android/app/build/outputs/bundle/prodRelease/app-prod-release.aab")
end
end

CI/CD (GitHub Actions)

name: Mobile CI/CD
on:
  push: { branches: [main] }
  pull_request:

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with: { flutter-version: '3.22.0', channel: 'stable' }
- run: flutter pub get
- run: dart analyze --fatal-infos
- run: flutter test --coverage
- uses: codecov/codecov-action@v3

deploy-ios:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- run: brew install fastlane && bundle install
- env:
MATCH_PASSWORD: "${{ secrets.MATCH_PASSWORD }}"
APP_STORE_CONNECT_API_KEY_ID: "${{ secrets.ASC_KEY_ID }}"
APP_STORE_CONNECT_ISSUER_ID: "${{ secrets.ASC_ISSUER_ID }}"
APP_STORE_CONNECT_API_KEY: "${{ secrets.ASC_PRIVATE_KEY }}"
CI_BUILD_NUMBER: "${{ github.run_number }}"
run: bundle exec fastlane ios release

deploy-android:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- env:
KEYSTORE_PATH: "${{ secrets.KEYSTORE_PATH }}"
KEYSTORE_PASSWORD: "${{ secrets.KEYSTORE_PASSWORD }}"
KEY_ALIAS: "${{ secrets.KEY_ALIAS }}"
KEY_PASSWORD: "${{ secrets.KEY_PASSWORD }}"
CI_BUILD_NUMBER: "${{ github.run_number }}"
run: bundle exec fastlane android release

Code Signing

# iOS — fastlane match (shared signing via encrypted git repo)
fastlane match appstore -y

CI uses App Store Connect API key — no 2FA bottleneck

Android — generate upload keystore

keytool -genkeypair -v -keystore upload.keystore -alias upload \ -keyalg RSA -keysize 2048 -validity 10000 -storetype PKCS12

OTA Updates

// Flutter — Shorebird (patches Dart heap at runtime)
// CLI: shorebird patch --release-version 1.0.0
import 'package:shorebird_code_push/shorebird_code_push.dart';

Future checkPatch() async {
final status = await ShorebirdUpdater().checkForUpdate();
if (status == UpdateStatus.restartRequired) {
// Prompt user to restart the app
}
}

// React Native — EAS Update
// Push: eas update --branch production --message "Fix login crash"
import * as Updates from 'expo-updates';

export function useOTAUpdate() {
useEffect(() => { (async () => {
const {isAvailable} = await Updates.checkForUpdateAsync();
if (isAvailable) {
await Updates.fetchUpdateAsync();
Alert.alert('Update Ready', 'Restart to apply?', [
{text: 'Later', style: 'cancel'},
{text: 'Restart', onPress: () => Updates.reloadAsync()},
]);
}
})(); }, []);
}


Quick Decision Matrix

| Decision | Flutter | React Native |
|---|---|---|
| UI fidelity | Pixel-perfect custom | Native feel by default |
| Performance | Higher (Skia/Impeller) | Good (Hermes + Fabric) |
| Team skill | Dart knowledge | JS/TS ecosystem |
| Hot reload | ✅ Stateful | ✅ Fast refresh |
| Native modules | Channels / FFI | Bridge / TurboModules |
| Desktop/web | ✅ Official | Community (rn-web) |
| OTA updates | Shorebird | EAS Update |

💬 Ask about Mobile Mastery