Android App development

Native Android development at Software Mile handles the reality Android brings: many devices, many screen sizes, many OS versions. We build in Kotlin, test across the fragmentation that matters to your users, and ship through Google Play.

Android’s Real Challenge Is Range

An Android app that works on the developer’s phone and nowhere else is a common, expensive mistake. We target the device and OS spread your users actually carry – including the rugged handhelds and older tablets common in field and warehouse work – and test accordingly.

What We Deliver

  • Kotlin apps built to current Android standards and Material Design
  • Device-feature integration and support for enterprise and rugged hardware
  • Offline capability and reliable sync for field use
  • Google Play submission, and managed distribution for internal enterprise apps

If you also need iOS, a cross-platform approach may cover both economically – tell us the device landscape and we will recommend the right path.

Choosing a Minimum Android Version

Minimum SDK is a business decision dressed up as a technical one. Set it high and you cut off users on older hardware, which in field and warehouse fleets can be a large share of the devices still in service. Set it low and you inherit compatibility workarounds and libraries that have moved on without you.

Two numbers get confused constantly. Minimum SDK is the oldest OS the app runs on. Target SDK is the version it is built against, and Google Play enforces a floor on that before it will accept updates. You can support old devices and still target current Android. Pull your real device spread from the Play Console, or from IT’s inventory for an internal app, before anyone picks a number.

Why Does the App Stop Working When the Screen Goes Off?

This is a common complaint on Android field apps, and it is often not a bug in the app at all. Android deliberately restricts background work to protect battery through Doze, app standby buckets, background location limits, and rules about which foreground services are permitted and what they must show the user. Several manufacturers layer their own, more aggressive battery management on top of that.

The result is background sync, alarms, and location tracking that work on the developer’s device and fail on a technician’s. Designing for it means using the scheduling APIs Android sanctions, declaring foreground service types honestly, and testing on the brands your users actually carry. If the app has to track location or upload all day, say so during scoping, because it changes the architecture.

How Do You Get an Internal App Onto Company Devices?

Public Play Store publishing is only one option and is often the wrong one for an internal tool. Managed Google Play lets you publish privately to your own organization so the app is invisible to everyone else, and with an EMM or MDM in place apps install and configure themselves without anyone tapping through a setup.

  • Private publishing through managed Google Play for internal-only apps
  • Silent install and configuration through your existing device management platform
  • Dedicated device or kiosk setup, where a handset is locked to a single app
  • Sideloaded builds where no store is involved, along with the update problem that creates
  • Rugged hardware that ships without Google Mobile Services, which rules some options out entirely

How Much Device Testing Is Enough?

Emulators cover OS versions at scale and are the right tool for most regression work. What they do not cover is what actually breaks in the field: a particular manufacturer’s camera implementation, a scanner trigger key, an unusually narrow screen, a device with a hardware keypad. Those need real hardware in someone’s hands.

A workable split is emulators and a cloud device lab for breadth, plus physical units of whatever your users carry for depth. If your fleet includes rugged handhelds with integrated scanners or payment hardware, we ask for one on the desk during the build. Those integrations tend to surface their problems on real devices, and a desk is a better place to meet them than a technician’s route.

What Should You Do With an Existing Java Android App?

Kotlin and Java interoperate inside the same project, so a working Java app does not need a rewrite to move forward. New code can be written in Kotlin while old code is left alone until there is a reason to touch it. A rewrite is warranted when the app depends on abandoned libraries, cannot meet the Play target API floor, or has no tests and nobody left who understands it.

Where the constraint is capacity, staff augmentation is the other route. We can put an Android developer in your repository, on your board, working the way your team already works.

What Discovery Has to Settle Before Code

Discovery settles four things: the device and OS spread the app has to run on, whether distribution is public or managed, what the backend already exposes, and which hardware the app has to drive. Those four answers set the build order. The usual sequence is a vertical slice first: one screen, one real API call, one record written and synced, running on the oldest device in the fleet, before the rest of the screens exist. Hardware integrations are scheduled into that first slice rather than left until the end.

What we need from you at that stage is access and inventory rather than documents.

  • A device list giving manufacturer, model number, and the build fingerprint from each model's About screen, which is what separates a carrier or OEM variant from the base model.
  • Access to the backend API in a non-production environment, with credentials that can be issued and revoked without a ticket.
  • Store listing material for a public release: icon, screenshots at the sizes Play accepts, short and full descriptions, and a privacy policy URL that already resolves.
  • For managed distribution, the person who administers the EMM, since managed configuration keys have to be agreed with whoever writes the device policy.
  • Model numbers and vendor developer documentation for any scanner, printer, or payment peripheral.

Package Identity and Signing Are Decided Once

A release goes to Play as an app bundle, and Play generates the per-device artifacts from it, splitting by ABI, screen density, and language. That is why the file you upload is not the file that installs: you cannot hand someone the upload file to install directly. For local testing, bundletool turns a bundle into an installable set for a specific device, and anything wider than that goes through an internal test track or a private release.

An app's applicationId cannot be changed after publication, and neither can its signing identity, so anything that forces a rename or a re-sign becomes a new listing with no installs, no ratings, and no update path for the people already running the old one. Two decisions follow from that and both belong at the start. Pick a package name that does not embed a product name likely to be rebranded. Decide whether debug, staging, and production builds get distinct applicationId suffixes, because that is what lets a tester keep the released app and the build under test on one device at the same time.

Offline Is a Data Design Decision, Not a Setting

The moment a device can create and edit records without a connection, the app owns a local database and a queue rather than a cache. Those writes have to survive process death, a reboot, and a battery pull, which means they land in local storage, usually Room over SQLite in a Kotlin project, before the UI tells anyone the work was saved. Every queued item also needs an idempotency key, or a retry that succeeded on the server but lost its response on the way back creates a second record.

Reads are the easier half. The conflict rule is a business decision rather than an engineering one: last write wins, merge field by field, or stop and ask a person. What the app cannot do is leave it undecided, because whatever emerges from the order requests happen to arrive in is still a rule, just one nobody chose.

  • Which records are downloaded for offline use: everything, one territory, or a working set for the day.
  • How long a device may stay disconnected before its queued work is treated as stale rather than sent.
  • What happens to a queued record the server rejects on validation: discarded, retried, or put back in front of the person who created it.
  • Whether the interface distinguishes saved on this device from confirmed by the server, and what a partially synced device is allowed to do in the meantime.

Play Policy Work Belongs in the Build

Store requirements are not a submission-day checklist. The Data safety form has to describe what the app actually collects and shares, per data type, and it is read against the app's real behavior, so a third-party SDK quietly sending an advertising identifier makes the form wrong. Sensitive permissions carry their own declarations: background location, all-files access, and full package visibility each need a written justification naming the in-app feature that requires them, and background location review normally expects a video of that feature in use.

Some of these constrain the design rather than the paperwork. An app that lets people create an account has to offer account deletion in the app and from a web address Play can reach. Photo and video access on current Android versions is expected to go through the system photo picker unless the app genuinely needs the whole library, which removes a permission prompt but changes how the feature is built. Meeting these at the end usually means cutting something under time pressure; meeting them during design usually means one fewer permission to justify.

The App Is Only as Good as What It Talks To

A mobile client consumes somebody else's API, and it is a harsher consumer than a web front end. Old versions stay installed on devices for months, so an endpoint that changes shape breaks people who have not updated, and versioning has to be planned before the first release rather than after the first break. Payloads sized for a desktop browser become visible delay on a cellular connection, so list endpoints usually need pagination and a narrower projection than the internal web app uses. Push goes through Firebase Cloud Messaging, which means the backend needs somewhere to store per-device tokens and a way to retire the ones it gets told are no longer registered.

Authentication is the part most often redesigned. A phone is not a browser session: tokens have to survive app restarts, sit somewhere the OS protects, and refresh without dropping a warehouse worker at a login screen mid-shift. If the backend only issues a cookie-based session from a web login, a token flow has to exist before the app can behave properly. And if the data the app is meant to show does not sit behind an API yet, that integration is the first piece of work, not something to sequence after a mobile client that has nothing to talk to.

Frequently Asked Questions

What does native Android development mean here?

It means the app is written in Kotlin against the Android SDK and compiled into an installable Android package that runs directly on the device, rather than a web page inside a wrapper. Native code gets direct access to the camera, Bluetooth, the scan APIs a rugged handheld exposes, and the background scheduling the operating system provides. The tradeoff is that the Android app is its own codebase, and an iOS app is a separate build.

What does a Google Play Developer account require before we can publish?

An organization account goes through identity verification, which includes a D-U-N-S number for the legal entity and a developer name, address, and email that Play displays publicly on the listing. Once it exists, developers are added as users with scoped permissions, so nobody needs the owner login to upload a build. If distribution is managed rather than public, that account is also what gets linked to your EMM so private apps appear inside the right organization and nowhere else.

Does the backend have to be finished before the app starts?

No, but the contract for it does. A client can be built against stubbed endpoints as long as the response shapes, error cases, and auth flow are agreed, with the stubs swapped out as real endpoints land. What does not work is inferring the response shape from whatever the server happens to return that week, because mobile release cycles are too slow to absorb a change discovered late.

How does a barcode scanner on a rugged device actually get integrated?

There are three paths and the hardware decides which one applies. Devices from vendors such as Zebra or Honeywell expose an integrated scan engine through a vendor SDK or an intent broadcast the app registers for, which is what gives you the physical trigger key and control over decode settings. Cheaper handhelds often present the scanner as a keyboard wedge, where decoded characters arrive as keystrokes into whichever field has focus, which holds up until a screen transition or a second input field is involved. A phone with no scan hardware uses the camera through a library such as ML Kit, which is fine for occasional scans and noticeably slower for someone scanning all day.

Why do two apps with the same number of screens differ so much in effort?

Because a read-mostly app that displays data from an existing API is a different kind of build from one that creates records offline and drives peripherals. The first is largely screens and a network layer. The second adds a local database, a sync queue, conflict rules, and a separate integration per piece of hardware, each with its own failure behavior that has to be tested on the real unit.

Who owns the code and the signing keys?

You do. Source sits in your repository or is transferred into it, and third-party service accounts for crash reporting, maps, or messaging are created under your organization rather than ours. With Play App Signing, Google holds the app signing key and builds are uploaded using a separate upload key, which can be reset through the console if it is lost or compromised.

How do updates reach devices after release?

Through Play for anything published there, on a schedule the user partly controls, since auto-update can be turned off or limited to Wi-Fi and some share of the installed base is always behind. The In-App Updates API lets the app ask Play to update itself, either as a background download or as a blocking update, which is how a build with a breaking API change gets forced out. Under an EMM the update is policy instead: the administrator can require it and set a maintenance window so a device does not restart the app mid-shift. Where no store is involved, whoever manages the devices pushes the build by hand, and the app needs its own version check so people are told when what they are running is out of date.