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.
Các phương pháp hay nhất dưới đây được nhóm theo chủ đề. Mỗi nhóm sẽ có một mức ưu tiên phản ánh mức độ đề xuất riêng. Dưới đây là danh sách mức ưu tiên:
- Strongly recommended (Rất nên dùng): Bạn nên triển khai phương pháp này, trừ phi phương pháp đó xung đột với cách làm của bạn.
- Recommended (Nên dùng): Phương pháp này có thể giúp cải thiện ứng dụng của bạn.
- Optional (Không bắt buộc): Phương pháp này có thể cải thiện ứng dụng của bạn trong một số trường hợp nhất định.
Cấu trúc phân lớp
Cấu trúc phân lớp được đề xuất của chúng tôi ưu tiên tách biệt các mối quan ngại. Cấu trúc này điều khiển giao diện người dùng qua mô hình dữ liệu, tuân thủ nguyên tắc một nguồn đáng tin cậy và tuân theo nguyên tắc luồng dữ liệu một chiều. Dưới đây là một số phương pháp hay nhất về cấu trúc phân lớp:
| Nội dung đề xuất | Nội dung mô tả |
|---|---|
| Dùng lớp dữ liệu được xác định rõ ràng.
Strongly recommended (Rất nên dùng) |
Lớp dữ liệu hiển thị dữ liệu ứng dụng với phần còn lại của ứng dụng và chứa phần lớn logic nghiệp vụ của ứng dụng.
|
| Dùng lớp giao diện người dùng được xác định rõ ràng.
Strongly recommended (Rất nên dùng) |
Vai trò của lớp giao diện người dùng UI layer 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. Jetpack Compose là một bộ công cụ hiện đại, được khuyên dùng để xây dựng giao diện người dùng của ứng dụng.
|
| Hiển thị dữ liệu ứng dụng từ lớp dữ liệu bằng cách dùng kho lưu trữ.
Strongly recommended (Rất nên dùng) |
Đảm bảo các thành phần trong lớp giao diện người dùng, chẳng hạn như thành phần kết hợp hoặc ViewModel không tương tác trực tiếp với nguồn dữ liệu. Ví dụ về nguồn dữ liệu:
|
| Dùng coroutine và luồng.
Strongly recommended (Rất nên dùng) |
Dùng coroutine và luồng để giao tiếp giữa các lớp.
Để biết thêm thông tin về các phương pháp hay nhất cho coroutine, hãy xem bài viết Các phương pháp hay nhất cho coroutine trong Android. |
| Dùng lớp miền.
Recommended in big apps (Nên dùng trong các ứng dụng lớn) |
Dùng lớp miền, các trường hợp sử dụng nếu bạn cần dùng lại logic nghiệp vụ để tương tác với lớp dữ liệu trên nhiều ViewModel hoặc bạn muốn đơn giản hoá logic nghiệp vụ của một ViewModel cụ thể |
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:
| 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).
Strongly recommended (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.
Strongly recommended (Rất nên dùng) |
Dùng AAC ViewModel để xử lý logic nghiệp vụ, 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.
Để biết thêm thông tin về các phương pháp hay nhất cho ViewModel, hãy xem bài viết Nội dung đề xuất về cấu trúc. Để biết thêm thông tin về lợi ích của ViewModel, hãy xem bài viết ViewModel với tư cách là phần tử giữ trạng thái logic nghiệp vụ. |
| 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.
Strongly recommended (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, collectAsStateWithLifecycle.
Đọc thêm về |
| Không gửi các sự kiện từ ViewModel đến giao diện người dùng.
Strongly recommended (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. Để biết thêm thông tin về các sự kiện trên giao diện người dùng, hãy xem bài viết Xử lý sự kiện ViewModel. |
| Dùng ứng dụng hoạt động đơn.
Strongly recommended (Rất nên dùng) |
Dùng Navigation 3 để 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. |
| Dùng Jetpack Compose.
Strongly recommended (Rất nên dùng) |
Dùng Jetpack Compose để tạo ứng dụng mới cho điện thoại, máy tính bảng, thiết bị có thể gập lại và Wear OS. |
Đ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:
@Composable
fun MyScreen(
viewModel: MyViewModel = viewModel()
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
}
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ả |
|---|---|
| Giữ cho ViewModel độc lập với vòng đời của Android.
Strongly recommended (Rất nên dùng) |
Trong ViewModel, không giữ 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, Context hoặc Resources làm phần phụ thuộc.
Nếu cần Context trong ViewModel, hãy đánh giá cẩn thận xem liệu ViewModel có thuộc đúng lớp (layer) hay không. |
| Dùng coroutine và luồng.
Strongly recommended (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:
|
| Dùng ViewModel ở cấp màn hình.
Strongly recommended (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:
|
| Dùng các lớp của phần tử giữ trạng thái thuần tuý trong các thành phần giao diện người dùng có thể tái sử dụng.
Strongly recommended (Rất nên dùng) |
Dùng các lớp của phần tử giữ trạng thái thuần tuý để xử lý độ phức tạp của các thành phần trên giao diện người dùng có thể tái sử dụng. Khi bạn thực hiện việc này, trạng thái có thể được chuyển lên trên và kiểm soát bên ngoài. |
Không sử dụng AndroidViewModel.
Recommended (Nên dùng) |
Dùng lớp ViewModel, chứ không phải AndroidViewModel. Không 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.
Recommended (Nên dùng) |
Hãy làm cho ViewModel 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, máy ảo có thể hiển thị nhiều thuộc tính trạng thái giao diện người dùng.
|
Đ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
Làm theo các phương pháp hay nhất để xử lý vòng đời của Activity lifecycle:
| Nội dung đề xuất | Nội dung mô tả |
|---|---|
Sử dụng các hiệu ứng nhận biết vòng đời trong thành phần kết hợp thay vì ghi đè các phương thức gọi lại trong vòng đời của Activity.
Strongly recommended (Rất nên dùng) |
Không ghi đè các phương thức vòng đời của
|
Đ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:
@Composable
fun LocationChangedEffect(
locationManager: LocationManager,
onLocationChanged: (Location) -> Unit
) {
val currentOnLocationChanged by rememberUpdatedState(onLocationChanged)
LifecycleStartEffect(locationManager) {
val listener = LocationListener { newLocation ->
currentOnLocationChanged(newLocation)
}
try {
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
1000L,
1f,
listener,
)
} catch (e: SecurityException) {
// TODO: Handle missing permissions
}
onStopOrDispose {
locationManager.removeUpdates(listener)
}
}
}
Xử lý các phần phụ thuộc
Làm theo các phương pháp hay nhất khi quản lý các phần phụ thuộc giữa các thành phần:
| Nội dung đề xuất | Nội dung mô tả |
|---|---|
| Dùng tính năng chèn phần phụ thuộc.
Strongly recommended (Rất nên dùng) |
Áp dụng các phương pháp hay nhất về kỹ thuật chèn phần phụ thuộc, chủ yếu là chèn hàm khởi tạo khi có thể. |
| Xác định phạm vi ở một thành phần khi cần.
Strongly recommended (Rất nên dùng) |
Xác định phạm vi ở một vùng chứa phần phụ thuộc khi loại đó chứa dữ liệu có thể thay đổi cần được chia sẻ hoặc loại cần khởi chạy gây tốn kém và được sử dụng rộng rãi trong ứng dụng. |
| Dùng Hilt.
Recommended (Nên dùng) |
Dùng Hilt hoặc kỹ thuật chèn phần phụ thuộc theo cách thủ công trong các ứng dụng đơn giản. Dùng Hilt nếu dự án của bạn khá phức tạp, chẳng hạn như nếu dự án đó có bất kỳ nội dung nào sau đây:
|
Thử nghiệm
Sau đây là một số phương pháp hay nhất để kiểm thử:
| Nội dung đề xuất | Nội dung mô tả |
|---|---|
| Nắm rõ nội dung cần kiểm thử.
Strongly recommended (Rất nên dùng) |
Trừ phi dự án này đơn giản như ứng dụng "hello world", hãy kiểm thử ứng dụng. Cung cấp ít nhất:
|
| Ưu tiên loại kiểm thử fake hoặc mock (giả hoặc mô phỏng).
Strongly recommended (Rất nên dùng) |
Để biết thêm thông tin về cách sử dụng loại kiểm thử fake, hãy xem bài viết Dùng đối tượng kiểm thử trong Android. |
| Kiểm thử StateFlow.
Strongly recommended (Rất nên dùng) |
Khi kiểm thử StateFlow, hãy làm như sau:
|
Để biết thêm thông tin, hãy xem bài viết Nội dung cần kiểm thử trong Android và Kiểm thử bố cục Compose.
Mô hình
Áp dụng các phương pháp hay nhất sau đây khi phát triển mô hình trong ứng dụng của bạn:
| Nội dung đề xuất | Nội dung mô tả |
|---|---|
| Tạo một mô hình trên mỗi lớp trong các ứng dụng phức tạp.
Recommended (Nên dùng) |
Trong các ứng dụng phức tạp, hãy tạo mô hình mới ở các lớp hoặc thành phần khác nhau khi thích hợp. Hãy xem các ví dụ sau đây:
|
Quy ước đặt tên
Khi đặt tên cho bộ mã, bạn nên nắm được các phương pháp hay nhất sau đây:
| Nội dung đề xuất | Nội dung mô tả |
|---|---|
| Đặt tên cho phương thức.
Không bắt buộc |
Sử dụng cụm động từ để đặt tên cho phương thức, ví dụ: makePayment(). |
| Đặt tên cho thuộc tính.
Không bắt buộc |
Sử dụng cụm danh từ để đặt tên cho thuộc tính, ví dụ: inProgressTopicSelection. |
| Đặt tên cho luồng dữ liệu.
Không bắt buộc |
Khi một lớp hiển thị luồng Quy trình hoặc bất kỳ luồng nào khác, quy ước đặt tên là get{model}Stream. Ví dụ: getAuthorStream(): Flow<Author>.
Nếu hàm trả về danh sách mô hình, hãy sử dụng tên mô hình số nhiều: getAuthorsStream(): Flow<List<Author>>. |
| Đặt tên cho việc triển khai giao diện.
Không bắt buộc |
Sử dụng tên có ý nghĩa cho việc triển khai giao diện. Đặt Default làm tiền tố nếu không tìm thấy tên phù hợp hơn. Ví dụ: đối với giao diện NewsRepository, bạn có thể dùng tên OfflineFirstNewsRepository hoặc InMemoryNewsRepository. Nếu bạn không tìm thấy tên phù hợp, hãy dùng tên DefaultNewsRepository.
Tiền tố của việc triển khai giả phải là Fake, như trong FakeAuthorsRepository. |
Tài nguyên khác
Để biết thêm thông tin về cấu trúc Android, hãy xem các tài nguyên khác sau đây: