Android Development

Introduction to Android Development

Android development involves creating applications for the Android operating system. This guide covers essential concepts, tools, and best practices for building Android apps.

Key Components:

  • Activities - App screens and UI
  • Fragments - Reusable UI components
  • Services - Background operations
  • Broadcast Receivers - System events
  • Content Providers - Data management
  • Intents - Component communication

Development Setup

Essential Tools

  • Android Studio - Official IDE
  • Java Development Kit (JDK)
  • Android SDK
  • Gradle - Build system
  • Android Virtual Device (AVD)

Project Structure

// build.gradle (Project)
buildscript {
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:7.0.4'
        classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10'
    }
}

// build.gradle (Module)
plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-kapt'
}

android {
    compileSdk 33

    defaultConfig {
        applicationId "com.example.myapp"
        minSdk 21
        targetSdk 33
        versionCode 1
        versionName "1.0"
    }

    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile(
                'proguard-android-optimize.txt'
            ), 'proguard-rules.pro'
        }
    }

    buildFeatures {
        viewBinding true
    }
}

dependencies {
    // Android core libraries
    implementation 'androidx.core:core-ktx:1.7.0'
    implementation 'androidx.appcompat:appcompat:1.4.1'
    implementation 'com.google.android.material:material:1.5.0'

    // Architecture components
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1'
    implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1'
    implementation 'androidx.room:room-runtime:2.4.2'
    kapt 'androidx.room:room-compiler:2.4.2'

    // Networking
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    implementation 'com.squareup.okhttp3:logging-interceptor:4.9.0'

    // Testing
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

Android Basics

Activity Example

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    private val viewModel: MainViewModel by viewModels()

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

        setupUI()
        observeViewModel()
    }

    private fun setupUI() {
        binding.buttonLogin.setOnClickListener {
            val username = binding.editTextUsername.text.toString()
            val password = binding.editTextPassword.text.toString()
            viewModel.login(username, password)
        }
    }

    private fun observeViewModel() {
        viewModel.loginResult.observe(this) { result ->
            when (result) {
                is Result.Success -> {
                    // Navigate to main screen
                    startActivity(Intent(this, HomeActivity::class.java))
                    finish()
                }
                is Result.Error -> {
                    // Show error message
                    showError(result.message)
                }
                is Result.Loading -> {
                    // Show loading indicator
                    showLoading()
                }
            }
        }
    }

    private fun showError(message: String) {
        MaterialAlertDialogBuilder(this)
            .setTitle("Error")
            .setMessage(message)
            .setPositiveButton("OK", null)
            .show()
    }

    private fun showLoading() {
        binding.progressBar.isVisible = true
        binding.buttonLogin.isEnabled = false
    }
}

UI Development

Layout Example

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp">

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/usernameLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="32dp"
        android:hint="Username"
        app:layout_constraintTop_toTopOf="parent">

        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/editTextUsername"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType="text" />

    </com.google.android.material.textfield.TextInputLayout>

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/passwordLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:hint="Password"
        app:layout_constraintTop_toBottomOf="@id/usernameLayout"
        app:passwordToggleEnabled="true">

        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/editTextPassword"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType="textPassword" />

    </com.google.android.material.textfield.TextInputLayout>

    <com.google.android.material.button.MaterialButton
        android:id="@+id/buttonLogin"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        android:text="Login"
        app:layout_constraintTop_toBottomOf="@id/passwordLayout" />

    <ProgressBar
        android:id="@+id/progressBar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:visibility="gone"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

App Architecture

MVVM Architecture Example

// Data class
data class User(
    val id: String,
    val username: String,
    val email: String
)

// Repository
class UserRepository @Inject constructor(
    private val api: ApiService,
    private val userDao: UserDao
) {
    suspend fun login(username: String, password: String): Result {
        return try {
            val response = api.login(LoginRequest(username, password))
            if (response.isSuccessful) {
                val user = response.body()!!
                userDao.insertUser(user)
                Result.Success(user)
            } else {
                Result.Error("Login failed")
            }
        } catch (e: Exception) {
            Result.Error(e.message ?: "Unknown error")
        }
    }

    fun getUser(): Flow = userDao.getUser()
}

// ViewModel
@HiltViewModel
class MainViewModel @Inject constructor(
    private val repository: UserRepository
) : ViewModel() {
    
    private val _loginResult = MutableLiveData>()
    val loginResult: LiveData> = _loginResult

    fun login(username: String, password: String) {
        viewModelScope.launch {
            _loginResult.value = Result.Loading
            _loginResult.value = repository.login(username, password)
        }
    }
}

Data Management

Room Database Example

// Entity
@Entity(tableName = "users")
data class UserEntity(
    @PrimaryKey val id: String,
    val username: String,
    val email: String,
    val createdAt: Long = System.currentTimeMillis()
)

// DAO (Data Access Object)
@Dao
interface UserDao {
    @Query("SELECT * FROM users WHERE id = :userId")
    suspend fun getUserById(userId: String): UserEntity?

    @Query("SELECT * FROM users")
    fun getAllUsers(): Flow>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertUser(user: UserEntity)

    @Delete
    suspend fun deleteUser(user: UserEntity)
}

// Database
@Database(
    entities = [UserEntity::class],
    version = 1
)
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao

    companion object {
        @Volatile
        private var instance: AppDatabase? = null

        fun getInstance(context: Context): AppDatabase {
            return instance ?: synchronized(this) {
                instance ?: buildDatabase(context).also {
                    instance = it
                }
            }
        }

        private fun buildDatabase(context: Context): AppDatabase {
            return Room.databaseBuilder(
                context,
                AppDatabase::class.java,
                "app.db"
            ).build()
        }
    }
}

Activity Lifecycle

Lifecycle States:

  • onCreate() - Activity is created
  • onStart() - Activity becomes visible
  • onResume() - Activity can interact with user
  • onPause() - Activity loses focus
  • onStop() - Activity is no longer visible
  • onDestroy() - Activity is destroyed

Lifecycle-Aware Component

class LocationManager(
    private val context: Context
) : DefaultLifecycleObserver {
    
    private var enabled = false

    override fun onStart(owner: LifecycleOwner) {
        if (enabled) {
            startLocationUpdates()
        }
    }

    override fun onStop(owner: LifecycleOwner) {
        stopLocationUpdates()
    }

    private fun startLocationUpdates() {
        // Request location updates
    }

    private fun stopLocationUpdates() {
        // Remove location updates
    }
}

Networking

Retrofit API Example

// API Interface
interface ApiService {
    @GET("users")
    suspend fun getUsers(): Response>

    @GET("users/{id}")
    suspend fun getUser(@Path("id") userId: String): Response

    @POST("login")
    suspend fun login(@Body request: LoginRequest): Response
}

// API Client
object ApiClient {
    private const val BASE_URL = "https://api.example.com/"

    private val logging = HttpLoggingInterceptor().apply {
        level = HttpLoggingInterceptor.Level.BODY
    }

    private val client = OkHttpClient.Builder()
        .addInterceptor(logging)
        .connectTimeout(30, TimeUnit.SECONDS)
        .readTimeout(30, TimeUnit.SECONDS)
        .build()

    private val retrofit = Retrofit.Builder()
        .baseUrl(BASE_URL)
        .client(client)
        .addConverterFactory(GsonConverterFactory.create())
        .build()

    val api: ApiService = retrofit.create(ApiService::class.java)
}