iOS
Swift and SwiftUI apps for iPhone and iPad, designed to Apple conventions and checked against the review, privacy and accessibility rules long before anything is submitted.
SBPO Consulting · Mobile
Android is not one device. It is thousands of them, across a decade of OS versions, several manufacturer software layers and screen shapes that change while the app is running. Building well here means planning for that spread deliberately, rather than discovering it in the crash reports.
Where we come in
The most common way an Android app goes wrong is that it was never planned as an Android app. It was planned as an iOS app, built first, and then translated — same navigation, same gestures, same assumption of a narrow, well-behaved set of devices.
Android does not reward that. It runs on hardware from a large number of manufacturers, each with its own software layer on top of the operating system, its own approach to killing background processes, and its own camera and power management behaviour. The screen might fold. The system font might be set two sizes larger than your designer imagined. The device might have been good in 2021 and be running an OS three versions behind today.
None of this makes Android harder in a mysterious way. It makes it harder in a specific, plannable way, which is the useful news: the failures are predictable enough to design against if you decide to.
We build in Kotlin unless there is a concrete reason not to, and the reason is not fashion. Android has been officially Kotlin-first since 2019, meaning Jetpack libraries, documentation, samples and tooling are designed for Kotlin with Java support trailing behind. Google’s own comparison puts apps written in Kotlin at twenty per cent less likely to crash, which is largely the compile-time null safety doing its job on the single most common crash class in Android’s history.
Jetpack Compose is the interface layer. Declarative UI removes an entire family of bugs around view state getting out of sync with data, and it makes theming genuinely systemic: colour roles, typography and shape are defined once and every screen inherits them. That is what makes dark theme, dynamic colour and large-text support properties of the app rather than three separate retrofits.
Compose interoperates with the older View system in both directions, which matters for existing apps. A screen at a time is a viable migration path, and it is nearly always the right one, because a total rewrite trades a known set of small problems for an unknown set of large ones.
Material Design 3 is not decoration, it is a set of conventions your users have already learned from every other app on their phone. Navigation placement, the behaviour of the system back gesture, how a sheet dismisses, where a floating action button belongs — these are expectations, and violating them is expensive in reviews.
Working within the system also handles a set of requirements you would otherwise meet individually: dynamic colour derived from the user’s wallpaper, dark theme that is a real theme rather than an inverted palette, and components that already scale with the system font size. Where the interaction design is the substantial part of a project, we run it as design work in its own right, using Material as the grammar rather than the whole vocabulary.
“Android fragmentation” is usually said in a tone of complaint, which is not useful. Treated as a scoping exercise it becomes tractable.
We start with your data. OS version distribution, manufacturer mix, screen size and density classes, and memory profile from your existing analytics — or from your target market if the app is new. That evidence sets three things: the minimum SDK, the device matrix that will be physically tested, and the list of what is explicitly out of scope.
Every one of those is a cost decision. Supporting an Android version two years older extends your reach and costs testing time and compatibility branches. Supporting foldables and tablets properly means designing adaptive layouts rather than stretching a phone layout across a large screen. Wear OS or Android Auto are separate products with their own review criteria, not a checkbox. We would rather have that conversation with numbers at the start than have it as a change request halfway through.
Google Play’s own delivery model helps on the size side. App bundles have been the required publishing format for new apps since August 2021, and they let Play generate device-specific downloads rather than shipping every user every resource and every architecture.
The architecture question we care most about is not which pattern, it is whether a competent Android engineer who has never met us can open the project and understand it in a morning.
In practice that means a unidirectional data flow with view models holding state, a repository boundary so the interface layer does not know or care whether data came from the network or from local storage, coroutines and flows for asynchronous work, and dependency injection where it removes real coupling rather than because a diagram wanted a box. Modularisation happens when build times or team size actually justify it. A four-screen app split into eleven Gradle modules is not architecture, it is a tax.
Local persistence generally means Room, network access Retrofit, deferred and constrained work WorkManager — the boring, well-documented choices, because an app that outlives its original team is worth more than an app that showcases one.
Security follows the same principle of being checkable. Credentials belong in the platform keystore rather than in shared preferences, transport security is configured explicitly, and where an app has something worth attacking — payments, licensing, gated content — the Play Integrity API can tell your server whether the request came from an unmodified build on a genuine device, installed by an account that actually obtained it from Google Play. We review this against the OWASP Mobile Application Security Verification Standard so the discussion cites a published document rather than our opinion.
If the app sells digital content or subscriptions, Google Play’s billing system is not an integration you can defer. It is a policy requirement for digital goods, it carries its own version deadlines — new apps and updates must use Billing Library version 8 or later from 31 August 2026 — and it is the part of the app where bugs turn into refunds.
The work that actually repays effort is in the states nobody demonstrates: a purchase interrupted by a lost connection, an entitlement restored on a new device, a subscription that lapses and resumes, an upgrade mid-period, a refund issued from the Play side. Entitlement is verified server-side using the Play Developer API rather than trusted from the client, with real-time developer notifications keeping your records aligned with Google’s. Physical goods and services are a different matter entirely and belong outside the billing system, which is a distinction worth confirming before your business model assumes otherwise.
Android measures your app whether you do or not, and it acts on the results.
Google Play’s Android vitals tracks core vitals including the user-perceived crash rate and the user-perceived ANR rate, with bad-behaviour thresholds set at 1.09% and 0.47% respectively over a rolling 28-day window. Exceed them and Play can reduce your store visibility and show users a warning on the listing. Excessive wake locks and memory usage sit alongside those as further quality signals.
That reframes performance work. Frame rendering, startup time, background work scheduling and wake lock discipline are not polish to be done if there is budget left — they feed a metric that determines how findable the app is. Google’s core app quality guidelines are similarly concrete: frames rendered within the 16ms budget for smooth scrolling, and visible feedback if startup takes more than a couple of seconds.
The practical consequence in our process is that staged rollout is a control rather than a ceremony. Crash and ANR rates are checked at each rollout increment, and a regression stops the release rather than being noted for next time.
Accessibility on Android is largely a matter of doing four things consistently, and inherited apps usually fail at all four.
Every interactive element needs a content label, decorative images need to be explicitly marked as decorative so TalkBack skips them, headings and panes need semantic marking so a screen reader user can navigate structurally, and layouts need to survive the largest system font scale without clipping. Touch targets have a stated minimum of 48dp in Google’s core app quality guidelines, and text contrast of 4.5:1 for small text and 3:1 for large text.
Compose makes most of this a matter of setting semantics properly, which is cheap during development and irritating afterwards. We test with TalkBack running rather than relying on an automated scan, because the common failures — an unlabelled icon button, a form where focus order makes no sense — are obvious in thirty seconds of real use and invisible to a linter.
Unit tests cover logic, instrumentation tests cover the journeys that matter commercially, and both run as part of the release process rather than when someone remembers. Beyond that, physical devices from the lower end of your support matrix, plus a cloud device lab for breadth.
Google Play’s release tracks then do the rest of the work. Internal testing distributes to up to 100 testers for quick quality checks, closed testing supports much larger invited groups, and open testing exposes a build publicly before production. Used in sequence with a staged production rollout, this catches manufacturer-specific problems while the affected population is small enough to be an inconvenience.
The listing itself deserves separate attention: install rate is a ranking input on Play, and store listing optimisation is a different discipline from building the app.
Android maintenance runs on a published calendar, which is at least honest of it.
Target API level requirements are the big one. From 31 August 2026 new apps and updates must target Android 16 (API level 36) or higher, with different levels for Wear OS, TV, Automotive and XR, and existing apps must target at least Android 15 to remain available to new users on newer devices. Extensions can be requested through Play Console, which buys time rather than removing the obligation. Apps selling digital goods have a parallel obligation to keep the Play Billing Library current — version 8 or later for new apps and updates from the same date.
We map these against your release schedule at the start of an engagement so they become planned work. It is a dull thing to be good at, and it is the difference between an app that is still installable in three years and one that quietly is not.
Most organisations are not building for Android in isolation. If both platforms are in scope, the iOS side has a different set of constraints — review, privacy declarations and an annual OS cadence of its own — and a single shared codebase is worth considering before you commit to two teams. The overall trade-offs sit on our mobile app development page, which is the right place to start if the platform decision is still open.
If you already have an Android app and mainly want to know how bad it is, an audit is a reasonable first conversation and does not commit you to a rebuild.
Scope
Every engagement is scoped in writing before it starts. These are the artefacts that leave our hands and become yours.
Kotlin source, Gradle configuration, signing set up for release, and documentation that lets an engineer who has never seen the project produce a signed build on their first day.
Release artefacts published as app bundles so Google Play generates optimised downloads per device configuration, with Play App Signing set up and the upload key recorded and handed to you.
The minimum SDK, the target SDK, the manufacturers and screen classes in scope, and the ones deliberately excluded. Every one of those is a cost decision, so each is made openly rather than by default.
Layouts built from the design system with typography, colour roles and components defined once, including dark theme and large-text behaviour rather than a light-mode design that degrades when the system changes.
Unit tests for logic, instrumentation tests for the critical journeys, and a run configuration that executes them on physical or cloud-hosted devices as part of the release process.
Store listing, content rating, Data safety declaration, testing tracks and staged rollout configured under your own developer account, with your team holding the owner role rather than us.
Crash reporting wired to a named recipient and the Android vitals dashboard reviewed as part of every release, so regressions surface as data rather than as one-star reviews.
The published target API level deadlines, billing library requirements and library upgrades that will affect you, mapped to a schedule so the maintenance is planned work rather than an emergency.
How it runs
Before any architecture decisions, we look at what your users actually hold: OS version distribution, manufacturer mix, screen sizes and memory classes from your own analytics where they exist, or from your market where they do not. That data sets the minimum SDK and the test matrix, and it usually surprises people.
A unidirectional data flow, a view model layer, a repository boundary and dependency injection where it earns its place. Modularisation is applied when build times or team size justify it, not because a diagram looks impressive in a proposal.
Declarative UI with theming, typography and colour roles defined once and applied everywhere, so dark mode, dynamic colour and large text are properties of the system rather than three separate pieces of retrofitting.
Unit and instrumentation tests run continuously, then physical devices covering the low end of your support matrix. A mid-range handset three years old will find problems no emulator will, particularly around memory and background process death.
Internal testing, then closed and open tracks where the audience justifies them, then a staged production rollout with crash and ANR rates watched at each percentage before the next increment. A bad release caught at five per cent is an inconvenience rather than an incident.
After launch, Android vitals and crash reporting are reviewed on a schedule, and platform deadlines are handled ahead of time rather than when Play Console starts warning about them.
Tooling
We pick tools for the problem, not for the résumé. Where a platform is a poor fit we will say so before you have paid for it.
Non-negotiables
These are checkable. Ask us to demonstrate any of them on your own project before you sign anything.
The Google Play developer account is registered to your organisation with your team as owner, and the upload key is recorded and handed over. Losing control of signing material is one of the few genuinely unrecoverable situations in Android, so we do not let it depend on us.
Minimum SDK, target SDK and the tested device set are written into the scope. If supporting an older Android version costs meaningfully more, you get told the price before the decision, not after the estimate.
Content labels on every interactive element, correct heading semantics, touch targets at the 48dp minimum from Google's core app quality guidelines, and layouts that hold together at the largest system font scale.
Crash and ANR rates are reviewed against Google Play thresholds during staged rollout, and a regression stops the rollout. Store visibility depends on these numbers, so they are release criteria rather than a report.
Questions
The honest answer comes from your own data rather than from a rule of thumb. Every additional older version you support costs testing time and constrains which APIs you can use without compatibility branches, so the decision is a commercial one. Two things are not negotiable: Google Play sets a minimum target API level for apps that want to keep publishing updates, currently Android 16 (API level 36) for new apps and updates from 31 August 2026, and apps that stay on older targets stop being available to new users on newer devices. Supporting old versions at the bottom end is a choice; falling behind at the top end is a slow removal from the store.
Because Google does. Android has been officially Kotlin-first since 2019, which means new Jetpack libraries, samples, documentation and tooling are designed for Kotlin and Java support follows behind. Google also reports that Android apps using Kotlin are twenty per cent less likely to crash, largely because the language removes an entire category of null-related failure at compile time. Existing Java code is not a problem, since the two interoperate fully and a codebase can be migrated file by file rather than rewritten.
With a matrix, not with hope. We take the manufacturer and OS version distribution from your analytics, pick physical devices covering the low end and the awkward middle of that range, and use a cloud device lab for breadth on top of that. Emulators are used continuously during development and trusted for logic, not for memory pressure, battery behaviour, camera stacks or manufacturer-specific background process management, which is where the real failures live. Staged rollout then acts as the final control: a problem that only shows on one manufacturer usually appears in the vitals data within the first few per cent of users.
Policy, almost always, rather than code. The recurring causes are a Data safety declaration that does not match what the app actually collects, permissions requested without a clear in-app justification, missing account deletion where an account can be created, content or ads inconsistent with the declared rating, and metadata that overstates what the app does. Google publishes the developer programme policies in full, and reading the ones relevant to your category before the build starts is considerably cheaper than reading them after a rejection. We prepare the declarations alongside the app rather than filling them in on submission day.
Yes, and incrementally is usually the right approach. Kotlin and Java coexist in the same module, so new work can be written in Kotlin while existing classes are converted as they are touched. Compose interoperates with the older View system in both directions, which means screens can be migrated one at a time rather than in a single risky rewrite. The first step is always an audit: whether the project builds from a clean checkout, how far behind the target API level it is, what state the dependencies are in, and whether the signing key still exists. Occasionally that audit concludes a rewrite is cheaper, and we will show you the arithmetic rather than simply assert it.
Directly. Google Play tracks a set of core vitals including the user-perceived crash rate and user-perceived ANR rate, and defines bad-behaviour thresholds at 1.09% and 0.47% respectively, assessed over the previous 28 days. Apps exceeding those thresholds can have their store visibility reduced, and Play may show a warning on the store listing. In other words stability is a discovery factor, not just an engineering quality measure. It is also why we treat a rising crash rate during staged rollout as a reason to stop rather than as something to fix in the next release.
Mostly work that produces no new features. Each year brings a new Android release with behaviour changes and deprecations, plus published deadlines you have to meet to keep publishing: target API level requirements, and current billing library versions for apps selling digital goods. Add dependency and Gradle upgrades, manufacturer-specific bugs that appear with OEM software updates, crash triage, and design changes that alter how existing screens render. A realistic arrangement assumes several small releases a year even if nothing about the product changes.
Adjacent work
Swift and SwiftUI apps for iPhone and iPad, designed to Apple conventions and checked against the review, privacy and accessibility rules long before anything is submitted.
Flutter and React Native apps built from a single codebase, with the framework chosen on your constraints and the cases where cross-platform is the wrong answer named before you commit.
Keyword strategy, listing copy, creative testing and store analytics for the App Store and Google Play — measured on installs and retained users, not on ranking screenshots.
Part of our mobile apps practice.
Mobile
Send us the problem, the constraint and the deadline. You will get a considered reply from someone who would actually do the work — not a templated proposal.