Mobile Development · Guide

Choosing a Platform

Native or cross-platform, Kotlin or Swift, and the lifecycle every mobile app has to survive.

— min read Mobile Development

The Decision That Shapes Everything Else

Mobile is not desktop with a smaller window. The operating system can suspend or kill your process at any moment, memory is tight, the network comes and goes, and the battery is a budget you are spending.

The first question is native or cross-platform, and it is more organisational than technical. Native means two codebases, two languages, two teams — and full access to every platform feature the day it ships. Cross-platform means one codebase and a bridge that is usually close enough.

ApproachBuys youCosts you
Native (Kotlin + Swift)Best performance, every API on day one, platform-correct feelTwo codebases, two skill sets
React Native / FlutterOne codebase, shared logic, fast iterationA bridge to maintain, lag behind new OS features
Web wrapped in a shellCheapest by farFeels like a website, and users notice

The honest heuristic: if the app is the product and its feel is the differentiator, go native. If it is a client onto a service you already run, cross-platform will save you a year.

Kotlin & Android

Kotlin is the language Android ships with, and it fixed the parts of Java that hurt most in app code: null safety in the type system, data classes instead of fifty lines of boilerplate, extension functions, and coroutines for asynchronous work that reads like sequential code.

data class User(val id: String, val name: String, val email: String?)

// The type says whether null is possible, and the compiler enforces it
fun greeting(user: User): String =
    user.email?.let { "Signed in as $it" } ?: "Signed in as ${user.name}"

// Coroutines: asynchronous, but written top to bottom
suspend fun loadProfile(id: String): User = withContext(Dispatchers.IO) {
    api.fetchUser(id)
}

Android itself is the harder half. An Activity is a screen, a Fragment is a reusable piece of one, an Intent is how you ask the system or another app to do something, and the manifest declares what your app is and what it may touch. Modern apps lean on Jetpack — the libraries that make lifecycle, navigation, storage and background work survivable.

Android runs on thousands of device models across a decade of OS versions. Test the low end: a two-year-old mid-range phone tells you more about your app than a flagship ever will.

Swift & iOS

Swift is fast, statically typed and expressive, with optionals doing the same job Kotlin's nullable types do. The ecosystem is smaller and more consistent — a handful of device sizes, an OS version curve where most users update within months, and one vendor deciding how everything works.

struct User: Codable {
    let id: String
    let name: String
    let email: String?
}

// async/await, the same shape as Kotlin's coroutines
func loadProfile(id: String) async throws -> User {
    try await api.fetchUser(id: id)
}

A UIViewController is roughly an Activity, Combine or async sequences handle streams of values, and Swift Package Manager handles dependencies. The platform is opinionated: doing things the Apple way is easy and everything else is uphill.

The two ecosystems have converged hard. Kotlin coroutines and Swift concurrency, Compose and SwiftUI, MVVM on both sides — learning one now transfers most of the way to the other.

The App Lifecycle

This is the part with no desktop equivalent, and the part that produces the bugs nobody can reproduce. Your app does not own its process. The system starts it, backgrounds it, freezes it, and — under memory pressure — kills it without warning, then expects it to reappear exactly where the user left off.

TransitionWhat you must do
LaunchedRestore saved state, not just show a fresh screen
BackgroundedPersist anything unsaved, release expensive resources
ResumedRefresh anything that may be stale, reacquire the camera or location
Killed while backgroundedNothing — you are not running. This is why you saved on the way out
Rotated or resizedOn Android the screen is rebuilt; survive it, do not reload
The classic Android bug: state held in the Activity, lost on rotation, and the screen reloads from the network every time the user turns the phone. State belongs in something lifecycle-aware — a ViewModel — not in the screen.

Test it deliberately. Enable "don't keep activities" on Android, or background the app and let iOS reclaim it, then reopen. If the user loses their place, their draft, or their scroll position, the lifecycle is not handled.

Interview Questions

When would you choose native over cross-platform?

When the app is the product and its feel is the differentiator, when it needs new OS features on release day, or when performance-critical work like camera, graphics or audio is central. Otherwise a shared codebase usually wins on cost.

Why does Kotlin put nullability in the type system?

It moves an entire class of runtime crashes to compile time. The type states whether a value may be absent, and the compiler will not let you dereference it without handling that case.

What is the Android app lifecycle problem?

The system can stop and kill your process at any point and expects a seamless restore. State kept in the screen is lost on rotation or process death, so it belongs in a lifecycle-aware holder that outlives the view.

How do coroutines and async/await compare?

They solve the same problem the same way — suspend at an await point, free the thread, resume later — so asynchronous code reads sequentially instead of nesting callbacks.

Why is Android testing harder than iOS testing?

Fragmentation. Thousands of device models, screen sizes, OEM skins and OS versions still in use, versus a handful of Apple devices on a version curve where most users upgrade quickly.

How do you verify lifecycle handling?

Force the failure: turn on "don't keep activities", rotate the device, or background the app until the OS reclaims it. If the user loses their place or their unsaved input, it is not handled.

Quick Quiz

1. State that must survive rotation belongs in…
2. Kotlin null safety mainly prevents…
3. The strongest case for going native is…
4. A backgrounded app should…
5. Coroutines and Swift async/await both…