Skip to main content
Product engineering & technology · 01

Mobile app development

We build native Android and iOS applications that are still maintainable in their second year — real architecture, instrumentation shipped with the features that emit it, and store review treated as an engineering problem. Submission readiness is designed in from the first sprint, not assembled the week before.

  • Kotlin · Jetpack Compose
  • Swift · SwiftUI
  • Firebase
  • Play Console · App Store Connect

BUILD

Product engineering & technology

The applications, interfaces, services and infrastructure your business actually runs on — mobile, web, backend, cloud and the automation around them, built by the same people who will later measure and advertise them.

What this work is

Mobile app development here means native Android and iOS product engineering: the application, the data layer beneath it, the release process around it and the measurement inside it. We write Kotlin with Jetpack Compose and Swift with SwiftUI, against an architecture that can be reviewed by someone who did not write it. The aim is an app whose second year costs less than its first.

Instrumentation is not a phase that follows the build. Analytics events, conversion signals and crash reporting are implemented alongside the features that emit them and verified in a debug build before submission, because instrumentation added afterwards only ever describes a product that has already changed. The same discipline applies to store review: permissions, data safety declarations and privacy manifests are decided while the feature is being written, when changing them is still cheap.

We are equally willing to argue against building an app at all. A responsive web application is often the better commercial decision, and a cross-platform build is sometimes the honest answer for a product whose value sits in content rather than in device capability. When native is the right call, it is usually because of background work, hardware access, offline behaviour or the store distribution itself. We would rather say that in the first conversation.

Why people call about this

Almost nobody asks for an architecture review. The request arrives as a symptom instead, and the same four causes sit underneath the symptoms.

Every release takes longer than the last
The first version shipped quickly because everything lived close together. Then the product grew: view models call the network directly, business rules are duplicated across screens, and one change to a data model ripples through screens that should never have known about it. Nobody refuses the work; it simply takes longer each time, and the estimate for anything new keeps drifting upward.
You cannot say what users actually do
Installs and daily actives are visible, and nothing else is. There is no event for the moment a user reaches value, no parameter carrying what a purchase was worth, and no way to separate a cohort that arrived from search from one that arrived from a campaign. Every product argument ends in opinion, and every acquisition decision is made on install cost alone.
Store review has become a lottery
Submissions come back for reasons that read as arbitrary until you look underneath: a permission requested by an SDK nobody audited, a data safety form that no longer matches what the build collects, a missing privacy manifest, a login screen with no account deletion route. Each rejection costs a review cycle, and release dates start being set by the queue rather than by you.
The app fails the moment connectivity does
Screens that assume a fast, permanent connection behave badly on a train, in a basement or on a congested cell. Requests hang without timeouts, a half-finished write is lost on backgrounding, retries fire in a loop and drain the battery, and the user sees a spinner with no way out. The reviews say the app is slow; the problem is that no offline state was ever designed.
What we deliver

What we build

Engagements differ in scope, but the pieces below are what a native mobile build is actually made of.

01

Native Android engineering

Kotlin throughout, Jetpack Compose for interface, and a layered architecture with typed boundaries between UI, domain and data. State is held in one place per screen, coroutines and Flow handle concurrency, and the module structure is chosen so that a feature can be built and tested without the rest of the app compiling around it.

  • Kotlin with Jetpack Compose and Material 3 components
  • Coroutines and Flow for concurrency and state streams
  • Room for local persistence, with migrations written and tested
  • Hilt for dependency injection across feature modules
  • WorkManager for deferred, constrained and retried background work
  • Gradle version catalogues and reproducible release builds
02

Native iOS engineering

Swift and SwiftUI, with UIKit where a control genuinely needs it. Swift Concurrency replaces callback chains, persistence uses SwiftData or Core Data depending on the deployment target, and navigation is modelled as state rather than as a stack of side effects. Accessibility, dynamic type and dark appearance are handled as the screens are built.

  • Swift with SwiftUI, and UIKit interop where it is warranted
  • Swift Concurrency: async/await, actors, structured cancellation
  • SwiftData or Core Data, with a migration path decided up front
  • Keychain for credentials, never UserDefaults
  • BGTaskScheduler and background modes justified per capability
  • Dynamic type, VoiceOver labels and contrast checked per screen
03

Instrumentation shipped with the feature

Every feature lands with its events already implemented, named against an agreed model and carrying the parameters that make them useful later. Crash and non-fatal reporting is enabled from the first internal build, with symbol files uploaded automatically. Remote Config gates anything risky, so a bad feature can be switched off without a store submission.

  • An event and parameter model written before implementation
  • Google Analytics for Firebase, verified in DebugView per event
  • Crashlytics with mapping files and dSYMs uploaded by the build pipeline
  • Remote Config flags for risky, seasonal or region-specific behaviour
  • Conversion events exposed for campaign use, carrying value where it exists
  • A written map of every event, where it fires and who reads it
04

Offline behaviour and background work

We decide early what the app must still do with no network, then build for that instead of retrofitting it. Reads come from a local store, writes are queued and reconciled, and background work is scheduled through the platform APIs rather than through timers the operating system will kill. Failure states are designed screens, not spinners.

  • Local-first reads with an explicit cache and freshness policy
  • Queued mutations with idempotency keys and stated conflict rules
  • WorkManager and BGTaskScheduler for constrained background work
  • Firebase Cloud Messaging for push, including data-only messages
  • Timeouts, bounded retries and backoff on every network call
  • Empty, loading, stale and failed states designed per screen
05

Subscriptions, purchases and entitlement

Billing is the part that quietly loses money when it is wrong. Purchases are validated on the server rather than trusted from the client, and entitlement is stored where your backend can read it. Renewal, cancellation, refund and grace-period notifications are consumed and acted on, and restore works on a reinstalled device.

  • Google Play Billing and StoreKit 2 integration
  • Server-side receipt and transaction verification
  • Real-time developer notifications and App Store Server Notifications
  • Entitlement modelled in your own backend, not only on the device
  • Restore purchases, upgrades, downgrades and grace periods
  • Price and offer changes handled without forcing an app update
06

Release engineering and store readiness

Signing, build variants and continuous integration are set up once, in your accounts, so any release can be reproduced. Store submission is prepared as a checklist rather than a scramble: declarations that match the build, listing assets at current sizes, and an internal or TestFlight track that real people use before the public one.

  • Play App Signing and Apple signing configured under your ownership
  • Automated builds for internal, beta and production tracks
  • Data safety form and privacy manifest kept in step with the code
  • R8 and ProGuard rules verified against a real release build
  • Staged rollout with halt criteria and a rollback plan
  • Deep links and universal links verified from a cold start
Technical approach

How the work runs

The sequence below is what a build looks like from the inside. It is deliberately front-loaded, because the decisions that are expensive to reverse all sit early.

  1. 01

    Settle the platform question honestly

    Before scoping a build we test whether an app is the right vehicle at all, and if it is, whether it needs to be native on both platforms at once. Distribution, hardware access, background behaviour and the shape of your audience decide that, not preference. It is cheaper to have this argument in week one than after a launch.

  2. 02

    Architecture and the event model, before screens

    Module boundaries, the data layer and the API contracts are agreed before interface work starts, and the analytics model is written at the same time. Naming an event while the feature is still an idea is what makes the data usable a year later. Both artefacts are short documents you keep, not diagrams that live only in our heads.

  3. 03

    Build features with their instrumentation attached

    Features ship in vertical slices: interface, data access, error states, events and crash reporting together. Each slice goes to an internal track or TestFlight build so it is exercised on real devices rather than on an emulator. Events are checked in a debug view before a slice is called done, and the event map is updated in the same change.

  4. 04

    Harden the build for review

    Every permission is traced to a feature, every SDK is checked for what it collects, and the store declarations are filled in from the build rather than from memory. We rehearse the review path: sign-in credentials for the reviewer, account deletion, restricted permission justifications, and the screens a policy reviewer opens first.

  5. 05

    Release in stages and watch it

    Release goes out on a staged rollout where the platform allows it, with crash-free sessions and the key funnel events watched against the previous version. Halt criteria are agreed in writing beforehand, so stopping a rollout is a decision someone already made calmly rather than an argument at midnight.

Release readiness checklist

What we expect to be true before a build is submitted. Each line is something a submission can be rejected for when it is assumed rather than checked.

  • Signing set up under your ownership: upload key, Play App Signing enrolment and Apple certificates, with a written recovery path.
  • Play Console data safety form completed against the SDKs actually in the build, not the ones that were meant to be removed.
  • iOS privacy manifest and required-reason API declarations present for the app and for every third-party SDK that needs one.
  • Every runtime permission traced to a feature in the shipping build, with in-app rationale copy and a store justification written.
  • Crash and non-fatal reporting live in the release configuration, with mapping files and dSYMs uploaded automatically.
  • R8 and ProGuard rules exercised on a real release build: serialisation, reflection and third-party SDK paths tested, not assumed.
  • Minimum SDK and iOS deployment target chosen from install-base data, with the lowest supported version actually tested.
  • Deep links and universal links verified end to end from a cold start, including the domain association files on your servers.
  • Store listing assets complete at current sizes: icon, screenshots per device class, feature graphic, description and categories.
  • In-app account deletion and a web deletion route in place, since both stores ask about them.
  • Internal testing track or TestFlight build signed off by someone outside the people who wrote it.
  • Staged rollout percentage, halt criteria and rollback plan agreed in writing before the build is submitted.

Android, iOS and what is shared

Two native apps are not two separate projects. The interface layer is genuinely per-platform; most of the thinking is not.

A

Android

  • Kotlin and Jetpack Compose interface layer
  • Play Billing and Play Integrity
  • WorkManager for deferred and constrained work
  • Play Console tracks, data safety, staged rollout
  • R8 shrinking with mapping file upload
  • Device, OEM and API-level fragmentation testing
B

iOS

  • Swift and SwiftUI interface layer
  • StoreKit 2 transactions and entitlements
  • BGTaskScheduler and declared background modes
  • App Store Connect, TestFlight, privacy manifest
  • App thinning with dSYM upload
  • Review guideline checks: sign-in, deletion, purchases
C

Shared

  • Product scope, user flows and interface copy
  • The event, conversion and value model
  • API contracts, authentication and error semantics
  • Design tokens, type scale and component behaviour
  • Backend, push infrastructure and Remote Config
  • Release calendar, support runbook and escalation path

When native is the wrong answer

Sometimes the right answer is not an app. If your product is content, a booking flow or a dashboard, a responsive web application reaches more people, ships faster and updates without a review queue. If you need both platforms on a modest budget and the app is mostly screens over an API, a cross-platform build is a defensible trade. Native earns its cost when background work, hardware access, offline use or store distribution is the point.

Technology

What we build it with

These are tool and platform names used descriptively, to say what the work is built with. They imply no partnership, sponsorship, certification, authorisation or endorsement of any kind, and each mark remains the property of its owner.

Android
  • Kotlin
  • Jetpack Compose
  • Coroutines & Flow
  • Room
  • WorkManager
  • Hilt
  • Google Play Billing
  • Gradle
iOS
  • Swift
  • SwiftUI
  • Swift Concurrency
  • SwiftData / Core Data
  • StoreKit 2
  • Xcode
  • TestFlight
Platform services
  • Firebase Authentication
  • Firebase Cloud Messaging
  • Crashlytics
  • Remote Config
  • Google Analytics for Firebase
  • BigQuery export
Release & tooling
  • Play Console
  • App Store Connect
  • Android Studio
  • GitHub Actions
  • Fastlane
  • Git
Outcomes

What you are left holding

  • An application in your repository, with readable history and no hidden build steps.
  • Signing keys, store accounts and analytics properties owned by you from day one.
  • Events verified before release, so day-one data describes the product you shipped.
  • Crash reports that point at a line of code, not at a screenshot.
  • A release process someone on your side can run without us.
Questions

What people ask before starting

01Should we build native, or use a cross-platform framework?

It depends on what the app does. If the value sits in background work, hardware access, offline behaviour or deep platform integration, native earns its cost, because nothing you depend on is mediated by a bridge. If the app is largely screens over an API and you need both platforms quickly, a cross-platform build is a reasonable trade, and a web app is occasionally better than either. We will give you our reading and the reasons behind it.

02How long will our app take to build?

We will not quote a date before scope and architecture are agreed, because a number given that early is a guess dressed as a commitment. What we can do is break the work into slices you watch landing on a test track, with the first usable build early rather than at the end. Store review timing sits with the platforms and is outside anyone's control.

03What actually drives the cost of an app project?

Backend and integration work, not the number of screens. A feature touching payments, identity, offline sync or a third-party system carries far more work than a static screen does. Supporting two platforms natively means the interface layer is written twice while the backend stays shared. Older OS versions, tablet layouts, regulated data and anything needing a restricted permission all add work that is easy to underestimate.

04Who owns the code, the signing keys and the store accounts?

You do, throughout. Code lives in your repository from the first commit. Signing keys, Play Console and App Store Connect accounts, Firebase projects and analytics properties are created under your ownership, with us added as members. Nothing is held back at the end of an engagement, and there is no build step that only works on a machine of ours.

05What happens if the app is rejected by a store?

Nobody can guarantee approval and we will not pretend otherwise. What we can do is remove the common causes before submission and respond quickly when a rejection cites something specific. Where a rejection cites a declaration, a permission or a screen, the answer is a change to that specific thing, and we handle the exchange with the review team. Where a rejection is a policy fundamental, we say so plainly.

06What will you not do?

We will not add permissions or SDKs that collect more than the app needs so that a data partner can monetise the result. We will not build subscription flows designed to make cancellation hard. We will not clone another business's app, write misleading store listings, or buy installs and reviews. If a request is likely to get your developer account terminated, we say so and decline it.

Next step

Talk about the app you need

Send us what the app is meant to do, what already exists, and where it is stuck. We will tell you whether native is the right build and what we would do first.

Directcontact@mnfinfotech.com