Jetpack Libraries
Phần này tổng hợp các câu hỏi về Android Jetpack - bộ thư viện giúp phát triển Android app hiện đại.
1. ViewModel & LiveData
Phần tiêu đề “1. ViewModel & LiveData”Q: ViewModel là gì? Khi nào cần dùng?
Phần tiêu đề “Q: ViewModel là gì? Khi nào cần dùng?”Trả lời:
ViewModel là class lưu trữ và quản lý UI-related data, survive configuration changes.
flowchart LR subgraph "Activity Lifecycle" A1["onCreate"] --> A2["onDestroy<br/>(rotation)"] A2 --> A3["onCreate"] A3 --> A4["onDestroy<br/>(finish)"] end
subgraph "ViewModel Lifecycle" V1["Created"] --> V2["Active"] V2 --> V3["Cleared"] end
A1 -.-> V1 A4 -.-> V3
style V2 fill:#c8e6c9Use cases:
- ✅ Hold UI state (loading, data, errors)
- ✅ Business logic cho UI
- ✅ Communicate with Repository
- ❌ Reference đến View/Context
class UserViewModel( private val repository: UserRepository) : ViewModel() {
private val _uiState = MutableStateFlow(UserUiState()) val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()
fun loadUser(id: String) { viewModelScope.launch { _uiState.update { it.copy(isLoading = true) }
repository.getUser(id) .onSuccess { user -> _uiState.update { it.copy(isLoading = false, user = user) } } .onFailure { error -> _uiState.update { it.copy(isLoading = false, error = error.message) } } } }
override fun onCleared() { super.onCleared() // Cleanup resources }}
// Usage in Compose@Composablefun UserScreen(viewModel: UserViewModel = hiltViewModel()) { val uiState by viewModel.uiState.collectAsStateWithLifecycle()
when { uiState.isLoading -> LoadingIndicator() uiState.error != null -> ErrorMessage(uiState.error) else -> UserContent(uiState.user) }}📚 Tìm hiểu thêm: ViewModel
Q: LiveData vs StateFlow?
Phần tiêu đề “Q: LiveData vs StateFlow?”Trả lời:
| Aspect | LiveData | StateFlow |
|---|---|---|
| Origins | Android Architecture Components | Kotlin Coroutines |
| Lifecycle | Built-in aware | Manual with operators |
| Platform | Android only | Multiplatform |
| Initial value | Can be null | Required |
| Operators | Limited (map, switchMap) | Rich Flow operators |
| Testing | InstantTaskExecutorRule | runTest, Turbine |
// LiveData approachclass ViewModel : ViewModel() { private val _data = MutableLiveData<List<User>>() val data: LiveData<List<User>> = _data
fun load() { viewModelScope.launch { _data.value = repository.getUsers() } }}
// StateFlow approach (Recommended)class ViewModel : ViewModel() { val data: StateFlow<List<User>> = repository.getUsers() .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), initialValue = emptyList() )}💡 Mẹo: Dùng
WhileSubscribed(5000)để giữ flow active 5s sau configuration change, tránh refetch.
2. Room Database
Phần tiêu đề “2. Room Database”Q: Room Architecture gồm những thành phần nào?
Phần tiêu đề “Q: Room Architecture gồm những thành phần nào?”Trả lời:
Room là ORM layer trên SQLite với 3 components chính:
flowchart LR A["Repository"] --> B["DAO"] B --> C["Room Database"] C --> D["SQLite"]
E["Entity"] E -.-> C
style B fill:#fff3e0 style C fill:#e8f5e9| Component | Purpose |
|---|---|
| Entity | Table definition (data class) |
| DAO | Database operations (interface) |
| Database | Database holder, entry point |
// Entity@Entity(tableName = "users")data class UserEntity( @PrimaryKey val id: String, @ColumnInfo(name = "full_name") val name: String, val email: String, val createdAt: Long = System.currentTimeMillis())
// DAO@Daointerface UserDao { @Query("SELECT * FROM users ORDER BY createdAt DESC") fun getAll(): Flow<List<UserEntity>>
@Query("SELECT * FROM users WHERE id = :id") suspend fun getById(id: String): UserEntity?
@Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insert(user: UserEntity)
@Update suspend fun update(user: UserEntity)
@Delete suspend fun delete(user: UserEntity)
@Query("DELETE FROM users") suspend fun deleteAll()}
// Database@Database( entities = [UserEntity::class, PostEntity::class], version = 2, exportSchema = true)@TypeConverters(Converters::class)abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): UserDao abstract fun postDao(): PostDao}
// Build databaseval db = Room.databaseBuilder(context, AppDatabase::class.java, "app.db") .addMigrations(MIGRATION_1_2) .build()📚 Tìm hiểu thêm: Room Database
Q: Room Migrations hoạt động như thế nào?
Phần tiêu đề “Q: Room Migrations hoạt động như thế nào?”Trả lời:
// Migration from version 1 to 2val MIGRATION_1_2 = object : Migration(1, 2) { override fun migrate(database: SupportSQLiteDatabase) { // Add new column database.execSQL("ALTER TABLE users ADD COLUMN avatar_url TEXT") }}
// Migration from 2 to 3val MIGRATION_2_3 = object : Migration(2, 3) { override fun migrate(database: SupportSQLiteDatabase) { // Create new table database.execSQL(""" CREATE TABLE posts ( id TEXT PRIMARY KEY NOT NULL, title TEXT NOT NULL, content TEXT NOT NULL, user_id TEXT NOT NULL, FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE ) """) }}
// Add migrations to builderRoom.databaseBuilder(context, AppDatabase::class.java, "app.db") .addMigrations(MIGRATION_1_2, MIGRATION_2_3) .fallbackToDestructiveMigration() // Only as last resort! .build()Testing migrations:
@RunWith(AndroidJUnit4::class)class MigrationTest { @get:Rule val helper = MigrationTestHelper( InstrumentationRegistry.getInstrumentation(), AppDatabase::class.java )
@Test fun migrate1To2() { helper.createDatabase(TEST_DB, 1).apply { execSQL("INSERT INTO users VALUES ('1', 'John')") close() }
helper.runMigrationsAndValidate(TEST_DB, 2, true, MIGRATION_1_2) }}3. WorkManager
Phần tiêu đề “3. WorkManager”Q: WorkManager là gì? Khi nào nên dùng?
Phần tiêu đề “Q: WorkManager là gì? Khi nào nên dùng?”Trả lời:
WorkManager là API cho deferrable, guaranteed background work.
| Feature | WorkManager | Service | Coroutines |
|---|---|---|---|
| Guaranteed execution | ✅ | ❌ | ❌ |
| Survives app restart | ✅ | ❌ | ❌ |
| Survives device reboot | ✅ | ❌ | ❌ |
| Constraints support | ✅ | ❌ | ❌ |
| Immediate execution | ❌ | ✅ | ✅ |
Use cases:
- ✅ Sync data với server
- ✅ Upload/download files
- ✅ Periodic cleanup tasks
- ❌ Real-time updates (use Service)
// Worker classclass SyncWorker( context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result { return try { val userId = inputData.getString("user_id") ?: return Result.failure() repository.syncUser(userId) Result.success() } catch (e: Exception) { if (runAttemptCount < 3) { Result.retry() } else { Result.failure() } } }}
// Schedule workval constraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .setRequiresBatteryNotLow(true) .build()
val syncRequest = OneTimeWorkRequestBuilder<SyncWorker>() .setConstraints(constraints) .setInputData(workDataOf("user_id" to userId)) .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 10, TimeUnit.SECONDS) .build()
WorkManager.getInstance(context).enqueueUniqueWork( "sync_$userId", ExistingWorkPolicy.REPLACE, syncRequest)
// Observe work statusWorkManager.getInstance(context) .getWorkInfoByIdLiveData(syncRequest.id) .observe(owner) { info -> when (info?.state) { WorkInfo.State.SUCCEEDED -> showSuccess() WorkInfo.State.FAILED -> showError() WorkInfo.State.RUNNING -> showProgress() else -> {} } }📚 Tìm hiểu thêm: WorkManager
Q: Periodic work vs OneTime work?
Phần tiêu đề “Q: Periodic work vs OneTime work?”Trả lời:
// One-time workval oneTimeRequest = OneTimeWorkRequestBuilder<SyncWorker>() .setInitialDelay(10, TimeUnit.MINUTES) .build()
// Periodic work (minimum 15 minutes)val periodicRequest = PeriodicWorkRequestBuilder<CleanupWorker>( repeatInterval = 1, TimeUnit.HOURS, flexInterval = 15, TimeUnit.MINUTES // Can run within last 15 min of interval) .setConstraints(constraints) .build()
WorkManager.getInstance(context).enqueueUniquePeriodicWork( "cleanup", ExistingPeriodicWorkPolicy.KEEP, periodicRequest)
// Chain workWorkManager.getInstance(context) .beginWith(downloadWork) .then(processWork) .then(uploadWork) .enqueue()4. DataStore
Phần tiêu đề “4. DataStore”Q: DataStore vs SharedPreferences?
Phần tiêu đề “Q: DataStore vs SharedPreferences?”Trả lời:
| Aspect | SharedPreferences | DataStore |
|---|---|---|
| API | Synchronous | Asynchronous (Flow) |
| Thread safety | ❌ (can block UI) | ✅ |
| Error handling | No exceptions | Flow catches errors |
| Type safety | Runtime | Compile-time (Proto) |
| Data format | XML | Preferences or Protocol Buffers |
// Preferences DataStoreval Context.dataStore by preferencesDataStore(name = "settings")
// Keysobject PreferencesKeys { val DARK_MODE = booleanPreferencesKey("dark_mode") val USER_NAME = stringPreferencesKey("user_name") val FONT_SIZE = floatPreferencesKey("font_size")}
// Writesuspend fun setDarkMode(enabled: Boolean) { context.dataStore.edit { prefs -> prefs[PreferencesKeys.DARK_MODE] = enabled }}
// Readval darkModeFlow: Flow<Boolean> = context.dataStore.data .catch { exception -> if (exception is IOException) emit(emptyPreferences()) else throw exception } .map { prefs -> prefs[PreferencesKeys.DARK_MODE] ?: false }
// In ViewModelclass SettingsViewModel(private val dataStore: DataStore<Preferences>) : ViewModel() {
val darkMode = dataStore.data .map { it[PreferencesKeys.DARK_MODE] ?: false } .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), false)
fun toggleDarkMode() { viewModelScope.launch { dataStore.edit { prefs -> prefs[PreferencesKeys.DARK_MODE] = !(prefs[PreferencesKeys.DARK_MODE] ?: false) } } }}📚 Tìm hiểu thêm: DataStore
5. Navigation Component
Phần tiêu đề “5. Navigation Component”Q: Navigation Component gồm những thành phần nào?
Phần tiêu đề “Q: Navigation Component gồm những thành phần nào?”Trả lời:
| Component | Purpose |
|---|---|
| NavGraph | Define destinations và actions |
| NavHost | Empty container hiển thị destinations |
| NavController | Navigate giữa destinations |
// Navigation graph (nav_graph.xml)<navigation xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/nav_graph" app:startDestination="@id/homeFragment">
<fragment android:id="@+id/homeFragment" android:name="com.example.HomeFragment"> <action android:id="@+id/action_home_to_detail" app:destination="@id/detailFragment"/> </fragment>
<fragment android:id="@+id/detailFragment" android:name="com.example.DetailFragment"> <argument android:name="itemId" app:argType="string"/> </fragment></navigation>
// Navigate with Safe Argsval action = HomeFragmentDirections.actionHomeToDetail(itemId = "123")findNavController().navigate(action)
// Receive argumentsval args: DetailFragmentArgs by navArgs()val itemId = args.itemIdCompose Navigation:
Phần tiêu đề “Compose Navigation:”@Composablefun AppNavigation() { val navController = rememberNavController()
NavHost(navController = navController, startDestination = "home") { composable("home") { HomeScreen( onItemClick = { id -> navController.navigate("detail/$id") } ) }
composable( route = "detail/{itemId}", arguments = listOf(navArgument("itemId") { type = NavType.StringType }) ) { backStackEntry -> val itemId = backStackEntry.arguments?.getString("itemId") DetailScreen(itemId = itemId) } }}📚 Tìm hiểu thêm: Compose Navigation
6. Paging 3
Phần tiêu đề “6. Paging 3”Q: Paging 3 hoạt động như thế nào?
Phần tiêu đề “Q: Paging 3 hoạt động như thế nào?”Trả lời:
Paging 3 load data in chunks, handling loading states và errors.
flowchart LR A["PagingSource"] --> B["Pager"] B --> C["PagingData Flow"] C --> D["LazyPagingItems"] D --> E["LazyColumn"]
style B fill:#fff3e0// PagingSourceclass UserPagingSource( private val api: UserApi) : PagingSource<Int, User>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, User> { return try { val page = params.key ?: 1 val response = api.getUsers(page, params.loadSize)
LoadResult.Page( data = response.users, prevKey = if (page == 1) null else page - 1, nextKey = if (response.users.isEmpty()) null else page + 1 ) } catch (e: Exception) { LoadResult.Error(e) } }
override fun getRefreshKey(state: PagingState<Int, User>): Int? { return state.anchorPosition?.let { position -> state.closestPageToPosition(position)?.prevKey?.plus(1) ?: state.closestPageToPosition(position)?.nextKey?.minus(1) } }}
// ViewModelclass UserViewModel(private val api: UserApi) : ViewModel() {
val users: Flow<PagingData<User>> = Pager( config = PagingConfig( pageSize = 20, prefetchDistance = 5, enablePlaceholders = false ), pagingSourceFactory = { UserPagingSource(api) } ).flow.cachedIn(viewModelScope)}
// Compose UI@Composablefun UserList(viewModel: UserViewModel) { val users = viewModel.users.collectAsLazyPagingItems()
LazyColumn { items(users.itemCount) { index -> users[index]?.let { user -> UserItem(user) } }
// Loading states when (users.loadState.append) { is LoadState.Loading -> item { LoadingItem() } is LoadState.Error -> item { RetryItem { users.retry() } } else -> {} } }}7. Hilt (Dependency Injection)
Phần tiêu đề “7. Hilt (Dependency Injection)”Q: Hilt scopes và components?
Phần tiêu đề “Q: Hilt scopes và components?”Trả lời:
| Component | Scope | Lifetime |
|---|---|---|
SingletonComponent |
@Singleton |
App |
ActivityRetainedComponent |
@ActivityRetainedScoped |
ViewModel |
ViewModelComponent |
@ViewModelScoped |
ViewModel |
ActivityComponent |
@ActivityScoped |
Activity |
FragmentComponent |
@FragmentScoped |
Fragment |
// Application@HiltAndroidAppclass MyApplication : Application()
// Module with Singleton scope@Module@InstallIn(SingletonComponent::class)object AppModule {
@Provides @Singleton fun provideDatabase(@ApplicationContext context: Context): AppDatabase { return Room.databaseBuilder(context, AppDatabase::class.java, "app.db").build() }
@Provides @Singleton fun provideRetrofit(): Retrofit { return Retrofit.Builder() .baseUrl("https://api.example.com/") .addConverterFactory(GsonConverterFactory.create()) .build() }}
// ViewModel with injection@HiltViewModelclass UserViewModel @Inject constructor( private val repository: UserRepository, private val savedStateHandle: SavedStateHandle) : ViewModel()
// Activity@AndroidEntryPointclass MainActivity : ComponentActivity() { private val viewModel: UserViewModel by viewModels()}📚 Tìm hiểu thêm: Dependency Injection
📝 Jetpack Library Selection Guide
Phần tiêu đề “📝 Jetpack Library Selection Guide”| Need | Library |
|---|---|
| UI state across config changes | ViewModel |
| Local database | Room |
| Guaranteed background work | WorkManager |
| Key-value storage | DataStore |
| Screen navigation | Navigation |
| Large data lists | Paging 3 |
| Dependency injection | Hilt |
| Observe lifecycle | Lifecycle |
| Image loading | Coil |