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)
}