Choosing Your Approach
The most important decision in mobile development is not which language to use — it is whether to go native or cross-platform, and that answer depends on your product, team, and timeline.
Native development means writing separate codebases for iOS (Swift/SwiftUI) and Android (Kotlin/Jetpack Compose). You get the deepest platform integration, the best performance ceiling, and the smoothest access to new OS features — but you pay for it with two codebases to maintain and effectively two platform-specialist skill sets required.
Cross-platform frameworks let you write one codebase that compiles or runs on both platforms. The tradeoff is some overhead when you need deep platform-specific behaviour, but for the vast majority of apps — CRUD, e-commerce, content, social — the overhead is negligible and the productivity gain is significant.
If your team is small and your product needs to ship fast, cross-platform is almost always the right call. Native makes sense when you are building OS-level tooling, games, or apps with heavy platform-specific hardware integration.
Architecture Before Code
Regardless of the platform choice, bad architecture will slow you down more than any framework decision. The most common mistake is mixing business logic with UI code. This makes the app hard to test, hard to maintain, and hard to hand off.
A clean architecture separates concerns into layers:
- Presentation — screens, widgets, and UI state management
- Domain — business logic, use cases, entities
- Data — API clients, local storage, repositories
Each layer depends only on the layer below it. The domain layer has no knowledge of the UI or the network — it is pure business logic. This makes it trivially testable and reusable across platforms.
State Management
Every mobile app has state. The question is how you manage it.
For small apps, simple local state is fine. As your app grows, you need a more deliberate approach. The key principles regardless of which pattern you choose:
- State should be single-source of truth — never duplicate state across the app
- State transitions should be explicit and predictable — prefer events over direct mutations
- UI should be a pure function of state — given the same state, the UI is always the same
Whatever pattern you adopt — BLoC, Redux, MVVM, or a simpler reactive approach — consistency matters more than the specific choice.
API Integration
Almost every app talks to a backend. A few fundamentals that hold across all platforms:
- Abstract your HTTP layer — do not call the network directly from your screens. Route through a repository or service layer.
- Handle loading, success, and error states explicitly — every async operation has three states; build your UI to account for all three.
- Cache strategically — decide which data needs to be fresh on every open and which can be served from local storage. Most data can tolerate some staleness.
- Offline-first where it matters — if your users operate in low-connectivity environments, design for it from the start, not as an afterthought.
Local Storage
Most apps need to persist some data locally. Common options:
- Key-value storage — for simple preferences and settings. Available natively on every platform.
- Relational databases (SQLite) — for structured data with relationships and queries.
- Document stores — for flexible, schema-less data.
- Secure storage — for credentials and tokens. Always use platform-provided secure storage (Keychain on iOS, Keystore on Android) rather than plain storage.
Encrypt anything sensitive. Never store tokens or passwords in plain key-value stores.
Performance
Most performance problems in mobile apps come from a small set of causes:
Rendering bottlenecks — doing expensive work on the main thread (UI thread). Keep your UI updates lightweight. Move computation, I/O, and network calls off the main thread.
Memory pressure — loading large images at full resolution and keeping unused data in memory. Use lazy loading, dispose of resources when they leave the screen, and profile with the platform's memory tools.
Startup time — doing too much work on app launch. Defer non-essential initialisation. Profile your cold start time and set a target — anything under 2 seconds feels fast.
Over-fetching — requesting more data than you display. Design your API payloads to match your UI needs, not your database schema.
Submitting to the App Stores
Both stores have a review process. Plan for it.
Apple App Store:
- Enrol in the Apple Developer Programme (annual fee)
- Create an App Store Connect record with metadata — name, description, screenshots, keywords
- Sign your build with a distribution certificate and provisioning profile
- Submit for review — typically 24–48 hours, though complex apps or first submissions can take longer
- Respond to any review rejections with specifics — Apple provides reasons
Google Play Store:
- Create a Google Play Console account (one-time fee)
- Create an app listing with metadata and screenshots
- Upload a signed release bundle (AAB format preferred over APK)
- Choose a release track — internal testing → closed testing → production
- Review is typically faster than Apple, often within hours for established accounts
Both stores require:
- Privacy policy (mandatory for apps that collect any data)
- Accurate content rating
- Screenshots in required sizes for all device categories you claim support for
Testing
Testing mobile apps has three layers:
Unit tests — test business logic and individual functions in isolation. Fast, cheap, should be the majority of your test suite.
Integration tests — test how components work together, particularly repository and API layers. Slower but catches integration issues early.
End-to-end / UI tests — automated tests that simulate user interactions. Valuable for regression testing critical flows (signup, checkout, onboarding). Run on real devices or emulators in CI.
Beyond automated tests, always test on real devices before submitting. Simulators do not capture all real-world performance characteristics, particularly around battery, memory pressure, and network conditions.
Shipping Is the Goal
The best mobile app architecture is the one that ships. It is better to launch a well-structured app on one platform than to over-engineer a cross-platform solution that never reaches users.
Start with the core user journey. Instrument it with analytics. Launch. Iterate based on real usage data. Everything else is secondary.
Share