Android Development Fundamentals
Android development is the practice of building applications for devices running Google's Android operating system. It covers the platform's component model, lifecycle management, UI framework, data persistence, and distribution through the Play Store.
itMobile and client application development | OpenSkills.info
Intro
Android Development Fundamentals
Android development means building software for a platform that controls your process, components, resources, permissions, and device integration. Your code does not own one permanent process from start to finish. The system starts components when needed and can reclaim the process later.
That platform relationship is the first mental model to learn. A reliable Android app cooperates with lifecycle changes, reconstructs disposable user interface state, protects persistent data, and treats device resources as constrained.
Modern Android development uses Kotlin, Android Studio, Gradle, Jetpack libraries, and Jetpack Compose. Google recommends Kotlin for new Android apps. Compose is the recommended modern user interface toolkit.
Android is a good fit when you need a native application across phones, tablets, foldables, watches, televisions, cars, or other Android devices. It is not automatically the best fit for every client. A web application may be enough when installation, offline behavior, platform integration, and native distribution add little value.
Think in system entry points
An Android app has four framework component types:
- An activity is an entry point for user interaction.
- A service performs work without a user interface.
- A broadcast receiver responds to broadcast messages.
- A content provider exposes structured data through a controlled interface.
The system can enter your app through any declared component. There is no required application-level main function that owns the whole lifetime.
Most current applications use one activity as a host for several screens. Compose renders those screens, while a navigation library tracks destinations and back-stack state. Other component types remain important when the app integrates with system or cross-app behavior.
Do not create a service for ordinary asynchronous work. A coroutine can handle work tied to an active screen or process. WorkManager handles deferrable work that must survive the visible screen. A foreground service is reserved for suitable user-noticeable work and carries stricter platform rules.
The manifest tells the system what exists
Every app project includes an AndroidManifest.xml file. The manifest declares essential information to the build tools, Android, and Google Play.
It can declare:
- Application components and their classes
- Intent filters that describe supported entry points
- Permissions needed for protected data or capabilities
- Permissions required to call exported components
- Required hardware and software features
The manifest is a security and compatibility boundary. An exported component is available outside your application under its declared rules. A required feature can remove incompatible devices from Play distribution. A permission declaration expands what the app may ask the user or system to grant.
Gradle can merge several manifests from the app, build variant, and libraries. Inspect the merged manifest when behavior differs from the source file you opened.
The activity lifecycle is a state machine
The system moves an activity through lifecycle states and invokes callbacks around those transitions.
| Callback | Working meaning |
|---|---|
onCreate | Initialize the activity instance and compose the initial interface |
onStart | The activity becomes visible |
onResume | The activity becomes interactive in the foreground |
onPause | The activity is losing foreground focus |
onStop | The activity is no longer visible |
onDestroy | The activity instance is being destroyed |
Treat callbacks as state transitions, not a script that always runs in one fixed sequence. A configuration change can recreate the activity. The process can disappear while the app is not visible. Returning users expect their meaningful state to be restored.
Keep screen state in a ViewModel when it should survive activity recreation. Persist small restorable values through saved state when process recreation matters. Store durable application data in a database, DataStore, or files. These mechanisms solve different lifetimes.
Compose renders state
Jetpack Compose is declarative. A composable function describes the user interface for its current inputs. When observed state changes, Compose can run affected composables again and apply the required updates.
@Composable
fun Counter(
count: Int,
onIncrement: () -> Unit,
) {
Button(onClick = onIncrement) {
Text(text = "Count: " + count)
}
}
This function does not search for an existing button and mutate its label. It describes what the button should be now.
State is any value that can change over time. Compose observes supported state holders and schedules recomposition after changes. remember keeps an object across recompositions while that composable remains in the composition. It does not provide durable storage.
Prefer state hoisting for reusable components. Pass state down and send user actions up. A stateless composable is easier to preview, test, and reuse.
ViewModel state -> screen -> reusable component
ViewModel action <- screen <- user event
Recomposition can occur often, in a different order, or be skipped. Keep composables free of unguarded side effects. Use Compose effect APIs when work must follow the composition lifecycle.
Architecture separates policy from presentation
The recommended Android architecture separates at least two layers:
- The user interface layer displays application data and handles user interaction.
- The data layer contains application data and most business logic.
A repository exposes data to the rest of the app. It coordinates one or more data sources, such as a database, network client, sensor, or DataStore. The user interface layer should not reach directly into those data sources.
A ViewModel is a screen-level state holder. It receives actions, calls the data layer, and exposes user interface state. Modern guidance favors unidirectional data flow:
repository -> ViewModel -> user interface state -> Compose
repository <- ViewModel <- user action <- Compose
Keep one source of truth for each piece of data. Duplicate mutable copies drift. Derived presentation values can be calculated from source data.
Use a domain layer only when shared or complex business logic benefits from separate use cases. Small apps do not need every possible layer.
Resources adapt without branching everywhere
Android resources keep strings, images, colors, dimensions, and other values separate from code. Build tools generate identifiers in the R class.
The resource system selects alternatives for the current device configuration. You can provide different resources for languages, screen sizes, densities, orientations, and night mode while referring to one resource identifier.
Always provide suitable defaults. Use configuration qualifiers for alternatives. Do not scatter device checks through application logic when resource selection already models the variation.
Resource separation supports localization and adaptive layouts. It does not make an interface adaptive by itself. Your layout still needs to respond to available space and input modes.
Navigation models destinations and history
Navigation should represent user-visible destinations, their arguments, and the back stack. Pass stable identifiers between destinations rather than large mutable objects. Load current data from the source of truth at the destination.
A deep link is an external entry into a specific destination. Treat every deep link argument as untrusted input. Verify authorization after navigation when the destination accesses protected data.
Navigation state is not business state. The back stack answers where the user is. A repository answers what application data exists.
Store data by lifetime and sharing needs
Android provides several storage boundaries:
| Need | Starting point |
|---|---|
| Structured private data | Room database |
| Small preference-like data | DataStore |
| App-only files | App-specific internal storage |
| User-visible shared media | MediaStore or the appropriate system picker |
| Temporary recomposition state | Compose remember |
| Screen state across recreation | ViewModel |
Internal app storage is private to the app by default. Shared storage is intended for content that other apps or users need to access. Sensitive data does not belong in broadly accessible storage.
Caching and persistence are different decisions. A cache can be discarded and rebuilt. Persistent data must retain the user's committed intent.
Permissions are revocable capabilities
Declare only the permissions your feature needs. Prefer system APIs that complete the task without broad access.
For a runtime permission:
- Wait until the user invokes the feature.
- Check whether the permission is already granted.
- Explain the need when the platform indicates that a rationale is appropriate.
- Request the permission through the current activity result API.
- Handle denial without breaking unrelated parts of the app.
- Check again whenever performing the protected operation.
A permission can be denied or revoked. Code that assumes permanent access is incorrect.
Permission approval does not replace application authorization. A camera permission allows access to a device capability. It does not prove that a user may upload a photo to a particular account.
Background work must match user intent
Choose an execution mechanism from the required lifetime:
- Use a coroutine or another asynchronous mechanism for work that can stop with its owning lifecycle.
- Use WorkManager for deferrable work that must continue after the visible screen or process.
- Use a suitable foreground service only for supported user-noticeable work that must continue immediately.
- Use a specialized platform API when one matches the task.
Never block the main thread with network, storage, or expensive computation. The main thread handles user input and rendering. Blocking it causes jank and can lead to an application-not-responding failure.
Testing follows boundaries
Local tests run on the development machine or a server. They are usually fast and isolated. Instrumented tests run on a physical or emulated Android device and can exercise framework and user interface behavior.
Test the data layer and ViewModel with controlled dependencies. Test important Compose behavior through semantics, because the semantics tree is also what accessibility services use to understand the interface.
Use fakes when they provide a realistic, controllable implementation. Reserve broad end-to-end tests for critical flows. A test portfolio needs fast feedback and enough device coverage for platform-dependent behavior.
Accessibility is part of the component contract
Compose and Material components include useful accessibility semantics. Custom components need equivalent meaning and actions.
Label meaningful controls. Do not rely on color alone. Preserve readable contrast and touch targets. Support font scaling and changing window sizes. Test with TalkBack, Switch Access, accessibility inspection tools, and automated Compose tests.
The semantics tree connects accessibility and testing. A control with unclear semantics is harder for both a screen reader and a stable user interface test to identify.
Security starts with the sandbox
Android gives each app a separate identity and sandbox. The sandbox limits default access between apps. Your design must preserve that boundary.
- Keep sensitive files in internal storage.
- Export only components that need external access.
- Protect exported interfaces with appropriate permissions and validation.
- Minimize runtime permissions.
- Use secure network protocols.
- Treat intents, deep links, files, and network responses as untrusted input.
- Keep signing keys and service credentials out of the application package.
An app package can be inspected. A hard-coded secret is not secret.
Build, measure, and release deliberately
Gradle and the Android Gradle plugin build your source, resources, manifest, and dependencies into installable or publishable artifacts.
A build type represents a stage such as debug or release. A product flavor represents a product variation. Their cross-product creates build variants. Keep environment policy explicit instead of hiding it in source edits.
Measure performance on a production-like build. Debug instrumentation can distort results. Watch startup, rendering, main-thread work, memory, battery, crashes, and application-not-responding failures.
Google Play accepts an Android App Bundle and generates optimized APKs for supported device configurations. A release still needs versioning, signing, testing, store metadata, policy compliance, and monitoring after rollout.
A practical learning path
- Learn enough Kotlin to read nullability, functions, data classes, coroutines, and flows.
- Create a small Compose screen and hoist its state.
- Trace activity recreation and process-aware state restoration.
- Add navigation between two destinations.
- Move screen logic into a
ViewModel. - Put durable data behind a repository.
- Add one permission-gated feature with a denial path.
- Schedule one appropriate piece of persistent work.
- Test state logic and a critical Compose interaction.
- Review accessibility, security, release configuration, and measured performance.
Where this skill leads
Relevant careers
See how this topic contributes to broader role-level skill maps.
Sources
- https://developer.android.com/courses/android-basics-compose/course
Supports
- Official beginner progression through Kotlin, Android Studio, Compose, state, navigation, architecture, data, background work, testing, and adaptive interfaces
- Compose as the recommended toolkit for adaptive Android user interfaces
- https://developer.android.com/
Supports
- Official Android development documentation, tools, platform guidance, and learning paths
- Compose-first getting-started direction and multi-device scope
- https://developer.android.com/kotlin/first
Supports
- Kotlin as the recommended starting language for new Android apps
- Kotlin-first tools, samples, documentation, training, and Jetpack guidance
- Continued Java interoperability and support
- https://developer.android.com/guide/components/fundamentals
Supports
- Android application package and Android App Bundle distinction
- Per-app process, identity, sandbox, and least-privilege model
- Activities, services, broadcast receivers, and content providers as component types
- Components as system entry points with distinct lifecycles
- Manifest and resource roles
- https://developer.android.com/guide/topics/manifest/manifest-intro
Supports
- Mandatory AndroidManifest.xml file
- Component, permission, intent filter, and required feature declarations
- Component declaration and external exposure
- Build-time manifest merging
- https://developer.android.com/guide/components/activities/activity-lifecycle
Supports
- Activity lifecycle states and callbacks
- Initialization in onCreate
- Visibility and foreground transitions
- Configuration-driven recreation and state restoration responsibilities
- https://developer.android.com/develop/ui/compose/mental-model
Supports
- Compose as a declarative user interface toolkit
- Interface description instead of imperative view mutation
- Recomposition behavior and composable execution properties
- Side-effect constraints in composable functions
- https://developer.android.com/develop/ui/compose/state
Supports
- State as changing application values
- Observable state and recomposition
- remember and saveable state boundaries
- State hoisting, values flowing down, and actions flowing up
- Stateless composable reuse and testing
- https://developer.android.com/topic/architecture/recommendations
Supports
- User interface and data layers
- Repository boundary between user interface and data sources
- Unidirectional data flow and single source of truth
- Screen-level ViewModel state and lifecycle-aware collection
- Optional domain layer for shared or complex business logic
- Testing recommendations and fake implementations
- https://developer.android.com/topic/architecture/data-layer
Supports
- Repositories coordinating data sources
- Repository source-of-truth responsibilities
- Main-safe data-layer operations
- WorkManager for business operations that must survive process death
- https://developer.android.com/topic/libraries/architecture/viewmodel
Supports
- ViewModel as a screen-level state holder
- State retention across configuration changes
- User interface business logic and data-layer delegation
- https://developer.android.com/guide/topics/resources/providing-resources
Supports
- External resource storage and generated resource identifiers
- Default and configuration-specific resource directories
- Runtime selection of best-matching resources
- Language, density, and other configuration qualifiers
- https://developer.android.com/guide/navigation
Supports
- Navigation guidance, destinations, back-stack state, and deep links
- Current Navigation 3 learning path
- https://developer.android.com/training/data-storage
Supports
- App-specific storage, shared storage, preferences, and database choices
- Internal storage for sensitive app-only files
- Room as the structured private database boundary
- MediaStore and system storage interfaces
- https://developer.android.com/topic/libraries/architecture/datastore
Supports
- DataStore as a durable asynchronous storage API for small datasets
- Preference-like key-value data and typed data boundaries
- https://developer.android.com/training/data-storage/room
Supports
- Room as the recommended abstraction over SQLite for structured local data
- Database, entity, and data access object responsibilities
- https://developer.android.com/training/permissions/requesting
Supports
- Runtime permission request workflow
- Point-of-use permission checks
- Rationale, approval, and denial handling
- Rechecking before protected operations
- https://developer.android.com/privacy-and-security/minimize-permission-requests
Supports
- Minimum-permission design
- Permission-free API alternatives
- User privacy and flow costs of permission requests
- https://developer.android.com/develop/background-work/background-tasks
Supports
- Asynchronous work, task scheduling, and foreground service categories
- WorkManager as the usual persistent task scheduler
- Lifecycle limits of ordinary asynchronous work
- Main-thread blocking and application-not-responding risk
- Specialized APIs as alternatives to foreground services
- https://developer.android.com/develop/background-work/background-tasks/persistent
Supports
- Persistent work across app restarts and device reboots
- Immediate, long-running, and deferrable work categories
- Work constraints, unique work, and managed rescheduling
- https://developer.android.com/training/testing/fundamentals
Supports
- Testing benefits and repeatability
- Local tests on a development machine or server
- Instrumented tests on physical or emulated Android devices
- Unit, integration, and end-to-end test scopes
- https://developer.android.com/develop/ui/compose/accessibility/semantics
Supports
- Semantics as user interface meaning and actions
- Semantics tree use by accessibility services and testing
- Built-in semantics and manual semantics for custom components
- https://developer.android.com/guide/topics/ui/accessibility/principles
Supports
- Labels and accessibility actions
- Built-in Compose accessibility behavior
- Cues beyond color and accessible media
- TalkBack and Switch Access roles
- https://developer.android.com/guide/topics/ui/accessibility/testing
Supports
- Manual accessibility service testing
- Accessibility analysis tools
- Automated Compose testing
- https://developer.android.com/privacy-and-security/security-tips
Supports
- Internal storage privacy
- Exported content provider and component controls
- Minimum permission guidance
- HTTPS for secure network traffic
- Input validation and credential handling
- https://developer.android.com/topic/performance/measuring-performance
Supports
- Tracing and benchmarking
- Production-like performance measurement
- Severe debug-build performance distortion
- Startup, rendering, application-not-responding, memory, and battery measurement areas
- https://developer.android.com/build
Supports
- Gradle and Android Gradle plugin responsibilities
- Build types, product flavors, and build variants
- Source sets and manifest merging
- Debug and release signing
- Shrinking and Android App Bundle packaging
- https://developer.android.com/guide/app-bundle/app-bundle-format
Supports
- Android App Bundle as a publishing format
- Google Play generation of device-specific APK artifacts
- https://developer.android.com/studio/publish/
Supports
- Release configuration, signing, testing, and distribution
- Android App Bundle requirement for new Google Play apps
- Marketplace and direct distribution choices
- Store listing and release preparation
