Câu hỏi phỏng vấn Architecture & Design Patterns
Phần này tổng hợp các câu hỏi về Architecture patterns và Design principles trong Android development.
1. MVVM (Model-View-ViewModel)
Q: MVVM pattern là gì? Giải thích từng layer?
Trả lời:
MVVM là architecture pattern tách biệt UI logic khỏi business logic:
| Layer | Responsibility | Android Components |
|---|---|---|
| Model | Data + business logic | Repository, Room, Retrofit |
| View | Display UI, handle input | Activity, Fragment, Composable |
| ViewModel | Hold UI state, transform data | ViewModel class |
// Model (Repository)
class UserRepository(private val api: UserApi) {
suspend fun getUser(id: String): User = api.fetchUser(id)
}
// ViewModel
class UserViewModel(private val repository: UserRepository) : ViewModel() {
private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
fun loadUser(id: String) {
viewModelScope.launch {
_uiState.value = UiState.Loading
try {
val user = repository.getUser(id)
_uiState.value = UiState.Success(user)
} catch (e: Exception) {
_uiState.value = UiState.Error(e.message)
}
}
}
}
// View (Compose)
@Composable
fun UserScreen(viewModel: UserViewModel) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
when (uiState) {
is UiState.Loading -> CircularProgressIndicator()
is UiState.Success -> UserContent(uiState.user)
is UiState.Error -> ErrorText(uiState.message)
}
}Lợi ích:
- ✅ Separation of concerns
- ✅ Testable business logic
- ✅ Survives configuration changes
📚 Tìm hiểu thêm: MVVM trong Android
Q: ViewModel survive configuration change như thế nào?
Trả lời:
ViewModelStore được retain bởi ViewModelStoreOwner (Activity/Fragment) qua configuration changes.
Internal mechanism:
ViewModelStorelưu trữ ViewModel instances theo key- Khi configuration change,
NonConfigurationInstancesgiữ reference đến ViewModelStore - Activity mới nhận lại cùng ViewModelStore
// ViewModel tồn tại qua rotation
class MainViewModel : ViewModel() {
private val _data = MutableStateFlow<List<Item>>(emptyList())
val data = _data.asStateFlow()
init {
loadData() // Called only once, not on rotation
}
override fun onCleared() {
// Called when Activity is truly finishing
}
}📚 Tìm hiểu thêm: ViewModel
2. MVI (Model-View-Intent)
Q: MVI khác gì MVVM?
Trả lời:
MVI thêm unidirectional data flow với Intent (user actions):
| Aspect | MVVM | MVI |
|---|---|---|
| Data Flow | Bidirectional | Unidirectional |
| State | Multiple LiveData/Flows | Single immutable state |
| Events | Direct function calls | Intent objects |
| Complexity | Simpler | More structured |
// Intent (User Actions)
sealed class UserIntent {
data class LoadUser(val id: String) : UserIntent()
object Refresh : UserIntent()
data class UpdateName(val name: String) : UserIntent()
}
// State (Single source of truth)
data class UserState(
val isLoading: Boolean = false,
val user: User? = null,
val error: String? = null
)
// ViewModel processes intents
class UserViewModel : ViewModel() {
private val _state = MutableStateFlow(UserState())
val state = _state.asStateFlow()
fun processIntent(intent: UserIntent) {
when (intent) {
is UserIntent.LoadUser -> loadUser(intent.id)
is UserIntent.Refresh -> refresh()
is UserIntent.UpdateName -> updateName(intent.name)
}
}
private fun loadUser(id: String) {
viewModelScope.launch {
_state.update { it.copy(isLoading = true) }
try {
val user = repository.getUser(id)
_state.update { it.copy(isLoading = false, user = user) }
} catch (e: Exception) {
_state.update { it.copy(isLoading = false, error = e.message) }
}
}
}
}
// View sends intents
@Composable
fun UserScreen(viewModel: UserViewModel) {
val state by viewModel.state.collectAsStateWithLifecycle()
LaunchedEffect(Unit) {
viewModel.processIntent(UserIntent.LoadUser("123"))
}
Button(onClick = { viewModel.processIntent(UserIntent.Refresh) }) {
Text("Refresh")
}
}Khi nào dùng MVI:
- ✅ Complex state với nhiều interactions
- ✅ Need time-travel debugging
- ✅ Team prefers strict patterns
3. Clean Architecture
Q: Clean Architecture trong Android?
Trả lời:
Clean Architecture chia app thành layers với dependency rule: outer layers depend on inner layers.
| Layer | Contents | Dependencies |
|---|---|---|
| Domain | Use Cases, Entities, Repository interfaces | None (pure Kotlin) |
| Data | Repository impl, Data Sources, Mappers | Domain |
| Presentation | UI, ViewModels | Domain |
// Domain Layer - Pure Kotlin, no Android dependencies
data class User(val id: String, val name: String)
interface UserRepository {
suspend fun getUser(id: String): User
suspend fun saveUser(user: User)
}
class GetUserUseCase(private val repository: UserRepository) {
suspend operator fun invoke(id: String): Result<User> {
return runCatching { repository.getUser(id) }
}
}
// Data Layer
class UserRepositoryImpl(
private val api: UserApi,
private val dao: UserDao
) : UserRepository {
override suspend fun getUser(id: String): User {
return try {
api.fetchUser(id).also { dao.insert(it.toEntity()) }
} catch (e: Exception) {
dao.getById(id)?.toUser() ?: throw e
}
}
}
// Presentation Layer
class UserViewModel(
private val getUserUseCase: GetUserUseCase
) : ViewModel() {
fun loadUser(id: String) {
viewModelScope.launch {
getUserUseCase(id)
.onSuccess { _user.value = it }
.onFailure { _error.value = it.message }
}
}
}📚 Tìm hiểu thêm: Clean Architecture
4. Dependency Injection
Q: Dependency Injection là gì? Hilt vs Koin?
Trả lời:
DI là pattern cung cấp dependencies từ bên ngoài thay vì tạo trong class.
// ❌ Without DI - tight coupling
class UserViewModel {
private val repository = UserRepository(
UserApi(RetrofitClient.create()),
UserDatabase.getInstance(context).userDao()
)
}
// ✅ With DI - loose coupling
class UserViewModel(
private val repository: UserRepository // Injected
) : ViewModel()Hilt vs Koin:
| Aspect | Hilt | Koin |
|---|---|---|
| Type | Compile-time | Runtime |
| Error Detection | Compile time | Runtime |
| Performance | Faster (no reflection) | Slower (uses reflection) |
| Setup | More boilerplate | Simpler |
| Google Support | ✅ Official | Community |
Hilt Example:
// Module
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Provides
@Singleton
fun provideUserRepository(
api: UserApi,
dao: UserDao
): UserRepository = UserRepositoryImpl(api, dao)
}
// ViewModel
@HiltViewModel
class UserViewModel @Inject constructor(
private val repository: UserRepository
) : ViewModel()
// Activity
@AndroidEntryPoint
class MainActivity : ComponentActivity()Koin Example:
// Module
val appModule = module {
single<UserRepository> { UserRepositoryImpl(get(), get()) }
viewModel { UserViewModel(get()) }
}
// Application
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
modules(appModule)
}
}
}
// Usage
class UserViewModel(
private val repository: UserRepository
) : ViewModel()📚 Tìm hiểu thêm: Dependency Injection, Koin
5. Repository Pattern
Q: Repository pattern là gì?
Trả lời:
Repository là abstraction layer giữa data sources và business logic:
Responsibilities:
- Abstract data sources
- Implement caching strategy
- Handle offline-first logic
- Map DTOs to domain entities
class UserRepository(
private val api: UserApi,
private val dao: UserDao,
private val cache: UserCache
) {
// Single source of truth pattern
fun getUsers(): Flow<List<User>> = flow {
// 1. Emit cached data first
cache.getUsers()?.let { emit(it) }
// 2. Fetch from network
try {
val users = api.fetchUsers()
dao.insertAll(users.map { it.toEntity() })
cache.setUsers(users)
emit(users)
} catch (e: Exception) {
// 3. Fallback to local DB
emit(dao.getAll().map { it.toUser() })
}
}
// Offline-first with NetworkBoundResource
fun getUser(id: String): Flow<Resource<User>> = networkBoundResource(
query = { dao.getById(id).map { it?.toUser() } },
fetch = { api.fetchUser(id) },
saveFetchResult = { dao.insert(it.toEntity()) },
shouldFetch = { it == null || cache.isExpired(id) }
)
}6. Design Principles
Q: SOLID principles trong Android?
Trả lời:
| Principle | Description | Android Example |
|---|---|---|
| Single Responsibility | One class, one job | Separate ViewModel, Repository |
| Open/Closed | Open for extension, closed for modification | Use interfaces |
| Liskov Substitution | Subtypes replaceable | Repository implementations |
| Interface Segregation | Small, focused interfaces | Separate Dao interfaces |
| Dependency Inversion | Depend on abstractions | Inject interfaces, not implementations |
// Single Responsibility
class UserRepository { /* only data operations */ }
class UserValidator { /* only validation */ }
class UserMapper { /* only mapping */ }
// Open/Closed - extend via interfaces
interface DataSource<T> {
suspend fun get(id: String): T?
suspend fun save(item: T)
}
class ApiDataSource : DataSource<UserDto> { /* ... */ }
class LocalDataSource : DataSource<UserEntity> { /* ... */ }
// Dependency Inversion
class UserViewModel(
private val repository: UserRepository // Interface, not UserRepositoryImpl
) : ViewModel()Q: Separation of Concerns trong Android?
Trả lời:
Mỗi component chỉ làm một việc:
| Component | Concern | Should NOT |
|---|---|---|
| Activity/Fragment | Host UI, handle navigation | Business logic, data fetching |
| Composable | Render UI | Direct API calls |
| ViewModel | UI state, orchestrate | Database access directly |
| Repository | Data operations | UI updates |
| Use Case | Business rules | Data persistence |
// ❌ Bad - Activity does everything
class BadActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
val users = Retrofit.create(UserApi::class.java)
.fetchUsers() // Network in Activity!
Room.databaseBuilder(this, AppDb::class.java, "db")
.build().userDao().insertAll(users) // DB in Activity!
}
}
// ✅ Good - Separation of concerns
@Composable
fun UserScreen(viewModel: UserViewModel = hiltViewModel()) {
val users by viewModel.users.collectAsStateWithLifecycle()
LazyColumn {
items(users) { UserItem(it) }
}
}
class UserViewModel(private val repository: UserRepository) : ViewModel() {
val users = repository.getUsers().stateIn(
viewModelScope, SharingStarted.WhileSubscribed(), emptyList()
)
}📝 Architecture Decision Guide
| Scenario | Recommended |
|---|---|
| Small app, simple state | MVVM |
| Complex state, many interactions | MVI |
| Large team, maintainability | Clean Architecture |
| Quick prototype | MVVM + Koin |
| Production app, Google stack | MVVM + Hilt |
| Multiple data sources | Repository pattern |
📚 Tìm hiểu thêm: MVVM, Clean Architecture