Mobile App Development
Mobile app development at Software Mile is for organizations that need an app to do a job – field data capture, customer self-service, workforce tools – not a checkbox on a marketing plan. We build apps that work offline, sync reliably, and tie into your backend.
Native, Cross-Platform, or Both
The right choice depends on the app, not on a preference we are selling. For most business apps, a cross-platform build covers iOS and Android from one codebase at lower cost. For performance-critical or deeply device-integrated apps, native iOS and native Android earn their extra cost. We will tell you which your app needs.
The Parts That Actually Matter
- Offline behavior and sync – because field users lose signal and the app cannot lose their work
- Backend and API integration into the systems the app feeds and reads
- Authentication, security, and device management for enterprise deployment
- App store submission, and the update discipline to keep it approved and current
Tell us what the app has to do and who uses it. We will recommend the platform approach and scope the build.
Start With Whether You Need an App at All
Plenty of projects that arrive as “we need an app” are better served by a mobile web build. Browsers now handle responsive layouts, camera capture, geolocation, offline caching, and an installable home screen shortcut. Push notifications work too, though on iOS the user has to add the site to their home screen first. If your audience is occasional visitors who will not tolerate an install, the web version usually wins on reach and skips store review entirely.
An app earns its place when work has to continue for long stretches without a signal, when the job depends on hardware a browser cannot reach, such as Bluetooth peripherals, barcode scanners, or payment terminals, or when you are pushing software to managed company devices. Be careful with background processing as a justification. Both platforms limit what an app may do once the screen is off, so background work has to be designed around those limits. If none of those conditions apply, our web development work is often where the project belongs.
The Work That Keeps an App Alive After Launch
Build effort is the number most buyers plan for. The running commitment is the one that tends to arrive unannounced. An app is software you have agreed to maintain against two operating systems that change every year, two stores with shifting policies, and a set of credentials that expire on their own schedule.
An unmaintained app usually does not decline gradually. It works until an OS release or a store policy change makes it unsubmittable, and at that point it becomes an emergency project at an inconvenient time.
- Annual OS releases. Each one brings behavior changes that can break a feature that worked the week before.
- Store policy floors. Google Play requires a recent target API level before it accepts updates, and Apple periodically requires builds against a newer SDK.
- Expiring credentials. Developer program membership, signing certificates, and provisioning profiles all lapse if nobody watches the calendar.
- Third-party SDK churn. Analytics, crash reporting, payment, and mapping libraries deprecate versions on their own timetable, not yours.
- Privacy declarations. Store disclosures have to be updated whenever what the app collects changes.
How Should Your Backend Change for a Mobile Client?
A web app ships a fix and every user has it on the next refresh. A mobile app ships a fix, waits for review, and then waits for people to update. Old versions stay in the field long after the fix went out. That single fact should shape the API: version it, keep it backward compatible, and never assume every client is current.
Mobile clients also pay for chatty APIs in battery and cellular data, so endpoints shaped for a browser often need reshaping until a single screen costs a single call. If the app writes data offline, every write has to be idempotent, or a retried sync creates duplicates. This is usually where a mobile project meets our REST API and cloud application development work.
Who Owns the Store Accounts, the Source, and the Signing Keys?
Settle this before the first line of code. The Apple Developer and Google Play accounts should be registered to your legal entity, with the development team added as users. If the app is published under a vendor’s account, moving it later means a transfer process at best, and at worst a re-publish that loses your ratings and your installed base.
Signing keys deserve the same attention. Losing an Android signing key, or the ability to sign iOS builds, can force users to uninstall and reinstall to get updates. Ask where keys are held, who has access, and how they are backed up. Ask the same questions about the repository, the build pipeline, and the store listing assets.
Native, Cross-Platform, or a Mix
The framework question comes after the platform question. If the app is mostly forms, lists, and sync, a shared codebase in React Native or Flutter can cover both platforms with one team. If it leans on hardware, background behavior, or platform APIs that move quickly, expect native work on each side whatever framework sits on top. Our iOS, Android, and cross-platform pages go into those trade-offs, and a given project often draws on more than one of them.
What Belongs in the First Release?
The most useful first version does one workflow completely for one type of user. Three workflows delivered partially for everyone is the weaker trade. Complete means the whole path: sign in, do the work, sync it, and see it land in the system of record. Half a workflow tends to get tested politely and then abandoned. A complete one gets used, and use is what tells you what to build next.
Get it in front of real users before the stores do, using Apple’s TestFlight and Google Play’s internal testing tracks. Include crash reporting and basic usage instrumentation in that first build, because without them you are guessing about what to fix.
The Build Decisions That Bind Earliest
Once an app is the right answer and the store accounts and keys sit with your entity, the build has an order of its own, and it is not the order a design-first plan assumes. The API contract comes first, because it fixes what the client is able to do at all. A single workflow slice running against a real environment comes next, not against mocks. Breadth comes last. The risk in a mobile project sits in authentication, sync, and network behavior, and none of that shows itself until real data moves over a real connection. Layout is the cheapest thing in the project to change, which makes it the wrong thing to spend the first weeks on.
A few choices have to be settled before that slice can be built, and each one is expensive to revisit once code depends on it.
- The identity provider and the token lifetime. A session that expires while a user is out of coverage, with a queue of unsent work behind it, is a different problem from one that expires at a desk, and the refresh strategy has to be chosen with that case in mind.
- The sync direction for every entity: read-only on the device, write-through, or write and reconcile. Screens that mix all three without saying so are where most sync defects start.
- The minimum OS versions in scope, which decides how many compatibility paths the code carries and which platform APIs are available at all.
- The distribution channel: a public store listing, internal-only distribution to a company-owned fleet, or both. Managed distribution changes enrollment, configuration, and how updates reach devices, so it is not a packaging decision left to the end.
- Who tests it. A small group who will run their actual work through the beta build, rather than a scripted QA pass, because scripts rarely reproduce the connectivity, data volume, and interruptions that break a mobile client.
What Offline Sync Has to Decide, Record by Record
Idempotent writes stop a retry from creating duplicates, but they say nothing about what to do when the same record changed in two places. That answer belongs to each entity, not to the app as a whole. A price list only the server edits needs nothing beyond a refresh. A work order edited by a technician in a basement while a dispatcher edits it at a desk needs a rule, and the rule has to be one the business will accept on the day it silently discards somebody's edit.
Device clocks are user-settable and they drift, so ordering edits by device time produces reconciliation bugs that are close to impossible to reproduce. Order by server-assigned versions or a monotonic sequence, and treat the device timestamp as data the user entered rather than as a fact about when something happened.
- Client-generated UUIDs for new records, so a row has a stable identity before the server has ever seen it and a retried write can be recognized as the same row.
- A per-record version counter or ETag, returned on read and sent back on write, so the server can reject a write made against a stale copy instead of overwriting blindly.
- A conflict rule chosen per entity: last write wins, per-field merge, or hold the conflict for a person to resolve. The third is the honest option and the most work, and some records deserve it.
- Tombstones for deletes, because an absent record and a record that has not synced yet look identical from the client.
- Batch sync endpoints that return per-item results. One bad record in a batch of 200 should not fail the other 199, and the client has to be told which one failed.
- An outbound queue in durable storage, usually SQLite, that survives the OS terminating the app in the background. A queue held in memory loses exactly the work the user could not afford to lose.
What App Review Actually Rejects
Rejections cluster around a small number of guideline sections rather than around code quality. Among the most common is minimum functionality: an app that wraps a website, or that offers nothing a browser tab already does. Another frequent one is asking for data or permissions with no visible feature behind them, such as a location purpose string that does not describe something the user can actually do, or a permission prompt fired at launch instead of at the moment the feature needs it.
Account handling carries its own rules. An app that lets users create an account has to let them delete it from inside the app, and pointing at a support address does not satisfy that. On Android, restricted permissions such as background location and all-files access need a declaration form and often a demonstration video before an update is accepted.
Third-party code brings review obligations you inherit. Apple requires privacy manifests declaring collected data and the reasons for using certain APIs, and expects commonly used SDKs to ship signed with manifests of their own, so an attribution SDK, a push provider, a support chat widget, or a session-replay library can hold up a submission on its own timetable. Google Play's Data safety form has to match what the app and everything inside it actually collects. Both declarations are made per submission, which is why an app that passed last year can be rejected this year against a rule enforced since.
Managed Devices Change the App, Not Just How It Is Delivered
Deploying to a company-owned fleet through device management is more than a different download path. Both platforms let an MDM push a configuration dictionary into the app at install time: on iOS the app reads it from the managed configuration key in UserDefaults, on Android through Android Enterprise managed configurations. That one feature removes the worst pattern in enterprise mobile work, the separately built binary per customer or per site, because the server URL, tenant, and feature switches arrive as configuration instead of as code.
Management also reaches into behavior the app does not control. A work profile separates business and personal data on an employee-owned Android device, and policy can block copy, paste, and screenshots in ways that surprise a team that assumed those worked. A per-app VPN can route only this app's traffic to the internal network, which is often how a mobile client reaches a system with no public endpoint. Corporate TLS inspection and certificate pinning conflict directly, so if you pin, you need to know whose certificate is actually terminating the connection.
Keep credentials in the platform key stores rather than in app storage: the iOS Keychain, and Keystore-backed keys on Android. Gate sensitive actions by having biometric authentication unlock a key rather than return true or false, since a boolean result is trivial to defeat on a rooted or jailbroken device and an unreleased key is not.
The Release Controls to Build Before the First Release
A mobile fix reaches users on the store's schedule and then on each user's, so the controls worth having are the ones that let you slow a bad release down and change behavior without shipping a build. Staged rollout is the first. Google Play can release to a percentage of users and halt, and the App Store's phased release spreads automatic updates over seven days and can be paused, though anyone who visits the listing can still pull the new version by hand.
Server-side switches are the second. A feature that can be turned off from the server, or a configuration value the client reads at launch, converts a store round trip into a support action. It is the same reason to put anything likely to change, such as endpoints, limits, and thresholds, in configuration rather than hard-code it into the binary.
Two mechanics are easy to skip and painful to add later. Crash reports are only readable if the symbols were uploaded when the build was made: dSYM files on iOS, the R8 or ProGuard mapping file on Android. And a client version check against a server-published minimum lets you tell an old client to update. Android's in-app update API can prompt or force that in place, while on iOS the app has to detect the condition itself and send the user to the store listing.
Frequently Asked Questions
What if the hard part of our project is our existing systems, not the app?
Then it is an integration project with a mobile client on the end of it, and it is worth scoping it that way rather than as an app project. The app can only be as good as what it is allowed to read and write, so the schedule is set by the API work, the data cleanup, and whatever the system of record will and will not expose. If a different vendor owns that system, their availability and their release calendar become part of your timeline, and that is a conversation to have before the mobile scope is fixed.
What has to exist on our side before a build can start?
An authenticated way into the data, meaning an API or a decision to build one, an identity source the app can authenticate against, a non-production environment with realistic data, and store accounts registered to your legal entity. Test data matters more than teams expect, because sync, pagination, and search defects appear at real record counts and real text lengths, not against a dozen tidy rows. You also need someone on your side who can answer domain questions quickly, since one blocked question stalls a workflow slice completely.
Our system is on-premises and not exposed to the internet. Can a mobile app use it?
Yes, but choosing that path is an early decision rather than a late one. The usual options are a published endpoint in a DMZ with its own authentication, a VPN the device joins, a per-app VPN pushed by device management so only this app's traffic crosses, or a relay that the internal system connects out to. That choice decides whether the client can assume an always-reachable server or has to be built store-and-forward, and the assumption reaches into the data layer, not only the network code.
How does the first store submission actually proceed?
Enrollment comes first: an Apple Developer Program membership in the organization's name, which requires a D-U-N-S number for a legal entity, and a Google Play developer account with organization verification. Then the app records, listing assets, and the privacy disclosures, meaning Apple's App Privacy answers plus privacy manifests and Google Play's Data safety form. A build is uploaded and exercised through TestFlight or an internal testing track before it is submitted, and note that external TestFlight testing itself needs a review pass while internal testing does not. Review is a human step whose duration nobody can promise, so treat the submission date and the launch date as two different dates.
If we use React Native or Flutter, is that one release or two?
Two. One codebase still produces two binaries, two store listings, two sets of signing material, two review queues, and two sets of privacy disclosures. Version numbering diverges as well, since Android uses an integer versionCode and iOS a build string, and push, permissions, and background execution each behave differently per platform. A shared codebase reduces feature work; it does not reduce release operations.
How do we choose the minimum OS versions to support?
From the devices your users actually have, not from general market share charts. For a company-owned fleet, device management reports the inventory exactly; for a public app you are estimating until you ship, so start no lower than you need to and check the store consoles after the first weeks. Every older version supported adds compatibility paths and rules out newer platform APIs, and that is code carried indefinitely rather than a one-time port. Note that minimum supported version and target API level are separate settings: Google Play's requirement is about what you target, and it does not force you to drop older devices.
Can an app someone else built be taken over?
Taking over an existing app is ordinary work, and the real question is what transfers with it: the store accounts or an app transfer between accounts, the repository with its history, the signing material, and the third-party service accounts the app depends on, including push credentials, crash reporting, and map or analytics keys. Whether a lost signing key is recoverable depends on which key it is, since with Play App Signing the app signing key is held by Google and an upload key can be reset, while a legacy self-signed app whose key is gone cannot publish an update under the same package name. On iOS, certificates and provisioning profiles can be regenerated from the account that owns them, which is why account ownership matters more than the files themselves.