Android Studio
Development Tool
Android Studio
Overview
Android Studio is an Android-specific integrated development environment (IDE) developed by Google. Specialized for Android application development, it is used by Android developers worldwide as the official development tool.
Details
Android Studio is an Android-specific IDE released by Google in 2013, based on IntelliJ IDEA. The current latest version is Android Studio Koala (2024.1.1), based on the IntelliJ 2024.1 platform. It provides an environment optimized for Android development, including complete support for Java and Kotlin, deep integration with Android SDK, fast emulator, and rich debugging tools.
The 2024 version introduces numerous important new features. Generative AI integration enables AI-powered app development using the Gemini API template with Google AI SDK. Enhanced Running Devices window allows simultaneous display of multiple devices, along with new terminal and "sticky lines" functionality in the editor.
Android Studio's greatest strength lies in its comprehensive feature set specialized for Android development. It covers the entire Android development workflow with smart code editor, automatic code completion, real-time auditing, Gradle integration, layout editor, profiling tools, Firebase integration, and Google Play Console connectivity. The AVD emulator achieves fast startup within 6 seconds, streamlining testing across various devices.
Advantages and Disadvantages
Advantages
- Official Android IDE: Official support from Google with complete Android SDK integration
- Lightweight and Fast: Lightweight design enables fast startup and memory-efficient operation
- Intuitive UI: User-friendly interface accessible from beginners to advanced users
- Rich Library Support: More library integration than other IDEs
- Fast Emulator: Startup within 6 seconds enables diverse device testing
- Team Development Support: Excellent support for code sharing, change merging, and version control
- Completely Free: All features available at no cost
Disadvantages
- High System Requirements: Requires minimum 4GB RAM and 64-bit OS
- Installation Time: Initial installation takes considerable time
- Heavy RAM Consumption: Uses large amounts of memory, making it slow on older PCs
- Plugin Impact: Performance degradation when using many plugins
- Android Only: Cannot be used for non-Android development
- Learning Cost: Initial mastery takes time due to rich feature set
- Build Time: Long build times for large projects
Key Links
- Android Studio Official Site
- Android Developers Documentation
- Android Studio Release Notes
- Kotlin for Android
- Firebase Documentation
- Google Play Console
Code Examples
Project Configuration Example
// MainActivity.kt - Basic Kotlin Activity Structure
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.myapp.ui.theme.MyAppTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyAppTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
MainScreen()
}
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainScreen(viewModel: MainViewModel = viewModel()) {
var text by remember { mutableStateOf("") }
val items by viewModel.items.collectAsState()
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
TopAppBar(
title = { Text("My Android App") }
)
Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField(
value = text,
onValueChange = { text = it },
label = { Text("Enter item") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(8.dp))
Button(
onClick = {
if (text.isNotEmpty()) {
viewModel.addItem(text)
text = ""
}
},
modifier = Modifier.fillMaxWidth()
) {
Text("Add Item")
}
Spacer(modifier = Modifier.height(16.dp))
LazyColumn {
items(items) { item ->
ItemCard(
item = item,
onDelete = { viewModel.removeItem(it) }
)
}
}
}
}
@Composable
fun ItemCard(
item: String,
onDelete: (String) -> Unit
) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = item,
style = MaterialTheme.typography.bodyLarge
)
IconButton(onClick = { onDelete(item) }) {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = "Delete"
)
}
}
}
}
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
MyAppTheme {
MainScreen()
}
}
Gradle Build Configuration
// build.gradle.kts (Module: app)
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("kotlin-kapt")
id("dagger.hilt.android.plugin")
id("androidx.navigation.safeargs.kotlin")
}
android {
namespace = "com.example.myapp"
compileSdk = 34
defaultConfig {
applicationId = "com.example.myapp"
minSdk = 24
targetSdk = 34
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
debug {
isDebuggable = true
applicationIdSuffix = ".debug"
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
compose = true
viewBinding = true
dataBinding = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.8"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}
dependencies {
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
implementation("androidx.activity:activity-compose:1.8.2")
implementation("androidx.compose.ui:ui:1.5.8")
implementation("androidx.compose.ui:ui-tooling-preview:1.5.8")
implementation("androidx.compose.material3:material3:1.1.2")
implementation("androidx.navigation:navigation-compose:2.7.6")
// Hilt Dependency Injection
implementation("com.google.dagger:hilt-android:2.48")
kapt("com.google.dagger:hilt-compiler:2.48")
// Network
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
// Database
implementation("androidx.room:room-runtime:2.6.1")
implementation("androidx.room:room-ktx:2.6.1")
kapt("androidx.room:room-compiler:2.6.1")
// Testing
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.5.8")
debugImplementation("androidx.compose.ui:ui-tooling:1.5.8")
debugImplementation("androidx.compose.ui:ui-test-manifest:1.5.8")
}
ViewModel Example
// MainViewModel.kt - MVVM Architecture
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class MainViewModel @Inject constructor(
private val repository: ItemRepository
) : ViewModel() {
private val _items = MutableStateFlow<List<String>>(emptyList())
val items: StateFlow<List<String>> = _items.asStateFlow()
private val _isLoading = MutableStateFlow(false)
val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()
private val _error = MutableStateFlow<String?>(null)
val error: StateFlow<String?> = _error.asStateFlow()
init {
loadItems()
}
fun addItem(item: String) {
viewModelScope.launch {
try {
_isLoading.value = true
repository.addItem(item)
loadItems()
} catch (e: Exception) {
_error.value = e.message
} finally {
_isLoading.value = false
}
}
}
fun removeItem(item: String) {
viewModelScope.launch {
try {
repository.removeItem(item)
loadItems()
} catch (e: Exception) {
_error.value = e.message
}
}
}
private fun loadItems() {
viewModelScope.launch {
try {
_isLoading.value = true
_items.value = repository.getAllItems()
_error.value = null
} catch (e: Exception) {
_error.value = e.message
} finally {
_isLoading.value = false
}
}
}
}
Room Database Configuration
// AppDatabase.kt - Room Database Configuration
import androidx.room.*
import androidx.room.Room
@Entity(tableName = "items")
data class ItemEntity(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
@ColumnInfo(name = "name")
val name: String,
@ColumnInfo(name = "created_at")
val createdAt: Long = System.currentTimeMillis()
)
@Dao
interface ItemDao {
@Query("SELECT * FROM items ORDER BY created_at DESC")
suspend fun getAllItems(): List<ItemEntity>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertItem(item: ItemEntity)
@Query("DELETE FROM items WHERE name = :name")
suspend fun deleteItemByName(name: String)
@Query("DELETE FROM items")
suspend fun deleteAllItems()
}
@Database(
entities = [ItemEntity::class],
version = 1,
exportSchema = false
)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun itemDao(): ItemDao
companion object {
const val DATABASE_NAME = "app_database"
}
}
// Database Provider (Hilt)
@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {
@Provides
@Singleton
fun provideAppDatabase(@ApplicationContext context: Context): AppDatabase {
return Room.databaseBuilder(
context,
AppDatabase::class.java,
AppDatabase.DATABASE_NAME
).build()
}
@Provides
fun provideItemDao(database: AppDatabase): ItemDao {
return database.itemDao()
}
}
Android Manifest Configuration
<!-- AndroidManifest.xml -->
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<application
android:name=".MyApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyApp"
android:usesCleartextTraffic="true"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.MyApp"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".DetailActivity"
android:exported="false"
android:parentActivityName=".MainActivity" />
<service
android:name=".BackgroundService"
android:enabled="true"
android:exported="false" />
<receiver
android:name=".NotificationReceiver"
android:enabled="true"
android:exported="false" />
</application>
</manifest>
Unit Test Example
// MainViewModelTest.kt - Unit Testing
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.*
import org.junit.*
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.*
import org.mockito.junit.MockitoJUnitRunner
@ExperimentalCoroutinesApi
@RunWith(MockitoJUnitRunner::class)
class MainViewModelTest {
@get:Rule
val instantTaskExecutorRule = InstantTaskExecutorRule()
private val testDispatcher = UnconfinedTestDispatcher()
@Mock
private lateinit var repository: ItemRepository
private lateinit var viewModel: MainViewModel
@Before
fun setup() {
Dispatchers.setMain(testDispatcher)
viewModel = MainViewModel(repository)
}
@After
fun tearDown() {
Dispatchers.resetMain()
}
@Test
fun `addItem should update items list`() = runTest {
// Given
val newItem = "Test Item"
val expectedItems = listOf(newItem)
`when`(repository.getAllItems()).thenReturn(expectedItems)
// When
viewModel.addItem(newItem)
// Then
verify(repository).addItem(newItem)
verify(repository, times(2)).getAllItems() // During initialization and after addItem
assert(viewModel.items.value == expectedItems)
}
@Test
fun `removeItem should remove item from list`() = runTest {
// Given
val itemToRemove = "Item to remove"
val updatedItems = emptyList<String>()
`when`(repository.getAllItems()).thenReturn(updatedItems)
// When
viewModel.removeItem(itemToRemove)
// Then
verify(repository).removeItem(itemToRemove)
verify(repository, times(2)).getAllItems()
assert(viewModel.items.value == updatedItems)
}
}