Opens in a new tab

Android Development

Android development is one of the main areas of software engineering I work in.

I build native Android applications with Kotlin, Jetpack Compose and the wider Android Jetpack ecosystem, with an emphasis on clean architecture, responsive user interfaces, reliable local persistence, monetization, offline behavior and maintainability.

For me, Android development is not simply creating screens that run on a phone.

A production application has to work across different devices, Android versions, lifecycle states, network conditions and resource constraints while remaining understandable enough to evolve safely over time.

How I approach Android development

I work on Android applications involving:

  • native Kotlin development,
  • Jetpack Compose interfaces,
  • Material 3 design,
  • local persistence,
  • REST API integration,
  • offline-first architecture,
  • background work,
  • billing and monetization,
  • advertising,
  • localization,
  • widgets,
  • application settings,
  • media processing,
  • device and system APIs.

I prefer modern Android architecture with explicit state ownership and clear separation between UI, application logic, persistence and external services.

Kotlin

Kotlin is the primary language I use for Android development.

I use it throughout the application stack:

  • UI state,
  • ViewModels,
  • repositories,
  • database access,
  • networking,
  • billing,
  • background processing,
  • application logic.

Kotlin’s null safety, coroutines, Flow and expressive type system fit naturally with the architecture of modern Android applications.

Jetpack Compose

Jetpack Compose is the primary UI toolkit I use for new Android interfaces.

I build screens declaratively from application state rather than manually synchronizing a hierarchy of views.

A typical flow may look like:

State
↓
Jetpack Compose
↓
UI
↓
User Action
↓
ViewModel
↓
Updated State

This makes the relationship between application state and visible UI much easier to reason about.

Material 3

I use Material 3 as the foundation for many Android interfaces.

It provides established interaction patterns and components for:

  • navigation,
  • buttons,
  • cards,
  • dialogs,
  • typography,
  • surfaces,
  • theming.

I still treat the application as its own product rather than simply assembling default Material components.

Spacing, hierarchy, typography and interaction design should reflect the purpose of the application.

Android Jetpack

Android Jetpack provides much of the architecture around the application.

Depending on the project, I use components such as:

  • ViewModel,
  • Navigation,
  • Room,
  • DataStore,
  • WorkManager,
  • Lifecycle APIs.

These components provide established solutions to common Android problems and reduce the need for custom infrastructure.

Application Architecture

I prefer clear architectural layers.

A typical application may look like:

Jetpack Compose
      ↓
ViewModel
      ↓
Repository
   ↙       ↘
Room     REST API

with separate infrastructure for concerns such as:

DataStore
Billing
Advertising
Background Work

Each layer has a defined responsibility.

The UI renders state.

The ViewModel coordinates a screen.

Repositories provide access to application data.

Persistence and networking handle their own technical concerns.

State Management

Android applications become much easier to maintain when important state has a clear owner.

I prefer explicit UI state models rather than a collection of unrelated mutable variables.

For example:

loading
content
error
selectedItem
premiumState

The screen renders itself from that state.

This makes behavior easier to inspect, debug and test.

ViewModel

I use ViewModel for state and logic that belongs to a screen or feature but should not depend on a particular Activity or composable instance.

A ViewModel may:

  • load application data,
  • process user actions,
  • communicate with repositories,
  • run asynchronous operations,
  • expose UI state.

Composable functions remain focused on presentation.

Kotlin Coroutines

Most modern Android applications perform substantial asynchronous work.

I use Kotlin coroutines for tasks such as:

  • API requests,
  • database operations,
  • file processing,
  • billing,
  • background calculations.

I keep expensive work away from the main thread so the interface remains responsive.

Kotlin Flow

I use Flow when application data changes over time.

Typical sources include:

  • Room,
  • DataStore,
  • repositories,
  • application services.

A common architecture is:

Data Source
↓
Flow
↓
ViewModel
↓
Compose

When the data changes, updated state can propagate naturally through the application.

Room

I use Room when an application needs structured relational local data.

Examples include:

  • histories,
  • user-created records,
  • cached remote data,
  • structured collections,
  • offline content.

Room provides entities, DAOs, SQL query verification, migrations and integration with Flow and coroutines.

DataStore

I use DataStore for lightweight persistent state such as:

  • preferences,
  • settings,
  • onboarding state,
  • selected modes,
  • UI configuration.

I keep the distinction between DataStore and Room clear.

Preferences belong in DataStore.

Larger relational datasets belong in Room.

SQLite

Room uses SQLite underneath, so understanding relational database principles remains important.

I consider:

  • schema design,
  • indexes,
  • transactions,
  • relationships,
  • query performance.

A local mobile database still benefits from good data modeling.

Offline-First Design

Mobile connectivity is not reliable enough to assume that every operation can always reach a server.

Where appropriate, I design applications around local state first.

For example:

REST API
↓
Repository
↓
Room
↓
Flow
↓
UI

The application can remain useful while offline, and synchronization can update the local database when connectivity becomes available.

REST APIs

I integrate Android applications with REST APIs for functionality such as:

  • remote content,
  • user accounts,
  • synchronization,
  • application services,
  • external integrations.

I handle network operations as unreliable by design.

The application needs to account for:

  • timeouts,
  • slow connections,
  • offline state,
  • authentication errors,
  • malformed responses.

The UI should remain predictable in all of these cases.

JSON

JSON is commonly used for communication between Android applications and backend services.

I map external data into typed Kotlin models rather than passing loosely structured JSON throughout the application.

Where appropriate, I separate:

  • API models,
  • database entities,
  • domain models,
  • UI models.

This allows each layer to evolve independently.

Background Work

Some operations should continue or retry independently of the current screen.

For this type of work, I use appropriate Android background mechanisms such as WorkManager.

Typical tasks include:

  • synchronization,
  • uploads,
  • periodic processing,
  • deferred operations.

I distinguish persistent background work from ordinary asynchronous UI operations.

A coroutine launched from a ViewModel is not automatically a replacement for a persistent worker.

Application Lifecycle

Android applications do not run in a simple uninterrupted process.

Activities can be recreated.

The application can move between foreground and background.

The operating system can terminate the process.

I design state according to the lifetime it actually requires.

Temporary UI state may belong in Compose.

Screen state may belong in a ViewModel.

Persistent data belongs in Room or DataStore.

Process Death

A ViewModel does not replace persistence.

If important state must survive process termination, I store it appropriately.

This distinction is important for avoiding applications that appear correct during development but lose critical state under realistic mobile conditions.

Navigation

I use structured navigation rather than allowing screens to depend directly on each other.

For Compose applications, navigation is represented through explicit destinations and routes.

I prefer passing stable identifiers between destinations rather than transferring large application objects through navigation.

The destination can retrieve the data it needs from the application state layer.

Adaptive Layouts

Android runs across:

  • phones,
  • tablets,
  • foldables,
  • different orientations,
  • different window sizes.

I avoid designing interfaces around one fixed screen size.

For larger displays, the information architecture itself may adapt.

For example:

phone
→ single-pane interface

large screen
→ list-detail layout

Responsive Android design is not simply scaling the same phone interface.

Accessibility

Accessibility is part of application quality.

I consider:

  • semantic labels,
  • touch target size,
  • color contrast,
  • scalable text,
  • screen-reader behavior,
  • reduced-motion preferences where appropriate.

Custom UI should not lose accessibility information that standard Android components would otherwise provide.

Font Scaling

Users can significantly increase system font size.

I design interfaces that can tolerate this rather than assuming every label fits into a fixed rectangle.

This affects:

  • component height,
  • line wrapping,
  • layout constraints,
  • navigation labels.

Supporting larger text also tends to improve general UI robustness.

Dark Mode

I support light and dark themes where appropriate.

Rather than hard-coding individual colors throughout the application, I use semantic theme colors.

This makes the visual system easier to maintain and allows the application to adapt consistently.

Localization

I design applications so text is separated from implementation code and can be localized cleanly.

Localization requires more than translating strings.

Different languages can affect:

  • text length,
  • line wrapping,
  • button width,
  • plural rules,
  • date formatting,
  • number formatting.

I therefore avoid layouts that only work for one language.

Google Play Billing

I integrate Google Play Billing when an application needs:

  • one-time purchases,
  • subscriptions,
  • premium functionality,
  • remove-ads purchases.

I treat purchases as persistent entitlements rather than temporary button events.

Billing logic needs to account for:

  • restoration,
  • pending purchases,
  • acknowledgement,
  • reconnection,
  • failure states.

Google Mobile Ads / AdMob

For free applications supported by advertising, I integrate Google Mobile Ads carefully.

Depending on the application, this may include:

  • rewarded ads,
  • interstitials,
  • app open ads,
  • banners.

I keep advertising logic isolated from the core application.

If an ad fails to load, normal application functionality should generally continue working.

Monetization Architecture

I prefer treating monetization as a separate subsystem.

For example:

Google Play Billing
↓
Entitlement State
↓
Application Features

and:

Entitlement State
↓
AdMob

A remove-ads purchase can therefore disable advertising without requiring each individual screen to understand the entire billing implementation.

Consent and Privacy

Advertising and analytics can involve privacy-sensitive processing.

I design applications so consent state affects the actual behavior of the application rather than existing only as a visual dialog.

Where an SDK requires permission or consent, the surrounding architecture needs to respect that state.

Permissions

Android applications should request only the permissions they actually need.

I avoid requesting broad access simply because it is technically available.

Permission requests should occur in context, where the user can understand why the feature requires them.

The application should also handle denial gracefully.

File Handling

Some Android applications need to work with local files.

I use modern Android storage and document APIs rather than assuming unrestricted filesystem access.

The application should work within Android’s security model and request access only to the resources required for the feature.

Media Processing

For applications involving images, audio or video, I consider both functionality and device resource limits.

Media processing can require substantial:

  • CPU,
  • memory,
  • storage,
  • battery.

I avoid performing expensive work directly on the UI thread and provide clear progress feedback for longer operations.

Widgets

Where useful, I build Android home-screen widgets as extensions of the main application.

A widget needs its own design constraints because it:

  • has limited space,
  • is not a normal application screen,
  • may update independently,
  • needs to remain useful at a glance.

I treat widgets as lightweight interfaces into application data rather than miniature copies of the full application.

Notifications

When an application needs notifications, I design them around actual user value.

Notifications should communicate useful state or time-sensitive information.

I avoid creating notification behavior simply to increase engagement.

Users should retain clear control over what kinds of notifications they receive.

Performance

Mobile applications operate under stricter resource constraints than desktop software.

I pay attention to:

  • startup time,
  • unnecessary recomposition,
  • database queries,
  • network requests,
  • image loading,
  • memory usage,
  • background processing.

Performance problems that are barely visible on a development machine may become significant on lower-end devices.

Memory Management

Large images, media files and collections can consume memory quickly.

I design processing workflows so they do not assume unlimited resources.

This can involve:

  • incremental processing,
  • appropriate image sizing,
  • avoiding unnecessary buffer copies,
  • releasing resources when no longer required.

Reliable behavior on real devices matters more than theoretical maximum quality.

Battery Usage

Mobile software also needs to consider energy consumption.

I avoid unnecessary:

  • polling,
  • background loops,
  • network requests,
  • wakeups,
  • high-frequency location or sensor work.

Background behavior should match the actual value it provides.

Error Handling

I treat errors as normal application states.

Possible failures include:

  • network unavailable,
  • database failure,
  • invalid data,
  • billing unavailable,
  • permission denied,
  • unsupported file,
  • background task failure.

The application should explain what happened and provide a reasonable recovery path rather than simply crashing.

Defensive Development

Mobile applications operate in environments I do not control.

Users can have:

  • unusual device configurations,
  • low storage,
  • interrupted connectivity,
  • older Android versions,
  • manufacturer-specific behavior.

I therefore prefer defensive application logic with explicit handling for missing resources and failed operations.

Testing

I test important logic at the layer where it provides the most value.

This may include:

  • unit tests,
  • repository tests,
  • database tests,
  • ViewModel tests,
  • UI tests.

I particularly value regression tests for problems that have already occurred.

Once a bug is identified, a test can help prevent it from returning later.

Device Testing

An emulator is useful, but it does not represent every real device.

I consider testing across:

  • different screen sizes,
  • Android versions,
  • performance levels,
  • orientation changes,
  • lifecycle events.

Applications intended for real users need to survive conditions beyond the ideal development setup.

Logging

Useful logging helps diagnose issues that cannot be reproduced immediately.

I log enough information to understand important failures without exposing sensitive user or authentication data.

Production diagnostics should help answer:

  • what failed,
  • where,
  • under which application state.

Crash Prevention

Avoiding crashes is not only about try-catch blocks.

It begins with:

  • explicit state,
  • null safety,
  • validated input,
  • lifecycle-aware architecture,
  • safe persistence,
  • defensive external integrations.

Good architecture prevents many failure states before exception handling is required.

Dependency Management

Android applications often depend on many libraries.

I add dependencies deliberately.

Before introducing another library, I consider:

  • whether it solves a real problem,
  • maintenance,
  • application size,
  • compatibility,
  • security,
  • whether Android or Jetpack already provides the functionality.

A smaller dependency surface is generally easier to maintain.

Gradle

Gradle is part of the Android build system I work with for:

  • dependencies,
  • build variants,
  • version configuration,
  • plugins,
  • packaging.

I keep build configuration under version control and avoid relying unnecessarily on machine-specific setup.

The build should be reproducible outside the original development environment.

Build Variants

Where appropriate, I use different build configurations for environments such as:

  • development,
  • testing,
  • production.

This can help separate:

  • API endpoints,
  • logging behavior,
  • test advertising configuration,
  • feature flags.

Environment-specific configuration should be deliberate rather than implemented through last-minute manual source edits.

Release Engineering

An Android application is not finished when it runs successfully in the IDE.

Release work can include:

  • application versioning,
  • signing,
  • release builds,
  • shrinking and optimization,
  • store configuration,
  • testing production behavior.

I treat release configuration as part of the project rather than as an unrelated final step.

Google Play

For applications distributed through Google Play, development also includes understanding the publishing lifecycle.

This can involve:

  • application bundles,
  • release tracks,
  • testing releases,
  • billing configuration,
  • store requirements.

Production behavior needs to be tested with the same care as development builds.

Git and GitHub

I keep Android projects under Git version control.

This includes:

  • Kotlin source code,
  • Compose interfaces,
  • Gradle configuration,
  • localization,
  • database schemas,
  • application resources.

I use version control as a safety layer for:

  • feature development,
  • refactoring,
  • releases,
  • AI-assisted code changes.

Important changes should remain traceable and reversible.

AI-Assisted Development

I use AI-assisted development to accelerate Android engineering where it provides real value.

This can help with:

  • implementation,
  • refactoring,
  • debugging,
  • architecture exploration,
  • localization,
  • tests.

Generated code is still reviewed and tested.

Android has many lifecycle and platform-specific constraints that cannot safely be ignored simply because generated code compiles.

Maintainability

I design Android projects with the assumption that they will change.

New features may introduce:

  • additional screens,
  • new data sources,
  • billing,
  • localization,
  • background tasks,
  • new persistence requirements.

Clear boundaries between components make these changes much safer.

I prefer architecture that can grow gradually rather than requiring the entire application to be rewritten for every significant feature.

Avoiding Overengineering

A good Android architecture does not need the maximum possible number of layers.

Small utilities can remain small.

I introduce architectural complexity only when it solves a real problem.

The objective is:

  • clear state,
  • clear ownership,
  • clear data flow.

Not architectural ceremony.

Android Development in My Technology Stack

I commonly combine Android development with:

  • Kotlin,
  • Android Jetpack,
  • Jetpack Compose,
  • Material 3,
  • Room,
  • DataStore,
  • SQLite,
  • Kotlin Coroutines,
  • Kotlin Flow,
  • REST APIs,
  • JSON,
  • Google Play Billing,
  • Google Mobile Ads / AdMob,
  • Git and GitHub.

Together, these technologies provide the foundation for modern native Android applications.

Why I Build Native Android Applications

I use native Android development when an application benefits from direct integration with the Android platform, strong performance and access to the wider Android ecosystem.

Kotlin and Jetpack Compose provide a modern development model.

Jetpack provides established architecture.

Room and DataStore provide persistence.

REST APIs provide connectivity.

Google Play services provide distribution and monetization infrastructure.

The result is not simply an Android interface.

It is a complete software system that needs to remain reliable across changing devices, networks, lifecycle states and future application versions.

That is how I approach Android development: as long-term application engineering, not just mobile screen building.

Back to main page