Android Development: Getting Started with Kotlin
Android development is the process of building applications for the world’s most popular mobile operating system. Google’s official language for Android is Kotlin — a modern, concise, and safe programming language that runs on the JVM and interoperates fully with Java.
This guide walks you through setting up Android Studio, understanding the Android project structure, and building your first app with both the traditional View system and Jetpack Compose.
Setting Up Android Studio
Android Studio is the official IDE for Android development. It is built on IntelliJ IDEA and includes everything you need: code editor, debugger, emulator, layout editor, and build tools.
Download Android Studio from the official website. The installer handles SDK setup, but you may need to install additional SDK versions later through the SDK Manager (Tools > SDK Manager). A good rule is to target the latest stable SDK version while setting your minimum SDK to a version that covers at least 95% of active devices.
Create a new project by selecting File > New > New Project. Choose a template — Empty Views Activity for traditional development or Empty Compose Activity for Jetpack Compose. Name your project, set the package name (typically com.example.yourapp), and choose the minimum SDK.
Gradle Build System
Android projects use Gradle for building, testing, and dependency management. The build.gradle.kts file at the module level declares dependencies, SDK versions, and build configurations. Kotlin DSL (.kts) is now the standard over Groovy. Key configuration blocks include plugins, android, and dependencies. Use version catalogs (libs.versions.toml) to manage dependency versions in a central file for multi-module projects.
Kotlin Essentials
Kotlin is designed to be pragmatic and concise while maintaining full Java interoperability.
Variables
var name = "Alice" // Mutable
val age = 30 // ImmutableKotlin uses type inference. You can specify the type explicitly when needed: val name: String = "Alice".
Null Safety
Null safety is built into the type system:
var maybeName: String? = null
val length = maybeName?.length ?: 0The ? operator marks a nullable type. The ?. safe call operator returns null if the value is null. The ?: Elvis operator provides a default.
Functions
fun greet(name: String): String {
return "Hello, $name!"
---
// Single-expression function
fun greet(name: String) = "Hello, $name!"Functions can have default parameter values, named arguments, and extension functions — features that reduce boilerplate significantly compared to Java.
Data Classes
Data classes automatically generate equals(), hashCode(), toString(), and copy():
data class User(val name: String, val age: Int)Use data classes for model objects. They eliminate an enormous amount of boilerplate compared to Java.
Android Project Structure
An Android project has several key components:
app/src/main/java/— Your Kotlin source filesapp/src/main/res/— Resources (layouts, strings, drawables)app/build.gradle.kts— Module-level build configurationAndroidManifest.xml— App declaration, permissions, components
The manifest declares activities, services, broadcast receivers, and content providers. Every activity must be registered here:
<activity android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>Resource Management
Android resources are organized by type in the res/ directory: layout/ for UI layouts, values/ for strings, colors, and themes, drawable/ for images and vector graphics, mipmap/ for launcher icons, and raw/ for arbitrary files. Use resource qualifiers like -night, -land, and -sw600dp to provide alternative resources for different device configurations.
Activities and Fragments
Activities
An activity represents a single screen with a user interface. It is the entry point for user interaction.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
---The activity lifecycle mirrors the screen’s visibility state:
onCreate()— Activity is created (set up views here)onStart()— Activity becomes visibleonResume()— Activity gains user focusonPause()— Activity partially obscured (e.g., a dialog appears)onStop()— Activity no longer visibleonDestroy()— Activity is destroyed
Saving instance state is critical for handling configuration changes like screen rotations. Override onSaveInstanceState() to preserve UI state, and restore it in onCreate() from the savedInstanceState bundle.
Fragments
Fragments represent reusable portions of the UI within an activity. They have their own lifecycle tied to the host activity.
class HomeFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_home, container, false)
}
---The Fragment lifecycle is more complex than the Activity lifecycle and includes onAttach(), onCreateView(), onViewCreated(), onDestroyView(), and onDetach(). The Navigation component simplifies fragment transactions by managing the back stack automatically.
Jetpack Compose
Jetpack Compose is Android’s modern declarative UI toolkit. Instead of XML layouts and imperative view manipulation, you describe your UI with composable functions.
@Composable
fun Greeting(name: String) {
Text(text = "Hello, $name!")
---
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Column {
Text("Count: $count")
Button(onClick = { count++ }) {
Text("Increment")
}
}
---Compose simplifies UI development dramatically. No more findViewById, no more adapter boilerplate for lists, no more XML layout files. Everything is Kotlin.
State Management
Compose manages state through observable state holders:
mutableStateOf()— Local component stateMutableStateFlow/StateFlow— ViewModel stateremember— Retains state across recompositionsrememberSaveable— Retains state across configuration changes
class MainViewModel : ViewModel() {
private val _uiState = MutableStateFlow(UiState())
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
---Material Design 3
Compose fully supports Material Design 3 with dynamic theming. Use MaterialTheme(colorScheme = dynamicLightColorScheme(context)) to generate a color scheme from the user’s wallpaper on Android 12+. The Material3 library provides updated components like NavigationBar, NavigationDrawer, TopAppBar, and Card with modern styling.
Building and Running
Android Studio’s emulator lets you test on virtual devices. Create a virtual device through Tools > AVD Manager. Choose a device definition (Pixel 8 is a good default) and a system image.
Run the app by selecting a device or emulator from the run configuration menu and clicking the green play button. Gradle builds the APK, installs it on the target, and launches the activity.
App Bundle vs APK
Use the Android App Bundle (.aab) format for publishing on Google Play. It defers APK generation to Google Play servers, delivering only the resources and code needed for the user’s specific device configuration. This reduces download sizes by 15-30% on average compared to a universal APK.
Logging
Use Log.d(), Log.i(), Log.w(), and Log.e() for debug, info, warning, and error messages respectively. View logs in the Logcat tool window at the bottom of Android Studio.
Log.d("MainActivity", "User clicked the button")Filter by tag to focus on your app’s logs. Logcat also shows system logs, which helps diagnose permission issues, ANRs, and other system-level problems.
Android Architecture Components
Google recommends the MVVM architecture using the android architecture components. ViewModel holds UI state and survives configuration changes. LiveData or StateFlow exposes data reactively. Room provides an abstraction layer over SQLite with compile-time query verification. Navigation Component manages fragment transactions and deep linking. WorkManager schedules deferrable background tasks. These components work together to create a robust, testable application architecture that follows the separation of concerns principle.
Conclusion
Android development with Kotlin is productive and enjoyable. Start with simple apps, get comfortable with the activity lifecycle and Compose, then explore more advanced topics like Room (local database), Retrofit (networking), and WorkManager (background tasks). Google’s documentation and codelabs are excellent learning resources.
FAQ
How do I choose between Views and Compose for a new project?
Start with Compose for new projects unless you need libraries that lack Compose support. Compose reduces boilerplate by 40-60%, has a gentler learning curve for new developers, and is the direction Google is investing in. Use Views for projects that must support older devices or integrate with legacy codebases.
What is the best architecture for an Android app?
Google recommends the MVVM pattern with a single-activity architecture. Use ViewModel for UI state, Repository for data access, Room for local persistence, and Retrofit for networking. The androidx.lifecycle and androidx.navigation libraries provide first-class support for this pattern.
How do I handle background tasks in Android?
Use WorkManager for deferrable, reliable background work like syncing data or uploading logs. Use Foreground Services for user-visible tasks like music playback. Use AlarmManager for time-sensitive tasks that must execute at exact times. Avoid deprecated services like IntentService.
How can I optimize my Android app’s performance?
Profile with Android Studio’s CPU, memory, and network profilers. Optimize layouts by reducing nesting with ConstraintLayout or Compose. Use lazy loading for lists with RecyclerView or LazyColumn. Minimize work on the main thread by offloading to coroutines. Use App Startup for initializing libraries efficiently.
What testing strategy should I use for Android apps?
Write unit tests with JUnit and Mockito for ViewModels and repositories. Use Compose UI tests or Espresso for UI testing. Use Robolectric for JVM-based instrumented tests without an emulator. Run UI tests on Firebase Test Lab for device coverage. Aim for 70-80% test coverage on business logic and critical UI flows.
For a comprehensive overview, read our article on App Store Google Play Guide.
For a comprehensive overview, read our article on Flutter App Lifecycle.