Introduction

Android dominates the global smartphone market with over 70% market share. In Australia, the split is closer to 50/50 with iOS, but Android’s reach across diverse price points makes it essential for any mobile strategy.

Kotlin, officially supported by Google since 2017 and the preferred language since 2019, has transformed Android development. It eliminates Java’s verbosity while adding modern language features that prevent entire categories of bugs.

This guide takes you from zero to your first working Android app.

Setting Up Your Development Environment

Setting Up Your Development Environment Infographic

System Requirements

Android development works on Windows, macOS, and Linux. You need:

  • 8GB RAM minimum (16GB recommended)
  • 8GB disk space for Android Studio and SDK
  • 64-bit operating system
  • Hardware acceleration support (Intel HAXM or AMD equivalent)

Installing Android Studio

Download Android Studio from developer.android.com. As of January 2021, version 4.1 is current. The installer includes Android Studio and the Android SDK.

Run the installer and accept defaults. On first launch, the Setup Wizard downloads additional SDK components. This may take 15-30 minutes depending on your connection.

Configuring the SDK

Android Studio manages SDK versions through the SDK Manager (Tools > SDK Manager).

For development in 2021, install:

  • SDK Platforms: Android 11 (API 30), Android 10 (API 29)
  • SDK Tools: Android SDK Build-Tools, Android Emulator, Android SDK Platform-Tools

The SDK Manager shows which components are installed and available. Keep build tools updated for best compatibility.

Creating an Emulator

The AVD Manager (Tools > AVD Manager) creates virtual devices for testing.

Click “Create Virtual Device” and choose a phone model. Pixel 4 is a good choice—modern specs without being the absolute latest. Select a system image (Android 11 recommended) and finish creation.

Start the emulator to verify it works. First boot takes several minutes as the virtual device initialises.

Kotlin Lang

Kotlin Language Fundamentals Infographic uage Fundamentals

Why Kotlin

Kotlin addresses Java’s pain points while maintaining full interoperability. Key advantages:

  • Null safety: The compiler prevents null pointer exceptions
  • Conciseness: Less boilerplate than Java
  • Modern features: Extension functions, coroutines, data classes
  • Java interoperability: Call Java from Kotlin and vice versa

Variables and Types

Kotlin distinguishes between mutable (var) and immutable (val) variables:

val name = "Sydney"        // Immutable - cannot reassign
var population = 5312000   // Mutable - can reassign

population = 5400000       // Valid
// name = "Melbourne"      // Compiler error

Type inference determines types automatically, but you can be explicit:

val temperature: Double = 28.5
val cities: List<String> = listOf("Sydney", "Melbourne", "Brisbane")

Null Safety

Kotlin’s type system distinguishes nullable and non-nullable types:

var name: String = "Alice"   // Cannot be null
var nickname: String? = null // Can be null

// Safe call operator
val length = nickname?.length  // Returns null if nickname is null

// Elvis operator for defaults
val displayName = nickname ?: "Unknown"

// Not-null assertion (use sparingly)
val forcedLength = nickname!!.length  // Crashes if null

Functions

Kotlin functions are concise:

// Standard function
fun greet(name: String): String {
    return "Hello, $name!"
}

// Single-expression function
fun greet(name: String) = "Hello, $name!"

// Default parameters
fun greet(name: String, greeting: String = "Hello") = "$greeting, $name!"

// Named arguments
greet(name = "Alice", greeting = "G'day")

Classes and Data Classes

// Standard class
class Person(val name: String, var age: Int) {
    fun celebrateBirthday() {
        age++
    }
}

// Data class - generates equals, hashCode, toString, copy
data class User(
    val id: Int,
    val email: String,
    val name: String
)

val user = User(1, "[email protected]", "Alice")
val copy = user.copy(name = "Alicia")

Collections

// Lists
val fruits = listOf("Apple", "Banana", "Cherry")  // Immutable
val mutableFruits = mutableListOf("Apple")        // Mutable
mutableFruits.add("Banana")

// Maps
val scores = mapOf("Alice" to 95, "Bob" to 87)
val mutableScores = mutableMapOf<String, Int>()
mutableScores["Carol"] = 92

// Functional operations
val highScorers = scores
    .filter { it.value >= 90 }
    .map { it.key }

Extension Functions

Add methods to existing classes without inheritance:

fun String.addExclamation() = this + "!"

val excited = "Hello".addExclamation()  // "Hello!"

fun Int.isEven() = this % 2 == 0
val check = 4.isEven()  // true

Building Your Firs

Building Your First Android App Infographic t Android App

Creating a New Project

In Android Studio, select File > New > New Project. Choose “Empty Activity” template.

Configure your project:

  • Name: TaskTracker
  • Package name: com.example.tasktracker
  • Save location: Your preferred directory
  • Language: Kotlin
  • Minimum SDK: API 21 (Android 5.0, covers 95%+ of devices)

Click Finish. Android Studio creates the project and performs initial Gradle sync.

Project Structure

Key directories and files:

app/
  src/
    main/
      java/com/example/tasktracker/  # Kotlin code goes here
        MainActivity.kt
      res/
        layout/                       # XML layouts
          activity_main.xml
        values/                       # Resources
          colors.xml
          strings.xml
          themes.xml
      AndroidManifest.xml             # App configuration
  build.gradle                        # App-level dependencies
build.gradle                          # Project-level configuration

Understanding Activities

Activities represent screens in your app. Each activity has a lifecycle:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // Initialize UI
    }

    override fun onStart() {
        super.onStart()
        // Activity becoming visible
    }

    override fun onResume() {
        super.onResume()
        // Activity in foreground, interactive
    }

    override fun onPause() {
        super.onPause()
        // Activity losing focus
    }

    override fun onStop() {
        super.onStop()
        // Activity no longer visible
    }

    override fun onDestroy() {
        super.onDestroy()
        // Activity being destroyed
    }
}

Layouts with XML

Android uses XML to define user interfaces. Open activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <TextView
        android:id="@+id/titleText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="My Tasks"
        android:textSize="24sp"
        android:textStyle="bold" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/taskRecyclerView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:layout_marginTop="16dp" />

    <Button
        android:id="@+id/addButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Add Task" />

</LinearLayout>

View Binding

View binding generates classes to access views safely. Enable it in app/build.gradle:

android {
    buildFeatures {
        viewBinding true
    }
}

Sync Gradle, then use binding in your activity:

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.addButton.setOnClickListener {
            showAddTaskDialog()
        }
    }
}

Building the Task List

Create a data class for tasks:

data class Task(
    val id: Int,
    val title: String,
    var isComplete: Boolean = false
)

Create an adapter for RecyclerView:

class TaskAdapter(
    private val tasks: MutableList<Task>,
    private val onTaskClick: (Task) -> Unit,
    private val onDeleteClick: (Task) -> Unit
) : RecyclerView.Adapter<TaskAdapter.TaskViewHolder>() {

    class TaskViewHolder(val binding: ItemTaskBinding) :
        RecyclerView.ViewHolder(binding.root)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TaskViewHolder {
        val binding = ItemTaskBinding.inflate(
            LayoutInflater.from(parent.context), parent, false
        )
        return TaskViewHolder(binding)
    }

    override fun onBindViewHolder(holder: TaskViewHolder, position: Int) {
        val task = tasks[position]
        holder.binding.apply {
            taskTitle.text = task.title
            taskCheckbox.isChecked = task.isComplete

            root.setOnClickListener { onTaskClick(task) }
            deleteButton.setOnClickListener { onDeleteClick(task) }

            taskCheckbox.setOnCheckedChangeListener { _, isChecked ->
                task.isComplete = isChecked
            }
        }
    }

    override fun getItemCount() = tasks.size

    fun addTask(task: Task) {
        tasks.add(task)
        notifyItemInserted(tasks.size - 1)
    }

    fun removeTask(task: Task) {
        val position = tasks.indexOf(task)
        if (position >= 0) {
            tasks.removeAt(position)
            notifyItemRemoved(position)
        }
    }
}

Create item_task.xml for individual task rows:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:padding="12dp"
    android:gravity="center_vertical">

    <CheckBox
        android:id="@+id/taskCheckbox"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/taskTitle"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:textSize="16sp"
        android:layout_marginStart="8dp" />

    <ImageButton
        android:id="@+id/deleteButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@android:drawable/ic_delete"
        android:background="?attr/selectableItemBackgroundBorderless"
        android:contentDescription="Delete task" />

</LinearLayout>

Completing the Activity

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    private lateinit var adapter: TaskAdapter
    private val tasks = mutableListOf<Task>()
    private var nextId = 1

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        setupRecyclerView()
        setupAddButton()
    }

    private fun setupRecyclerView() {
        adapter = TaskAdapter(
            tasks,
            onTaskClick = { task ->
                Toast.makeText(this, "Clicked: ${task.title}", Toast.LENGTH_SHORT).show()
            },
            onDeleteClick = { task ->
                adapter.removeTask(task)
            }
        )

        binding.taskRecyclerView.layoutManager = LinearLayoutManager(this)
        binding.taskRecyclerView.adapter = adapter
    }

    private fun setupAddButton() {
        binding.addButton.setOnClickListener {
            showAddTaskDialog()
        }
    }

    private fun showAddTaskDialog() {
        val editText = EditText(this).apply {
            hint = "Task name"
        }

        AlertDialog.Builder(this)
            .setTitle("New Task")
            .setView(editText)
            .setPositiveButton("Add") { _, _ ->
                val title = editText.text.toString()
                if (title.isNotBlank()) {
                    val task = Task(nextId++, title)
                    adapter.addTask(task)
                }
            }
            .setNegativeButton("Cancel", null)
            .show()
    }
}

Running Your App

Click the Run button (green play arrow) or press Shift+F10. Select your emulator or connected device. Gradle builds the app and installs it.

Your task tracker app should launch. Add tasks, mark them complete, and delete them.

Essential Android Concepts

Intents

Intents enable communication between components:

// Start a new activity
val intent = Intent(this, DetailActivity::class.java)
intent.putExtra("TASK_ID", task.id)
startActivity(intent)

// Open a URL
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://example.com"))
startActivity(browserIntent)

// Share content
val shareIntent = Intent(Intent.ACTION_SEND).apply {
    type = "text/plain"
    putExtra(Intent.EXTRA_TEXT, "Check out this task: ${task.title}")
}
startActivity(Intent.createChooser(shareIntent, "Share via"))

Fragments

Fragments are reusable UI components that can be combined within activities:

class TaskListFragment : Fragment() {

    private var _binding: FragmentTaskListBinding? = null
    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentTaskListBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

Resources

Android separates code from resources for localisation and configuration:

{/* res/values/strings.xml */}
<resources>
    <string name="app_name">TaskTracker</string>
    <string name="add_task">Add Task</string>
    <string name="task_placeholder">Enter task name</string>
</resources>

Reference strings in layouts:

<Button android:text="@string/add_task" />

Reference in code:

val text = getString(R.string.add_task)

Permissions

Android requires explicit permission requests for sensitive operations:

{/* AndroidManifest.xml */}
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />

Runtime permissions (for dangerous permissions):

private val requestPermissionLauncher = registerForActivityResult(
    ActivityResultContracts.RequestPermission()
) { isGranted ->
    if (isGranted) {
        // Permission granted, proceed
    } else {
        // Explain why permission is needed
    }
}

// Request permission
requestPermissionLauncher.launch(Manifest.permission.CAMERA)

Best Practices

Follow Material Design

Google’s Material Design provides consistent, intuitive interfaces. Use Material Components library for buttons, cards, text fields, and more.

Handle Configuration Changes

Screen rotation destroys and recreates activities. Use ViewModel to preserve data:

class TaskViewModel : ViewModel() {
    private val _tasks = MutableLiveData<List<Task>>()
    val tasks: LiveData<List<Task>> = _tasks

    fun addTask(task: Task) {
        val current = _tasks.value.orEmpty().toMutableList()
        current.add(task)
        _tasks.value = current
    }
}

Test on Multiple Devices

Screen sizes, API levels, and manufacturer customisations vary widely. Test on emulators with different configurations and physical devices when possible.

Optimise for Battery

Avoid unnecessary background work. Use WorkManager for deferred tasks. Respect Doze mode and App Standby.

Next Steps

This guide establishes the foundation. Continue learning with:

  • Android Jetpack: Google’s suite of libraries for modern Android development
  • Navigation Component: Type-safe navigation between screens
  • Room Database: SQLite abstraction for local data persistence
  • Retrofit: HTTP client for API communication
  • Coroutines: Asynchronous programming without callbacks

Build projects. Explore the official Android documentation. Join Android developer communities.

The Android ecosystem evolves rapidly, but the fundamentals remain stable. Master the basics, then layer on modern libraries and patterns. Your first app is just the beginning.