Đề xuất về cấu trúc Android (Khung hiển thị)

Các khái niệm và cách triển khai Jetpack Compose

Trang này trình bày một số nội dung đề xuất và phương pháp hay nhất về cấu trúc. Hãy áp dụng các nội dung đề xuất đó để cải thiện chất lượng, độ mạnh và khả năng mở rộng của ứng dụng. Các nội dung đề xuất này cũng giúp bạn bảo trì và kiểm thử ứng dụng dễ dàng hơn.

Lớp giao diện người dùng

Vai trò của lớp giao diện người dùng là hiển thị dữ liệu ứng dụng trên màn hình và đóng vai trò là điểm chính trong quá trình tương tác của người dùng. Dưới đây là một số phương pháp hay nhất cho lớp giao diện người dùng:

  • Bạn nên tạo các kho lưu trữ ngay cả khi chúng chỉ chứa một nguồn dữ liệu duy nhất.
  • Trong ứng dụng nhỏ, bạn có thể chọn đặt các loại lớp dữ liệu vào gói hoặc mô-đun data.

Nội dung đề xuất

Nội dung mô tả

Tuân theo nguyên tắc Luồng dữ liệu một chiều (UDF).

Rất nên dùng

Tuân theo nguyên tắc Luồng dữ liệu một chiều (UDF), trong đó ViewModel hiển thị trạng thái giao diện người dùng thông qua mẫu trình quan sát và nhận các thao tác từ giao diện người dùng thông qua lệnh gọi phương thức.

Dùng ViewModel AAC nếu có lợi cho ứng dụng của bạn.

Rất nên dùng

Dùng AAC ViewModels để xử lý logic kinh doanh, cũng như tìm nạp dữ liệu ứng dụng để hiển thị trạng thái giao diện người dùng cho giao diện người dùng.

Xem thêm các phương pháp hay nhất về ViewModel tại đây.

Xem các lợi ích của ViewModel tại đây.

Dùng bộ sưu tập trạng thái giao diện người dùng có nhận biết vòng đời.

Rất nên dùng

Thu thập trạng thái giao diện người dùng từ giao diện người dùng bằng trình tạo coroutine có nhận biết vòng đời thích hợp, repeatOnLifecycle.

Đọc thêm về repeatOnLifecycle.

Không gửi các sự kiện từ ViewModel đến giao diện người dùng.

Rất nên dùng

Xử lý sự kiện ngay lập tức trong ViewModel và cập nhật trạng thái bằng kết quả xử lý sự kiện. Tìm hiểu thêm về sự kiện giao diện người dùng tại đây.

Dùng ứng dụng hoạt động đơn.

Đề xuất

Dùng Mảnh điều hướng để di chuyển giữa các màn hình và liên kết sâu đến ứng dụng của bạn nếu ứng dụng có nhiều màn hình.

Đoạn mã sau đây chỉ ra cách thu thập trạng thái giao diện người dùng theo cách có nhận biết vòng đời:

class MyFragment : Fragment() {

    private val viewModel: MyViewModel by viewModel()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        viewLifecycleOwner.lifecycleScope.launch {
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect {
                    // Process item
                }
            }
        }
    }
}

ViewModel

ViewModel chịu trách nhiệm cung cấp trạng thái giao diện người dùng và quyền truy cập vào lớp dữ liệu. Dưới đây là một số phương pháp hay nhất về ViewModel:

Nội dung đề xuất

Nội dung mô tả

ViewModel phải độc lập với vòng đời của Android.

Rất nên dùng

ViewModel không được chứa tệp tham chiếu đến bất kỳ kiểu nào liên quan đến Vòng đời. Không truyền Activity, Fragment, Context hoặc Resources làm phần phụ thuộc. Nếu cần Context trong ViewModel, bạn nên đánh giá xem liệu ViewModel có thuộc đúng lớp (layer) hay không.

Dùng coroutine và luồng.

Rất nên dùng

ViewModel tương tác với các lớp dữ liệu hoặc miền thông qua:

  • Các luồng Kotlin để nhận dữ liệu ứng dụng,
  • Các hàm suspend thực hiện các thao tác bằng viewModelScope.

Dùng ViewModel ở cấp màn hình.

Rất nên dùng

Không sử dụng ViewModel trong các phần giao diện người dùng có thể tái sử dụng. Bạn nên sử dụng ViewModel trong:

  • Activities/Fragments (Hoạt động/Mảnh) trong Khung hiển thị
  • Đích đến hoặc biểu đồ khi sử dụng Jetpack Navigation.

Không sử dụng AndroidViewModel.

Rất nên dùng

Dùng lớp ViewModel, chứ không phải AndroidViewModel. Không nên dùng lớp Application trong ViewModel. Thay vào đó, hãy di chuyển phần phụ thuộc sang giao diện người dùng hoặc lớp dữ liệu.

Hiển thị trạng thái giao diện người dùng.

Đề xuất

ViewModel phải hiển thị dữ liệu cho giao diện người dùng thông qua một thuộc tính có tên là uiState. Nếu giao diện người dùng hiển thị nhiều phần dữ liệu không liên quan, ViewModel có thể hiển thị nhiều thuộc tính trạng thái giao diện người dùng.

  • Bạn nên đặt uiState làm StateFlow.
  • Bạn nên tạo uiState bằng toán tử stateIn dựa vào chính sách WhileSubscribed(5000) (ví dụ) nếu có dữ liệu dưới dạng luồng dữ liệu từ các lớp khác trong hệ phân cấp.
  • Đối với các trường hợp đơn giản hơn mà không có luồng dữ liệu nào đến từ lớp dữ liệu, bạn có thể sử dụng MutableStateFlow hiển thị dưới dạng StateFlow không thể thay đổi.
  • Bạn có thể chọn dùng ${Screen}UiState làm lớp dữ liệu có thể chứa dữ liệu, lỗi và tín hiệu tải. Lớp này cũng có thể là một lớp kín nếu các trạng thái khác nhau là loại trừ lẫn nhau.

Đoạn mã sau đây trình bày cách hiển thị trạng thái giao diện người dùng từ ViewModel:

@HiltViewModel
class BookmarksViewModel @Inject constructor(
    newsRepository: NewsRepository
) : ViewModel() {

    val feedState: StateFlow<NewsFeedUiState> =
        newsRepository
            .getNewsResourcesStream()
            .mapToFeedState(savedNewsResourcesState)
            .stateIn(
                scope = viewModelScope,
                started = SharingStarted.WhileSubscribed(5_000),
                initialValue = NewsFeedUiState.Loading
            )

    // ...
}

Vòng đời

Sau đây là một số phương pháp hay nhất để xử lý vòng đời của Android:

Nội dung đề xuất

Nội dung mô tả

Không ghi đè các phương thức vòng đời trong Activity (Hoạt động) hoặc Fragment (Mảnh).

Rất nên dùng

Không ghi đè các phương thức vòng đời, chẳng hạn như onResume trong Activity (Hoạt động) hoặc Fragment (Mảnh). Thay vào đó, hãy sử dụng LifecycleObserver. Nếu ứng dụng cần thực hiện công việc khi vòng đời đạt đến một Lifecycle.State nhất định, hãy sử dụng API repeatOnLifecycle.

Đoạn mã sau đây trình bày cách thực hiện các thao tác dựa trên một trạng thái Vòng đời nhất định:

class MyFragment: Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
            override fun onResume(owner: LifecycleOwner) {
                // ...
            }
            override fun onPause(owner: LifecycleOwner) {
                // ...
            }
        }
    }
}