0
0
Lập trình
Sơn Tùng Lê
Sơn Tùng Lê103931498422911686980

Làm chủ Android ViewModels: Những Điều Nên Và Không Nên Làm - Phần 1

Đăng vào 1 tuần trước

• 5 phút đọc

Giới thiệu

Chào mừng bạn đến với loạt bài viết về Android ViewModels! Trong phần đầu tiên này, chúng ta sẽ khám phá những phương pháp tốt nhất để sử dụng Android ViewModels. Bài viết sẽ nhấn mạnh các điều nên làm và không nên làm nhằm nâng cao chất lượng code. Chúng ta sẽ tìm hiểu vai trò của ViewModels trong việc quản lý trạng thái giao diện người dùng (UI state) và logic nghiệp vụ, đồng thời đi sâu vào các chiến lược lazy dependency injection và tầm quan trọng của reactive programming. Ngoài ra, sẽ thảo luận về những cạm bẫy phổ biến cần tránh như khởi tạo trạng thái không đúng cách và để lộ các trạng thái có thể thay đổi.

Hiểu Về ViewModels

Theo tài liệu chính thức của Android, lớp ViewModel hoạt động như một thành phần lưu trữ logic nghiệp vụ hoặc trạng thái của một màn hình cụ thể. Mục đích chính của ViewModel là lưu trữ và duy trì trạng thái UI, dựa vào bộ nhớ đệm để không cần fetch lại dữ liệu khi có các thay đổi cấu hình. Điều này giúp giao diện người dùng của bạn không bị gián đoạn khi điều hướng giữa các activities hoặc khi có thay đổi như xoay màn hình.

Các Điểm Thảo Luận Chính

  1. Tránh khởi tạo trạng thái trong khối init {}.
  2. Tránh để lộ các mutable states.
  3. Sử dụng update{} khi làm việc với MutableStateFlows.
  4. Inject dependencies một cách lazy trong constructor.
  5. Viết code theo phong cách reactive và hạn chế sự ràng buộc.
  6. Tránh khởi tạo ViewModel từ bên ngoài.
  7. Tránh truyền tham số từ bên ngoài vào ViewModel.
  8. Không hardcode Coroutine Dispatchers.
  9. Viết Unit test cho ViewModels.
  10. Tránh để lộ các hàm suspended.
  11. Tận dụng callback onCleared() trong ViewModels.
  12. Xử lý tình huống liên quan đến thay đổi cấu hình.
  13. Inject UseCase trong Repositories và gọi DataSource phù hợp.
  14. Chỉ đưa các đối tượng domain vào ViewModels.
  15. Sử dụng các toán tử shareIn() và stateIn() để hạn chế gọi upstream nhiều lần.

1. Tránh Khởi Tạo Trạng Thái Trong Khối init {}

Việc tải dữ liệu ngay trong khối init {} của ViewModel có vẻ là một thao tác dễ dàng, nhưng nó tiềm ẩn nhiều nhược điểm. Khởi tạo dữ liệu trong khối này thường dẫn đến việc kết hợp chặt chẽ với vòng đời của ViewModel, làm giảm tính linh hoạt và khó khăn trong việc testing.

Những Nhược Điểm Chính:

  • Kết hợp chặt chẽ: Việc tải dữ liệu tự động mỗi khi ViewModel được khởi tạo có thể gây khó khăn cho việc kiểm soát thời gian và cách thức tải dữ liệu trong các ứng dụng phức tạp.
  • Khó khăn trong Testing: Bắt đầu tải dữ liệu ngay lập tức tạo ra những bất ổn cho quá trình kiểm tra, khi các yêu cầu mạng hoặc truy vấn cơ sở dữ liệu có thể không cần thiết.
  • Tính linh hoạt hạn chế: Tự động tải dữ liệu có thể cản trở khả năng đáp ứng tốt với các hoạt động hoặc tình huống từ người dùng.
  • Vấn đề quản lý tài nguyên: Tải dữ liệu ngay lập tức có thể dẫn đến việc lãng phí tài nguyên vì người dùng có thể không cần dữ liệu ngay lập tức.
  • Khả năng phản hồi UI: Nếu quá trình tải dữ liệu kéo dài, nó có thể làm giảm độ phản hồi của giao diện người dùng.

Để khắc phục những vấn đề này, hãy cố gắng sử dụng phương pháp tải dữ liệu có chủ ý hơn, như chỉ khởi động quá trình nạp dữ liệu khi cần thiết.

Ví dụ về Anti-Pattern

Ví dụ 1

kotlin Copy
class SearchViewModel @Inject constructor(
    private val wordsUseCase: GetWordsUseCase,
) : ViewModel() {

    data class UiState(
        val isLoading: Boolean,
        val words: List<String> = emptyList()
    )
    
    init {
        getWords()
    }

    val _state = MutableStateFlow(UiState(isLoading = true))
    val state: StateFlow<UiState>
        get() = _state.asStateFlow()

    private fun getWords() {
        viewModelScope.launch {
            _state.update { UiState(isLoading = true) }
            val words = wordsUseCase.invoke()
            _state.update { UiState(isLoading = false, words = words) }
        }
    }
}

Trong đoạn mã trên, dữ liệu được tải ngay trong khối init, dẫn đến sự kết hợp với quá trình tạo ViewModel và thiếu linh hoạt. Để cải thiện, hãy xem xét việc sử dụng cách tiếp cận lazy hơn.

Cách Tiếp Cận Khác Tốt Hơn

kotlin Copy
class SearchViewModel @Inject constructor(
    private val wordsUseCase: GetWordsUseCase,
) : ViewModel() {
    
    data class UiState(
        val isLoading: Boolean = true,
        val words: List<String> = emptyList()
    )
    
    val state: StateFlow<UiState> = flow { 
        emit(UiState(isLoading = true))
        val words = wordsUseCase.invoke()
        emit(UiState(isLoading = false, words = words))
    }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), UiState())
}

Việc refactor loại bỏ khởi tạo dữ liệu trong init block và thay vào đó, tải dữ liệu khi cần, làm tăng tính linh hoạt và khả năng phản hồi của ứng dụng.

Kết Luận

Trong bài viết này, chúng ta đã khám phá lý do tại sao việc tải dữ liệu trong khối init {} có thể gây cản trở tiến trình của ViewModel. Bằng cách áp dụng các phương pháp tốt hơn để quản lý dữ liệu, bạn có thể tối ưu hóa hiệu suất và độ ổn định của ứng dụng Android của mình. Cảm ơn bạn đã theo dõi, hẹn gặp lại trong bài viết tiếp theo!
source: viblo

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào