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): 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) |
Lớp giao diện người dùng 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à bộ công cụ hiện đại, được khuyên dùng để xây dựng giao diện người dùng cho ứng dụng của bạn.
|
| Hiển thị dữ liệu ứng dụng từ lớp dữ liệu bằng cách dùng một kho lưu trữ.
Strongly recommended (Rất nên dùng) |
Đảm bảo rằng 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. Sau đây là một số 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 với 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 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.
Để biết thêm thông tin về các phương pháp hay nhất về ViewModel, hãy xem phần Đề xuất về cấu trúc. Để biết thêm thông tin về lợi ích của ViewModel, hãy xem phần 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 Điều hướng 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 | 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, đừng 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, 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 những thành phần sau:
|
| 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:
Đối với các thành phần kết hợp phức tạp hơn hoặc những thành phần có hành vi động dựa trên trạng thái, hãy sử dụng |
| 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 làm như vậ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 sử 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 để ViewModel hiển thị dữ liệu cho giao diện người dùng thông qua một thuộc tính duy nhất 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:
| Nội dung đề xuất | 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 Activity.
Strongly recommended (Rất nên dùng) |
Không ghi đè các phương thức vòng đời
|
Đ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
Hãy 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 đủ phức tạp (ví dụ: nếu dự án có chứa 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ử dự án. Ít nhất, hãy thêm những thông tin sau:
|
| Ư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 dữ liệu giả, 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 phần Nội dung cần kiểm thử trong Android và Kiểm thử bố cục Compose.
Mô hình
Hãy á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 các 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 các 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 ở dạng 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. Sử dụng 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.
Thêm tiền tố Fake vào các hoạt động triển khai giả, 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: