Getting Started
Android Setup
# Install Android Studio
# Download from: https://developer.android.com/studio
# Create a new project
# File → New → New Project → Choose template
# Gradle dependencies (app/build.gradle)
android {
compileSdkVersion 33
defaultConfig {
applicationId "com.example.myapp"
minSdkVersion 21
targetSdkVersion 33
versionCode 1
versionName "1.0"
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.9.0'
implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'com.google.android.material:material:1.7.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
# Download from: https://developer.android.com/studio
# Create a new project
# File → New → New Project → Choose template
# Gradle dependencies (app/build.gradle)
android {
compileSdkVersion 33
defaultConfig {
applicationId "com.example.myapp"
minSdkVersion 21
targetSdkVersion 33
versionCode 1
versionName "1.0"
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.9.0'
implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'com.google.android.material:material:1.7.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
Note: Make sure you have Java Development Kit (JDK) installed. Android Studio includes OpenJDK by default.
Project Structure
# Android Project Structure
app/
├── manifests/
│ └── AndroidManifest.xml
├── java/
│ └── com.example.myapp
│ ├── MainActivity.kt
│ ├── models/
│ ├── viewmodels/
│ └── adapters/
├── res/
│ ├── layout/
│ │ ├── activity_main.xml
│ │ └── item_view.xml
│ ├── values/
│ │ ├── strings.xml
│ │ ├── colors.xml
│ │ └── styles.xml
│ ├── drawable/
│ ├── mipmap/
│ └── menu/
└── Gradle Scripts/
├── build.gradle (Project)
└── build.gradle (Module: app)
# AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapp">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">
<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>
</application>
</manifest>
app/
├── manifests/
│ └── AndroidManifest.xml
├── java/
│ └── com.example.myapp
│ ├── MainActivity.kt
│ ├── models/
│ ├── viewmodels/
│ └── adapters/
├── res/
│ ├── layout/
│ │ ├── activity_main.xml
│ │ └── item_view.xml
│ ├── values/
│ │ ├── strings.xml
│ │ ├── colors.xml
│ │ └── styles.xml
│ ├── drawable/
│ ├── mipmap/
│ └── menu/
└── Gradle Scripts/
├── build.gradle (Project)
└── build.gradle (Module: app)
# AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapp">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">
<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>
</application>
</manifest>
Kotlin Basics
Kotlin Syntax
// Variables and constants
var mutableVariable: String = "Hello" // Mutable
val immutableVariable: Int = 42 // Immutable (read-only)
// Null safety
var nullableString: String? = null // Nullable type
val length = nullableString?.length // Safe call
val nonNullLength = nullableString!!.length // Non-null assertion
val result = nullableString ?: "default" // Elvis operator
// Functions
fun greet(name: String): String {
return "Hello, $name"
}
// Single-expression function
fun square(x: Int) = x * x
// Higher-order functions
fun calculate(x: Int, y: Int, operation: (Int, Int) -> Int): Int {
return operation(x, y)
}
// Lambda expression
val sum = { x: Int, y: Int -> x + y }
var mutableVariable: String = "Hello" // Mutable
val immutableVariable: Int = 42 // Immutable (read-only)
// Null safety
var nullableString: String? = null // Nullable type
val length = nullableString?.length // Safe call
val nonNullLength = nullableString!!.length // Non-null assertion
val result = nullableString ?: "default" // Elvis operator
// Functions
fun greet(name: String): String {
return "Hello, $name"
}
// Single-expression function
fun square(x: Int) = x * x
// Higher-order functions
fun calculate(x: Int, y: Int, operation: (Int, Int) -> Int): Int {
return operation(x, y)
}
// Lambda expression
val sum = { x: Int, y: Int -> x + y }
Note: Kotlin is now the preferred language for Android development, offering null safety, extension functions, and concise syntax.
Kotlin OOP
// Class definition
class Person(val name: String, var age: Int) {
// Property with custom getter
val isAdult: Boolean
get() = age >= 18
// Method
fun speak() {
println("My name is $name")
}
}
// Data class (automatically generates equals(), hashCode(), toString())
data class User(val id: Long, val name: String, val email: String)
// Singleton object
object AppConfig {
const val API_URL = "https://api.example.com"
fun initialize() {
// Initialization code
}
}
// Companion object (like static in Java)
class MyClass {
companion object {
fun create(): MyClass = MyClass()
}
}
// Extension function
fun String.addExclamation() = this + "!"
class Person(val name: String, var age: Int) {
// Property with custom getter
val isAdult: Boolean
get() = age >= 18
// Method
fun speak() {
println("My name is $name")
}
}
// Data class (automatically generates equals(), hashCode(), toString())
data class User(val id: Long, val name: String, val email: String)
// Singleton object
object AppConfig {
const val API_URL = "https://api.example.com"
fun initialize() {
// Initialization code
}
}
// Companion object (like static in Java)
class MyClass {
companion object {
fun create(): MyClass = MyClass()
}
}
// Extension function
fun String.addExclamation() = this + "!"
Note: Kotlin provides data classes, object declarations, extension functions, and other features that reduce boilerplate code compared to Java.
Android Components
Activities & Fragments
// Basic Activity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Initialize views
val button = findViewById<Button>(R.id.my_button)
button.setOnClickListener {
// Handle click
}
}
}
// Basic Fragment
class MyFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_my, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Initialize views
view.findViewById<Button>(R.id.button).setOnClickListener {
// Handle click
}
}
}
// Starting an Activity
val intent = Intent(this, SecondActivity::class.java)
intent.putExtra("key", "value")
startActivity(intent)
// Starting an Activity for result
val intent = Intent(this, SecondActivity::class.java)
startActivityForResult(intent, REQUEST_CODE)
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Initialize views
val button = findViewById<Button>(R.id.my_button)
button.setOnClickListener {
// Handle click
}
}
}
// Basic Fragment
class MyFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_my, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Initialize views
view.findViewById<Button>(R.id.button).setOnClickListener {
// Handle click
}
}
}
// Starting an Activity
val intent = Intent(this, SecondActivity::class.java)
intent.putExtra("key", "value")
startActivity(intent)
// Starting an Activity for result
val intent = Intent(this, SecondActivity::class.java)
startActivityForResult(intent, REQUEST_CODE)
Services & Broadcasts
// Basic Service
class MyService : Service() {
override fun onBind(intent: Intent?): IBinder? {
return null // For non-bound service
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// Perform tasks here
return START_STICKY
}
}
// Starting and stopping a service
val intent = Intent(this, MyService::class.java)
startService(intent) // To start
stopService(intent) // To stop
// Broadcast Receiver
class MyReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
// Handle broadcast
if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
// Do something after boot
}
}
}
// Registering broadcast receiver in AndroidManifest.xml
<receiver android:name=".MyReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
class MyService : Service() {
override fun onBind(intent: Intent?): IBinder? {
return null // For non-bound service
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// Perform tasks here
return START_STICKY
}
}
// Starting and stopping a service
val intent = Intent(this, MyService::class.java)
startService(intent) // To start
stopService(intent) // To stop
// Broadcast Receiver
class MyReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
// Handle broadcast
if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
// Do something after boot
}
}
}
// Registering broadcast receiver in AndroidManifest.xml
<receiver android:name=".MyReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
Note: Services run in the background to perform long-running operations, while Broadcast Receivers respond to system-wide announcements.
UI Design
Layouts & Views
// LinearLayout (vertical)
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:textSize="18sp" />
<Button
android:id="@+id/my_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Click Me" />
</LinearLayout>
// ConstraintLayout
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello ConstraintLayout!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
// RecyclerView item layout
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="8dp">
<ImageView
android:id="@+id/item_image"
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_launcher_foreground" />
<TextView
android:id="@+id/item_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Item Title"
android:textSize="16sp"
android:layout_gravity="center_vertical"
android:paddingStart="8dp"
android:paddingEnd="8dp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:textSize="18sp" />
<Button
android:id="@+id/my_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Click Me" />
</LinearLayout>
// ConstraintLayout
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello ConstraintLayout!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
// RecyclerView item layout
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="8dp">
<ImageView
android:id="@+id/item_image"
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_launcher_foreground" />
<TextView
android:id="@+id/item_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Item Title"
android:textSize="16sp"
android:layout_gravity="center_vertical"
android:paddingStart="8dp"
android:paddingEnd="8dp" />
</LinearLayout>
RecyclerView & Adapters
// Data class for RecyclerView items
data class Item(val id: Long, val title: String, val imageRes: Int)
// ViewHolder
class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(item: Item) {
itemView.findViewById<TextView>(R.id.item_title).text = item.title
itemView.findViewById<ImageView>(R.id.item_image).setImageResource(item.imageRes)
}
}
// Adapter
class ItemAdapter(private val items: List<Item>) : RecyclerView.Adapter<ItemViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_layout, parent, false)
return ItemViewHolder(view)
}
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
holder.bind(items[position])
}
override fun getItemCount() = items.size
}
// Using RecyclerView in Activity/Fragment
val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
recyclerView.layoutManager = LinearLayoutManager(this)
val items = listOf(
Item(1, "First Item", R.drawable.ic_first),
Item(2, "Second Item", R.drawable.ic_second),
Item(3, "Third Item", R.drawable.ic_third)
)
recyclerView.adapter = ItemAdapter(items)
data class Item(val id: Long, val title: String, val imageRes: Int)
// ViewHolder
class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(item: Item) {
itemView.findViewById<TextView>(R.id.item_title).text = item.title
itemView.findViewById<ImageView>(R.id.item_image).setImageResource(item.imageRes)
}
}
// Adapter
class ItemAdapter(private val items: List<Item>) : RecyclerView.Adapter<ItemViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_layout, parent, false)
return ItemViewHolder(view)
}
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
holder.bind(items[position])
}
override fun getItemCount() = items.size
}
// Using RecyclerView in Activity/Fragment
val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
recyclerView.layoutManager = LinearLayoutManager(this)
val items = listOf(
Item(1, "First Item", R.drawable.ic_first),
Item(2, "Second Item", R.drawable.ic_second),
Item(3, "Third Item", R.drawable.ic_third)
)
recyclerView.adapter = ItemAdapter(items)
Note: RecyclerView is more efficient than ListView for displaying large datasets as it recycles item views as they scroll off-screen.
Architecture & Patterns
MVVM with LiveData
// ViewModel
class MyViewModel : ViewModel() {
private val _data = MutableLiveData<String>()
val data: LiveData<String> = _data
fun loadData() {
viewModelScope.launch {
// Simulate data loading
delay(1000)
_data.value = "Loaded data"
}
}
}
// Activity/Fragment using ViewModel
class MyActivity : AppCompatActivity() {
private lateinit var viewModel: MyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_my)
// Initialize ViewModel
viewModel = ViewModelProvider(this).get(MyViewModel::class.java)
// Observe LiveData
viewModel.data.observe(this) { data ->
// Update UI with new data
findViewById<TextView>(R.id.text_view).text = data
}
// Load data
findViewById<Button>(R.id.load_button).setOnClickListener {
viewModel.loadData()
}
}
}
// Repository pattern
class DataRepository {
suspend fun fetchData(): String {
// Simulate network call
delay(2000)
return "Data from repository"
}
}
class MyViewModel : ViewModel() {
private val _data = MutableLiveData<String>()
val data: LiveData<String> = _data
fun loadData() {
viewModelScope.launch {
// Simulate data loading
delay(1000)
_data.value = "Loaded data"
}
}
}
// Activity/Fragment using ViewModel
class MyActivity : AppCompatActivity() {
private lateinit var viewModel: MyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_my)
// Initialize ViewModel
viewModel = ViewModelProvider(this).get(MyViewModel::class.java)
// Observe LiveData
viewModel.data.observe(this) { data ->
// Update UI with new data
findViewById<TextView>(R.id.text_view).text = data
}
// Load data
findViewById<Button>(R.id.load_button).setOnClickListener {
viewModel.loadData()
}
}
}
// Repository pattern
class DataRepository {
suspend fun fetchData(): String {
// Simulate network call
delay(2000)
return "Data from repository"
}
}
Room Database
// Entity (Table)
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
@ColumnInfo(name = "first_name") val firstName: String,
@ColumnInfo(name = "last_name") val lastName: String,
@ColumnInfo(name = "age") val age: Int
)
// DAO (Data Access Object)
@Dao
interface UserDao {
@Query("SELECT * FROM users")
fun getAll(): LiveData<List<User>>
@Query("SELECT * FROM users WHERE id = :id")
fun getById(id: Long): LiveData<User>
@Insert
suspend fun insert(user: User)
@Update
suspend fun update(user: User)
@Delete
suspend fun delete(user: User)
}
// Database
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
@Volatile
private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"app_database"
).build()
INSTANCE = instance
instance
}
}
}
}
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
@ColumnInfo(name = "first_name") val firstName: String,
@ColumnInfo(name = "last_name") val lastName: String,
@ColumnInfo(name = "age") val age: Int
)
// DAO (Data Access Object)
@Dao
interface UserDao {
@Query("SELECT * FROM users")
fun getAll(): LiveData<List<User>>
@Query("SELECT * FROM users WHERE id = :id")
fun getById(id: Long): LiveData<User>
@Insert
suspend fun insert(user: User)
@Update
suspend fun update(user: User)
@Delete
suspend fun delete(user: User)
}
// Database
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
@Volatile
private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"app_database"
).build()
INSTANCE = instance
instance
}
}
}
}
Note: Room is an abstraction layer over SQLite that provides compile-time checks of SQL queries and easy integration with LiveData and RxJava.