Expert in Android development with Kotlin, Jetpack Compose, Material 3, Coroutines, and modern Android architecture. Use for building Android apps, widgets, and Google platform integrations.
Provide expert-level Android development assistance focusing on Kotlin, Jetpack Compose, modern Android architecture, performance optimization, and Google platform best practices.
@Composable
fun ItemListScreen(
viewModel: ItemListViewModel = hiltViewModel(),
onItemClick: (String) -> Unit,
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
ItemListContent(
uiState = uiState,
onItemClick = onItemClick,
onRefresh = viewModel::refresh,
onRetry = viewModel::retry,
)
}
@Composable
private fun ItemListContent(
uiState: ItemListUiState,
onItemClick: (String) -> Unit,
onRefresh: () -> Unit,
onRetry: () -> Unit,
) {
Scaffold(
topBar = {
TopAppBar(title = { Text("Items") })
},
) { padding ->
when (uiState) {
is ItemListUiState.Loading -> {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
CircularProgressIndicator()
}
}
is ItemListUiState.Success -> {
LazyColumn(contentPadding = padding) {
items(uiState.items, key = { it.id }) { item ->
ItemRow(
item = item,
onClick = { onItemClick(item.id) },
)
}
}
}
is ItemListUiState.Error -> {
ErrorContent(
message = uiState.message,
onRetry = onRetry,
)
}
}
}
}
@Preview(showBackground = true)
@Composable
private fun ItemListContentPreview() {
AppTheme {
ItemListContent(
uiState = ItemListUiState.Success(sampleItems),
onItemClick = {},
onRefresh = {},
onRetry = {},
)
}
}
@HiltViewModel
class ItemListViewModel @Inject constructor(
private val repository: ItemRepository,
) : ViewModel() {
private val _uiState = MutableStateFlow<ItemListUiState>(ItemListUiState.Loading)
val uiState: StateFlow<ItemListUiState> = _uiState.asStateFlow()
init {
loadItems()
}
fun refresh() = loadItems()
fun retry() = loadItems()
private fun loadItems() {
viewModelScope.launch {
_uiState.value = ItemListUiState.Loading
repository.getItems()
.onSuccess { items ->
_uiState.value = ItemListUiState.Success(items)
}
.onFailure { error ->
_uiState.value = ItemListUiState.Error(
error.message ?: "Something went wrong"
)
}
}
}
}
sealed interface ItemListUiState {
data object Loading : ItemListUiState
data class Success(val items: List<Item>) : ItemListUiState
data class Error(val message: String) : ItemListUiState
}
interface ItemRepository {
suspend fun getItems(): Result<List<Item>>
suspend fun getItem(id: String): Result<Item>
suspend fun saveItem(item: Item): Result<Unit>
suspend fun deleteItem(id: String): Result<Unit>
}
class ItemRepositoryImpl @Inject constructor(
private val api: ItemApi,
private val dao: ItemDao,
private val dispatcher: CoroutineDispatcher = Dispatchers.IO,
) : ItemRepository {
override suspend fun getItems(): Result<Item> = withContext(dispatcher) {
runCatching {
val remote = api.getItems()
dao.insertAll(remote.map { it.toEntity() })
dao.getAll().map { it.toDomain() }
}.recoverCatching {
// Fallback to cache on network failure
dao.getAll().map { it.toDomain() }
}
}
}
interface ItemApi {
@GET("items")
suspend fun getItems(): List<ItemDto>
@GET("items/{id}")
suspend fun getItem(@Path("id") id: String): ItemDto
@POST("items")
suspend fun createItem(@Body item: CreateItemRequest): ItemDto
}
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
@Singleton
fun provideOkHttpClient(): OkHttpClient =
OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
})
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
@Provides
@Singleton
fun provideRetrofit(client: OkHttpClient): Retrofit =
Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
@Provides
@Singleton
fun provideItemApi(retrofit: Retrofit): ItemApi =
retrofit.create(ItemApi::class.java)
}
@Entity(tableName = "items")
data class ItemEntity(
@PrimaryKey val id: String,
val title: String,
val description: String,
@ColumnInfo(name = "created_at") val createdAt: Long,
)
@Dao
interface ItemDao {
@Query("SELECT * FROM items ORDER BY created_at DESC")
suspend fun getAll(): List<ItemEntity>
@Query("SELECT * FROM items WHERE id = :id")
suspend fun getById(id: String): ItemEntity?
@Query("SELECT * FROM items ORDER BY created_at DESC")
fun observeAll(): Flow<List<ItemEntity>>
@Upsert
suspend fun insertAll(items: List<ItemEntity>)
@Delete
suspend fun delete(item: ItemEntity)
}
@Database(entities = [ItemEntity::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun itemDao(): ItemDao
}
@Serializable data object Home
@Serializable data class Detail(val itemId: String)
@Serializable data object Settings
@Composable
fun AppNavHost(navController: NavHostController = rememberNavController()) {
NavHost(navController = navController, startDestination = Home) {
composable<Home> {
ItemListScreen(
onItemClick = { id -> navController.navigate(Detail(id)) },
)
}
composable<Detail> { backStackEntry ->
val detail: Detail = backStackEntry.toRoute()
ItemDetailScreen(itemId = detail.itemId)
}
composable<Settings> {
SettingsScreen()
}
}
}
@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {
@Binds
@Singleton
abstract fun bindItemRepository(impl: ItemRepositoryImpl): ItemRepository
}
@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {
@Provides
@Singleton
fun provideDatabase(@ApplicationContext context: Context): AppDatabase =
Room.databaseBuilder(context, AppDatabase::class.java, "app.db")
.fallbackToDestructiveMigration()
.build()
@Provides
fun provideItemDao(db: AppDatabase): ItemDao = db.itemDao()
}
val Context.dataStore by preferencesDataStore(name = "settings")
object PreferencesKeys {
val DARK_MODE = booleanPreferencesKey("dark_mode")
val AUTH_TOKEN = stringPreferencesKey("auth_token")
val ONBOARDING_COMPLETE = booleanPreferencesKey("onboarding_complete")
}
class SettingsRepository @Inject constructor(
@ApplicationContext private val context: Context,
) {
val darkMode: Flow<Boolean> = context.dataStore.data
.map { it[PreferencesKeys.DARK_MODE] ?: false }
suspend fun setDarkMode(enabled: Boolean) {
context.dataStore.edit { it[PreferencesKeys.DARK_MODE] = enabled }
}
}
Project Organization
Performance
Testing
Accessibility
App Lifecycle
When implementing Android features:
This skill ensures production-ready, maintainable, and performant Android applications following Google's latest guidelines and modern Kotlin patterns.