Chuyển đến nội dung chính

Architecture in Jetpack Compose — MVP, MVVM, & MVI

 


Được viết theo beta01. Compose đang phát triển nhanh chóng nên một số cú pháp có thể đã thay đổi.


Kiến trúc ứng dụng đã đi một chặng đường dài trong thế giới Android trong thập kỷ qua. Từ các hoạt động thần thánh đến MVP đến MVVM đến MVI với một loạt các xe buýt sự kiện, clean architecture và một loạt các mô hình khác cho chuyến đi.


Với Jetpack Compose có khả năng đạt ổn định vào năm 2021, chúng tôi có thể giả định rằng vài năm tới sẽ mang đến một loạt các mẫu mới cho Android khi giao diện người dùng khai báo thay đổi cách chúng tôi phát triển ứng dụng.


Bài viết này đưa ra một thử nghiệm xem xét cách các mẫu kiến trúc mà tất cả chúng ta đều quen thuộc chuyển vào mô hình Compose.


  1. Ứng dụng

Không có gì phức tạp cho thử nghiệm này, tôi chỉ muốn kiểm tra logic trích xuất từ lớp giao diện người dùng. Vì vậy, ứng dụng đặt một câu hỏi thực hiện một số xác nhận trên câu trả lời và hiển thị màn hình kết quả cho biết bạn đúng hay sai.



Khi gửi câu trả lời, ứng dụng cũng gọi một dịch vụ API giả mạo nên có trạng thái tải cần được hiển thị trước khi chuyển đến màn hình kết quả.


class AnswerService {

suspend fun save(answer: String) {
Log.v("Api call", "Make a call to an api")
delay(1000)
}
}

2. Ngành kiến trúc? Kiến trúc gì?

Để bắt đầu, chúng ta hãy xem điều gì sẽ xảy ra khi chúng ta bỏ qua kiến trúc và thử và gây nhiễu tất cả logic của chúng ta trong layer compose UI.


Có thể xảy ra— nếu khó hơn một chút so với android truyền thống - và kết quả xấu như bạn mong đợi, vì vậy tôi sẽ không đưa vào các đoạn trích, nhưng nếu bạn muốn xem thử các mẫu ở đây.


Có một chút thú vị cho thí nghiệm này. Vì các tác phẩm có thể chạy nhiều lần — thậm chí hàng trăm lần trong trường hợp hoạt ảnh - tất cả khởi tạo đối tượng hoặc logic cần được gói gọn trong bộ nhớ {} để đảm bảo công việc không chạy tốn kém cho mỗi lần bố cục lại (trong một số trường hợp, bạn cũng có thể sử dụng Các hiệu ứng).


val answerService = remember { AnswerService() }

val textToDisplay = remember {
if (isCorrect) {
"You've heard too many cheese jokes"
} else {
"Nacho cheese"
}
}


Điều này thật tuyệt vì mỗi khi bạn viết, hãy nhớ rằng nó buộc bạn phải đặt câu hỏi liệu mã đó có nên ở đó ngay từ đầu hay không. Điều này không có nghĩa là bạn không nên sử dụng ghi nhớ - đó là một phần cốt lõi của những gì tạo nên chức năng Compose - nhưng thường nếu bạn đang bao hàm logic ứng dụng, hãy nhớ rằng có một cách khác, tốt hơn để đạt được kết quả tương tự.


Và như bạn sẽ thấy từ mẫu, logic gây nhiễu trong quá trình Compose dẫn đến mã lộn xộn và khó bảo trì. Có thể nói rằng điều này sẽ không mở rộng quy mô tốt cho bất kỳ thứ gì ngoài một nhóm màn hình trong nguyên mẫu. Nhưng dù sao, trong một số trường hợp, đó là tất cả những gì bạn cần và không có ích gì khi đi sâu vào các mẫu kiến trúc.


3. Tổng quan về mẫu


MVP, MVVM và MVI là một số mẫu phổ biến trong Android và chúng có thể được coi là các hương vị khác nhau của cùng một khái niệm cốt lõi - trừu tượng hóa logic từ giao diện người dùng thành các lớp mà không cần tham chiếu đến Android. Cách chính mà chúng khác nhau là cách các lớp đó chấp nhận đầu vào của chúng và cách chúng xuất ra các bản cập nhật cho giao diện người dùng.


Tất nhiên, có nhiều biến thể tinh tế của mỗi mẫu tùy thuộc vào vấn đề mà họ đang giải quyết, thư viện nào được đưa vào hoạt động và sở thích của các nhà phát triển thực hiện dự án - không thể bao gồm tất cả các khả năng. Đối với thử nghiệm này, tôi sẽ cố gắng ước tính các cách triển khai phổ biến nhất của các mẫu để đưa ra ý tưởng sơ bộ về cách chúng hoạt động trong Compose.


  • MVP

Tính năng xác định của MVP là dữ liệu được truyền đạt trở lại chế độ xem một cách bắt buộc. Thật kỳ lạ, điều này có khả năng không thể thực hiện trong Compose. Chúng ta hãy xem tại sao.



Để cập nhật giao diện người dùng theo thứ bậc, người thuyết trình dựa vào việc có tham chiếu đến chế độ xem, thường là một hoạt động hoặc phân đoạn được đưa vào người trình bày dưới dạng giao diện. Người trình bày có thể gọi các chức năng trên giao diện để gây ra các thay đổi đối với giao diện người dùng.


Nhưng xem nhanh một bản có thể tổng hợp cho thấy không có giá trị trả lại nào - vì vậy không có tham chiếu chế độ xem nào để chuyển cho người trình bày của chúng tôi.


@Composable
fun HomeScreen() {
Column {
Text("Hello World!")
}
}

Từ tài liệu Compose:


Hàm không trả về bất kỳ thứ gì.Chức năng Compose phát ra giao diện người dùng không cần phải trả về bất cứ điều gì, bởi vì chúng mô tả trạng thái màn hình mong muốn thay vì xây dựng các widget giao diện người dùng.


Compose vẫn là một phần của Android, vì vậy hệ thống phân cấp sẽ luôn bắt đầu từ một hoạt động hoặc phân đoạn, vì vậy về mặt lý thuyết có thể sử dụng đối tượng Android gốc này làm tham chiếu chế độ xem và gọi điều này từ những người trình bày trước khi sắp xếp lại toàn bộ cây Compose. Mặc dù điều đó sẽ dẫn đến một số đối tượng xem Frankenstein quái dị. Chưa kể đến hiệu suất & UX kém cỏi khủng khiếp từ việc tạo lại toàn bộ giao diện người dùng màn hình chỉ với những thay đổi nhỏ - đánh bại một trong những lợi ích lớn của tính năng Compose.


Cũng có thể có tùy chọn sử dụng Composition Locals theo một cách nào đó để làm cho MVP hoạt động, mặc dù nó có vẻ hơi lạm dụng và nếu bạn đang đặt dữ liệu để kích hoạt bố cục lại, bạn cũng có thể cắt bỏ người trung gian và sử dụng MVVM.


Vì vậy, có lẽ an toàn khi nói rằng nếu bạn hào hứng với việc tích hợp Compose vào ứng dụng dựa trên MVP của mình thì nơi bắt đầu có thể là di chuyển khỏi MVP. Điều này cũng được đề xuất trong tài liệu Compose:


Các mẫu kiến trúc Unidirectional Data Flow (UDF) hoạt động liền mạch với Compose. Nếu ứng dụng sử dụng các loại mẫu kiến trúc khác thay thế, chẳng hạn như Model View Presenter (MVP), chúng tôi khuyên bạn nên di chuyển phần giao diện người dùng đó sang UDF trước hoặc trong khi sử dụng tính năng Compose.


  • MVVM

Điểm MVVM khác với MVP là ở cách dữ liệu được truyền tải trở lại chế độ xem. Thay vì gọi chế độ xem một cách ngụ ý, trong MVVM các thay đổi được mô tả bằng dữ liệu mà sau đó chế độ xem có thể quan sát được. Sau đó, giao diện người dùng có thể tự biên soạn lại dựa trên dữ liệu mới.


Nhưng mã để làm cho điều này xảy ra trông như thế nào? (đầy đủ mẫu tại đây) Chúng tôi sẽ thực hiện việc này mà không cần sử dụng Jetpack ViewModel vì bạn có sử dụng Jetpack ViewModel hay không, phần lớn mã sẽ giữ nguyên. Điều duy nhất thay đổi với Jetpack ViewModel là tạo VM & vòng đời VM.


class MvvmActivity : AppCompatActivity() {

private val mvvmViewModel = MvvmViewModel(AnswerService())

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeListPlaygroundTheme {
MvvmApp(mvvmViewModel)
}
}
}
}

Cây Compose sẽ bắt đầu trong hệ thống phân cấp của Android vì vậy Activity là một nơi tốt như bất kỳ nơi nào để bắt đầu. Điều thú vị là trong ứng dụng Compose 100% bạn sẽ chỉ có một hoạt động duy nhất và tất cả các màn hình của bạn sẽ được xác định bởi các điểm đến có thể tổng hợp - adios Fragment 👋.


Activity cũng là nơi bạn có thể xử lý việc đưa vào đồ thị đối tượng - ít nhất là trong các dự án nhỏ. Khi các dự án mở rộng, rất có thể bạn sẽ muốn mở rộng máy ảo đến một số màn hình hoặc nhóm màn hình nhất định. Trong trường hợp đó, bạn sẽ muốn tạo VM một cách lười biếng trong các bảng tổng hợp biểu đồ điều hướng - mặc dù tôi sẽ không đi sâu vào điều đó trong các mẫu ở đây.


@Composable
fun MvvmApp(
mvvmViewModel: MvvmViewModel
) {
val navController = rememberNavController()
NavHost(navController, startDestination = "question") {
composable("question") {
MvvmQuestionDestination(
mvvmViewModel = mvvmViewModel,
// You could pass the nav controller to further composables,
// but I like keeping nav logic in a single spot by using the hoisting pattern
// hoisting probably won't work as well in deep hierarchies,
// in which case CompositionLocal might be more appropriate
onConfirm = { navController.navigate("result") },
)
}
composable("result") {
MvvmResultDestination(
mvvmViewModel,
)
}
}
}

Điều hướng sẽ xảy ra gần gốc cây của bạn, ở đây thư viện điều hướng sẽ giải quyết công việc chuyển đổi các điểm đến vào và ra trên cây và trông nom đống đồ đạc. Đó cũng là nơi các mô hình chế độ xem được chuyển đến các điểm đến Compose của bạn.


Trong mẫu đơn giản này, tôi đã tạo trước ViewModel theo cách thủ công trong Activity Trong các dự án lớn hơn với phạm vi VM, bạn có thể sẽ muốn tạo các mô hình xem của mình tại đây. Bạn cũng có thể sẽ sử dụng khung DI trong các dự án lớn hơn. Không có cách nào trực tiếp để đưa vào Composables, vì vậy bạn sẽ cần một cách nào đó để tạo biểu đồ đối tượng một cách lười biếng trên cơ sở phạm vi.


Trong một dự án lớn hơn, tôi sử dụng Dagger và thực hiện việc này bằng cách đưa các nhà cung cấp vào Activity (ví dụ: Nhà cung cấp <MyViewModel>) và tạo các đối tượng một cách lười biếng trong biểu đồ điều hướng nơi chúng có thể được xác định phạm vi đến các điểm đến đơn lẻ hoặc các điểm đến lồng nhau.


Tiếp theo các điểm đến (hoặc màn hình) của ứng dụng nhỏ của chúng tôi.


@Composable
fun MvvmQuestionDestination(
mvvmViewModel: MvvmViewModel,
onConfirm: () -> Unit
) {
val textFieldState = remember { mutableStateOf(TextFieldValue()) }

// We only want the event stream to be attached once
// even if there are multiple re-compositions
LaunchedEffect("key") { // probably is a better way to set the key than hardcoding key...
mvvmViewModel.navigateToResults
.onEach { onConfirm() }
.collect()
}

Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.SpaceEvenly,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(text = "What do you call a mexican cheese?")
TextField(
value = textFieldState.value,
onValueChange = { textFieldState.value = it }
)
if (mvvmViewModel.isLoading.collectAsState().value) {
CircularProgressIndicator()
} else {
Button(onClick = { mvvmViewModel.confirmAnswer(textFieldState.value.text) }) {
Text(text = "Confirm")
}
}
}
}

@Composable
fun MvvmResultDestination(
mvvmViewModel: MvvmViewModel
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.SpaceEvenly,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(text = mvvmViewModel.textToDisplay.collectAsState().value)
}
}

Chúng là những màn hình khá đơn giản, vì vậy không có gì phức tạp trong cách giao diện người dùng. Những điều thú vị cần lưu ý là:

- Mỗi vật phẩm tổng hợp có một CoroutineScope gắn liền với vòng đời của nó. LaunchedEffect sẽ chạy trên phạm vi đó. Vì vậy, việc hủy bỏ được xử lý tự động cho chúng tôi khi vật liệu có thể kết hợp rời khỏi cây - không cần tấm lót!

- Mọi thứ bên trong LaunchedEffect sẽ chỉ được chạy một lần. Vì vậy, không cần phải lo lắng rằng mã sẽ chạy lại giữa các bản phối lại của cùng một bản tổng hợp.

- collectAsState().value là một trình bao bọc nhỏ được cung cấp cho chúng tôi để kết nối dòng chảy của kotlin vào thế giới Compose. Mỗi khi một giá trị mới được phát ra, các phần thích hợp của chế độ xem này sẽ (một cách thông minh) biên soạn lại với dữ liệu mới nhận được.

Và cuối cùng chúng ta hãy xem xét mô hình chế độ xem.


class MvvmViewModel(
private val answerService: AnswerService,
) {

private val coroutineScope = MainScope()
private val _isLoading: MutableStateFlow<Boolean> = MutableStateFlow(false)
val isLoading = _isLoading.asStateFlow()
private val _textToDisplay: MutableStateFlow<String> = MutableStateFlow("")
val textToDisplay = _textToDisplay.asStateFlow()
// See https://proandroiddev.com/android-singleliveevent-redux-with-kotlin-flow-b755c70bb055
// For why channel > SharedFlow/StateFlow in this case
private val _navigateToResults = Channel<Boolean>(Channel.BUFFERED)
val navigateToResults = _navigateToResults.receiveAsFlow()

fun confirmAnswer(answer: String) {
coroutineScope.launch {
_isLoading.value = true
withContext(Dispatchers.IO) { answerService.save(answer) }
val text = if (answer == "Nacho cheese") {
"You've heard too many cheese jokes"
} else {
"Nacho cheese"
}
_textToDisplay.emit(text)
_navigateToResults.send(true)
_isLoading.value = false
}
}
}

Tất cả logic của chúng tôi được trừu tượng hóa và có thể kiểm tra được, trạng thái chế độ xem dữ liệu được truyền tải trở lại chế độ xem với MutableStateFlow, các sự kiện ảnh đơn như chuyển hướng / snackbars / v.v. được thông báo với lớp xem bằng Channel và mọi công việc đang chạy lâu dài đều có thể được giảm tải xuống các quy trình nền - thành công 🎊!

Nhìn chung MVVM rất phù hợp với Compose và nếu ứng dụng của bạn đã được xây dựng với cấu trúc MVVM, việc tích hợp Compose cũng đơn giản như chuyển giao diện người dùng Android truyền thống của bạn cho Compose. Các mô hình chế độ xem của bạn phần lớn vẫn giữ nguyên.

  • Jetpack ViewModel

Khi sử dụng Jetpack’s ViewModel, sẽ có rất ít thay đổi so với ví dụ trên như các chức năng giao tiếp theo cùng một cách. Vì vậy, tôi sẽ không hiển thị các đoạn mã ở đây vì lợi ích tránh lặp lại. Mặc dù hãy kiểm tra toàn bộ mẫu ở đây nếu bạn quan tâm.


Thay đổi chính là cách ViewModel được tạo. Tôi đã sử dụng Hilt, mặc dù bạn có thể thay đổi mã để phù hợp với khung DI ưa thích của bạn (hoặc không có DI nào cả). Vì vậy, với một vài chú thích Hilt và val jetpackMvvmViewModel: JetpackMvvmViewModel by viewModel (), bạn có thể sử dụng và nhận được các lợi ích bổ sung của vòng đời mở rộng của Jetpack ViewModel và tích hợp đơn giản với các thư viện Jetpack khác.


Một điều cần lưu ý là trong mẫu, tôi tạo ViewModel trong Activity và chuyển nó vào thế giới Compose. Trong bất cứ điều gì ngoài một dự án nhỏ, điều này sẽ không thực sự khả thi.

Có vẻ như từ đây và ở đây, Jetpack ViewModel sẽ dễ dàng tạo và tích hợp phạm vi với thư viện Compose & điều hướng — tuy nhiên, tôi không thể làm cho mã mẫu chơi thành công. Nếu bạn có thể làm tốt hơn tôi và làm cho công việc này thành công, thì việc tích hợp Compose đơn giản chắc chắn là một điểm cộng lớn khi sử dụng Jetpack ViewModels. Việc tích hợp giữa các thư viện jetpack vẫn còn rất nhiều alpha, vì vậy điều này chắc chắn sẽ được cải thiện trong tương lai gần.

  • MVI

Đây là một mô hình rất giống với MVVM, với một số khác biệt quan trọng. Theo nhiều cách, đó là sự hợp nhất của MVVM và Redux.


MVI xác định trước một lựa chọn các sự kiện mà ViewModel xử lý và chế độ xem xuất bản một luồng các sự kiện này khi chúng xảy ra. Nhưng sự khác biệt chính là giao tiếp trở lại chế độ xem. Trong khi trong MVVM thường có một nhà xuất bản riêng cho từng phần dữ liệu, trong MVI một đối tượng xác định toàn bộ trạng thái của chế độ xem được xuất bản.


Thường thì có một phần MVI theo sau việc giảm phong cách Redux, nhưng vì điều đó sẽ xảy ra tốt bên ngoài Compose, chúng tôi sẽ bỏ qua phần đó ở đây và chỉ tập trung vào phần giao tiếp của MVI.



Trong biểu đồ hoạt động và điều hướng không có gì thay đổi, nhưng có những thay đổi về cách trạng thái chế độ xem và các sự kiện được lắng nghe.


@Composable
fun MviQuestionDestination(
mviViewModel: MviViewModel,
onConfirm: () -> Unit
) {
val textFieldState = remember { mutableStateOf(TextFieldValue()) }

val viewState = mviViewModel.viewState.collectAsState()
val onConfirmClicked = { mviViewModel.onAction(MviViewModel.UiAction.AnswerConfirmed(textFieldState.value.text)) }

// We only want the event stream to be attached once
// even if there are multiple re-compositions
LaunchedEffect {
mviViewModel.oneShotEvents
.onEach {
when (it) {
MviViewModel.OneShotEvent.NavigateToResults -> onConfirm()
}
}
.collect()
}

Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.SpaceEvenly,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(text = "What do you call a mexican cheese?")
TextField(
value = textFieldState.value,
onValueChange = { textFieldState.value = it }
)
if (viewState.value.isLoading) {
CircularProgressIndicator()
} else {
Button(onClick = onConfirmClicked) {
Text(text = "Confirm")
}
}
}
}

@Composable
fun MviResultDestination(
mviViewModel: MviViewModel
) {
val viewState = mviViewModel.viewState.collectAsState()

Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.SpaceEvenly,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(text = viewState.value.textToDisplay)
}
}

Vì hiện tại chỉ có một luồng trạng thái xem và một luồng duy nhất của một hành động quay, chúng tôi chỉ cần bắt đầu nghe các luồng này ở một nơi - ở phần đầu của bản tổng hợp mà chúng được sử dụng. Khi máy ảo mở rộng, điều này có nghĩa là ít nồi hơi hơn tấm để kết nối cho giao tiếp View-ViewModel.

Ngoài ra còn có một số thay đổi trong ViewModel.

class MviViewModel(
private val answerService: AnswerService,
) {

private val coroutineScope = MainScope()

private val _viewState: MutableStateFlow<ViewState> = MutableStateFlow(ViewState())
val viewState = _viewState.asStateFlow()

// See https://proandroiddev.com/android-singleliveevent-redux-with-kotlin-flow-b755c70bb055
// For why channel > SharedFlow/StateFlow in this case
private val _oneShotEvents = Channel<OneShotEvent>(Channel.BUFFERED)
val oneShotEvents = _oneShotEvents.receiveAsFlow()

fun onAction(uiAction: UiAction) {
when (uiAction) {
is UiAction.AnswerConfirmed -> {
coroutineScope.launch {
_viewState.value = _viewState.value.copy(isLoading = true)
withContext(Dispatchers.IO) { answerService.save(uiAction.answer) }
val text = if (uiAction.answer == "Nacho cheese") {
"You've heard too many cheese jokes"
} else {
"Nacho cheese"
}
_viewState.value = _viewState.value.copy(textToDisplay = text)
_oneShotEvents.send(OneShotEvent.NavigateToResults)
_viewState.value = _viewState.value.copy(isLoading = false)
}
}
}
}

data class ViewState(
val isLoading: Boolean = false,
val textToDisplay: String = "",
)

sealed class OneShotEvent {
object NavigateToResults : OneShotEvent()
}

sealed class UiAction {
class AnswerConfirmed(val answer: String) : UiAction()
}
}

Trong lớp ViewModel có ba cấu trúc dữ liệu - ViewState, OneShotEvents và UiActions - những cấu trúc này xác định giao diện View-ViewModel của bạn. Điều này làm cho việc hiểu ViewModel đang làm gì dễ dàng hơn nhiều thay vì tìm hiểu logic. Và cũng có những lợi ích mà bạn chỉ cần một MutableStateFlow và định nghĩa Channel duy nhất, do đó, việc tạo bong bóng ViewModel hiếm hơn.

  • Beyond MVI?

Điều khác cần đề cập là ở đây tôi sử dụng một định nghĩa ViewState đơn giản, nhưng không có lý do gì điều này không thể phức tạp hơn, chẳng hạn như việc sử dụng các lớp được niêm phong để tạo mô hình cho ViewState thường hữu ích. Kết hợp với một luồng ViewState, điều này đảm bảo chỉ có một trạng thái có thể hiển thị trên màn hình bất kỳ lúc nào, do đó giảm lỗi lập trình.


Tương tự như MVVM, nếu ứng dụng của bạn đã được xây dựng với MVI, việc tích hợp Compose sẽ đơn giản như việc chuyển đổi cách chế tạo chế độ xem của bạn. Phần còn lại phần lớn sẽ giữ nguyên.


  • Clean architecture?


Hãy nhớ rằng Compose chỉ là một khung giao diện người dùng. Phần lớn ứng dụng của chúng tôi sẽ không thay đổi. Nếu bạn đang làm việc với các ca sử dụng, dịch vụ, thực thể, logic nghiệp vụ được định hướng theo giao diện, v.v. thì những phần này của ứng dụng của bạn sẽ giữ nguyên.


Các lớp này chỉ giải quyết việc tìm nạp, lưu trữ và thao tác dữ liệu trước khi gửi nó đến dạng xem. Cho dù chế độ xem là Android truyền thống, Compose hay máy nướng bánh mì, các phần này của ứng dụng của bạn không quan tâm - như một chút tiếp tuyến, đây là lý do tại sao kiến trúc sạch có thể phù hợp với Kotlin Multiplatform rất tốt, đó là một đoạn nhỏ từ logic dữ liệu độc lập giao diện người dùng sang logic dữ liệu được chia sẻ trên nhiều nền tảng. Các ứng dụng đã được xây dựng với kiến trúc sạch sẽ chỉ còn một bước ngắn nữa là có thể tận dụng tất cả các lợi ích của KMP.


  • Conclusion


Từ việc đào sâu vào Compose, thật tuyệt vời. Giống như hít thở không khí trong lành sau khi bị mắc kẹt trong một tầng hầm không có cửa gió với một người đã bỏ qua một lần tắm quá nhiều. Khung giao diện người dùng Android luôn có rất nhiều vấn đề ẩn nấp xung quanh mọi ngóc ngách, trong khi Compose học hỏi từ những vấn đề đó và khắc phục nhiều vấn đề trong số đó, giúp việc phát triển trở nên đơn giản hơn.


Nếu bạn đang mắc kẹt trên cơ sở mã MVP hoặc tệ hơn, cơ sở mã không có logic nào được tóm tắt từ giao diện người dùng, thì việc cập nhật lên Compose sẽ rất khó khăn. Nếu bạn đang muốn tận dụng tính năng Compose thì giai đoạn beta sẽ là cơ hội tuyệt vời để nâng cấp kiến trúc của bạn để bạn có thể chuyển sang tính năng Compose khi nó ổn định.


Mặt khác, nếu bạn đủ may mắn để có cơ sở mã MVVM hoặc tốt hơn là MVI, thì bạn đã sẵn sàng chuyển sang Compose một cách tương đối thoải mái.

  • Beyond MVI?

Giao diện người dùng khai báo có thể là mới trong Android, nhưng khái niệm này đã được phổ biến trên web với React. Vì vậy, với 5 năm bắt đầu, các mẫu web sẽ là một nơi tuyệt vời để nghiên cứu những ý tưởng mới có thể phù hợp với tính năng Compose.


Đây là một thử nghiệm ban đầu với Compose vì vậy chắc chắn sẽ có những cách tốt hơn để viết các mẫu.Vui lòng bỏ bình luận với bất kỳ đề xuất nào!


Nhấp vào đây để tìm tất cả các mẫu đầy đủ








Nhận xét

Bài đăng phổ biến từ blog này

Thiết kế giao diện với DotNetBar (Phần 1)

Đây là phiên bản DotNetBar hỗ trợ C# và Visual Basic https://www.dropbox.com/s/wx80jpvgnlrmtux/DotNetBar.rar  , phiên bản này hỗ trợ giao diện Metro cực kỳ “dễ thương” Các bạn load về và cài đặt, khi cài đặt xong sẽ có source code mẫu của tất cả các control. Để sử dụng được các control của DotNetBar các bạn nhớ add item vào controls box. Thiết kế giao diện với DotNetBar, giao diện sẽ rất đẹp. Link các video hướng dẫn chi tiết cách sử dụng và coding: http://www.devcomponents.com/dotnetbar/movies.aspx Hiện tại DotNetBar có rất nhiều công cụ cực mạnh, trong đó có 3 công cụ dưới đây: DotNetBar for Windows Forms Requires with Visual Studio 2003, 2005, 2008, 2010 or 2012.   DotNetBar for WPF Requires with Visual Studio 2010 or 2012 and Windows Presentation Foundation.   DotNetBar for Silverlight Requires with Visual Studio 2010 or 2012 and Silverlight. Dưới đây là một số hình ảnh về các control trong DotnetBar.   Metro User Interface  controls with Metro Tiles, toolba...

Announcing Flutter 2

  Phụ lục: Flutter on the web Flutter 2 on desktops, foldables, and embedded devices The growing Flutter ecosystem Dart: The secret sauce behind Flutter Flutter 2: Available now Hôm nay, chúng tôi sẽ công bố Flutter 2: một bản nâng cấp lớn cho Flutter cho phép các nhà phát triển tạo các ứng dụng đẹp, nhanh chóng và di động cho bất kỳ nền tảng nào. Với Flutter 2, bạn có thể sử dụng cùng một cơ sở mã để gửi các ứng dụng gốc cho năm hệ điều hành: IOS, Android, Windows, macOS và Linux; cũng như trải nghiệm web nhắm mục tiêu các trình duyệt như Chrome, Firefox, Safari hoặc Edge. Flutter thậm chí có thể được nhúng vào ô tô, TV và thiết bị gia dụng thông minh, mang đến trải nghiệm di động và lan tỏa nhất cho thế giới điện toán xung quanh. Mục tiêu của chúng tôi là thay đổi cơ bản cách các nhà phát triển nghĩ về việc xây dựng ứng dụng, bắt đầu không phải với nền tảng bạn đang nhắm mục tiêu mà là với trải nghiệm bạn muốn tạo. Flutter cho phép bạn tạo ra những trải nghiệm tuyệt đẹp trong đó ...

Jetpack Compose VS SwiftUI !VS Flutter

  Việc phát triển Android đã trở nên dễ dàng hơn khi các bản cập nhật liên tục đến. Sau bản cập nhật 2020.3.1, rất nhiều thứ đã thay đổi. Nhưng thay đổi chính mà tôi nghĩ hầu hết các nhà phát triển phải chờ đợi là Jetpack Compose cho ứng dụng sản xuất. Và Kotlin là lựa chọn duy nhất cho jetpack Compose, cũng là ngôn ngữ được ưu tiên. Để biết thêm chi tiết hoặc các thay đổi trên Jetpack Compose, bạn có thể truy cập vào https://developer.android.com/jetpack/compose Tương tự, IOS Development cũng cung cấp một tùy chọn để phát triển khai báo, SwiftUI. Trong IDE, không có thay đổi nào do điều này. Nhưng khái niệm gần giống với Jetpack Compose. Thay vì bảng phân cảnh, chúng tôi tạo giao diện người dùng bằng Swift. Để biết thêm chi tiết hoặc các thay đổi trên SwiftUI, hãy truy cập https://developer.apple.com/xcode/swiftui/ Hãy xem cách cả hai hoạt động bằng cách sử dụng một dự án demo. Tôi đã lấy một số ví dụ về số lần chạm tương tự của Flutter. 1. Android Jetpack Compose Chúng tôi có thể...