Transformation der Android-Entwicklung mit Jetpack Compose und Coroutines

Jetpack Compose ist eines der am meisten diskutierten Themen in der Android 11-Videoserie, die Google IO ersetzt hat. Viele erwarten, dass die Bibliothek die Probleme des aktuellen Android-UI-Frameworks löst, das viel Legacy-Code und mehrdeutige Architekturentscheidungen enthält. Ein weiteres ebenso beliebtes Framework, das ich in diesem Artikel erläutern werde, sind Kotlin Coroutines und insbesondere die darin enthaltene Flow-API, mit deren Hilfe bei der Verwendung von RxJava ein Über-Engineering vermieden werden kann.

Ich zeige Ihnen, wie Sie diese Tools mithilfe einer kleinen Kaffeeverwaltungsanwendung verwenden, die mit Jetpack Compose für die Benutzeroberfläche und StateFlow als Statusverwaltungswerkzeug geschrieben wurde. Es verwendet auch MVI-Architektur.





, , , . , —



. , ( ), . , . :  .



Jetpack Compose



Jetpack Compose XML , , - UI- Kotlin . Flutter . UI . UI-.



, Compose UI.



Flutter, Compose MainActivity . . , Flutter Compose . Compose API , , Flutter.



Compose- Android Studio. MainActivity.kt:



class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            CoffeegramTheme {
                Greeting("Android")
            }
        }
    }
}

@Composable
fun Greeting(name: String) {
    Text(text = "Hello $name!")
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    CoffeegramTheme {
        Greeting("Android")
    }
} 


Compose,

Compose , @Composable. .

setContentView(), Activity.onCreate() , setContent(), Composable-.



@Preview Composable-, Android Studio ( 4.2 Canary) . . Hot Reload Flutter, - , . , UI , .

, , .idea Git . , - . , .

, .

Composable- - , , , , . , .



. , .



Bild


data class CoffeeType(
    @DrawableRes
    val image: Int,
    val name: String,
    val count: Int = 0
)

@Composable
fun CoffeeTypeItem(type: CoffeeType) {
    Row(
        modifier = Modifier.padding(16.dp)
    ) {
        Image(
            imageResource(type.image), modifier = Modifier
                .preferredHeightIn(maxHeight = 48.dp)
                .preferredWidthIn(maxWidth = 48.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(24.dp))
                .gravity(Alignment.CenterVertically),
            contentScale = ContentScale.Crop
        )
        Spacer(Modifier.preferredWidth(16.dp))
        Text(
            type.name, style = typography.body1,
            modifier = Modifier.gravity(Alignment.CenterVertically).weight(1f)
        )
        Row(modifier = Modifier.gravity(Alignment.CenterVertically)) {
            val count = state { type.count }
            Spacer(Modifier.preferredWidth(16.dp))
            val textButtonModifier = Modifier.gravity(Alignment.CenterVertically)
                .preferredSizeIn(
                    maxWidth = 32.dp,
                    maxHeight = 32.dp,
                    minWidth = 0.dp,
                    minHeight = 0.dp
                )
            TextButton(
                onClick = { count.value-- },
                padding = InnerPadding(0.dp),
                modifier = textButtonModifier
            ) {
                Text("-")
            }
            Text(
                "${count.value}", style = typography.body2,
                modifier = Modifier.gravity(Alignment.CenterVertically)
            )
            TextButton(
                onClick = { count.value++ },
                padding = InnerPadding(0.dp),
                modifier = textButtonModifier
            ) {
                Text("+")
            }
        }
    }
}


ListView — Row. ( Image png- drawable); Spacer; Text , - weight(1f) ( ListView); Row .



Android Studio . , . ( Android Studio ), . .



State



, , . - val count = state { type.count }, state type Composable- . count.value. , , , , ( state collectAsState ).



Flutter, Compose Stateful ( ) Stateless ( ) . , Stateful, Stateless.



. — Column , :



@Composable
fun CoffeeList(coffeeTypes: List<CoffeeType>) {
    Column {
        coffeeTypes.forEach { type ->
            CoffeeTypeItem(type)
        }
    }
}

@Composable
fun ScrollableCoffeeList(coffeeTypes: List<CoffeeType>) {
    VerticalScroller(modifier = Modifier.weight(1f)) {
        CoffeeList(coffeeTypes: List<CoffeeType>)
    }
}


Composable ,   if, for, when .. Column ListView , VerticalScroller — ScrollView.

. . Compose RecyclerView? — LazyColumnItems ( AdapterList). CoffeeList :



@Composable
fun CoffeeList( coffeeTypes: List<CoffeeType>, modifier: Modifier = Modifier) {
    LazyColumnItems(data = coffeeTypes, modifier = modifier.fillMaxHeight()) { type ->
        CoffeeTypeItem(type)
    }
}


RecyclerView GridLayoutManager ( ). .



Bild


, .



Material design Flutter Compose. Scaffold, . TopAppBar ( ), BottomAppBar ( , — Floating action button) Drawer ( ). BottomNavigationView Material Scaffold Column BottomNavigation :



@Composable
fun DefaultPreview() {
    CoffeegramTheme {
        Scaffold() {
                Column() {
                    var selectedItem by state { 0 }
                    when (selectedItem) {
                        0 -> {
                            Column(modifier = Modifier.weight(1f)){}
                        }
                        1 -> {
                            CoffeeList(listOf(...))
                        }
                    }

                    val items =
                        listOf(
                            "Calendar" to Icons.Filled.DateRange, 
                            "Info" to Icons.Filled.Info
                        )

                    BottomNavigation {
                        items.forEachIndexed { index, item ->
                            BottomNavigationItem(
                                icon = { Icon(item.second) },
                                text = { Text(item.first) },
                                selected = selectedItem == index,
                                onSelected = { selectedItem = index }
                            )
                        }
                    }
                }
            }
    }
}


selectedItem.   when . BottomNavigation selectedItem. Compose.



. , , , , . ContextAmbient.current.context Composable-. :



Bild


png- . imageResource Image vectorResource. Icon ( ), .



StateFlow



. Flow . — ( ). BehaviorSubject RxJava. StateFlow. BehaviorSubject, .



, , selectedItem selectedItemFlow:



val selectedItemFlow = MutableStateFlow(0)
@Composable
fun DefaultPreview() {
    ...
    val selectedItem by selectedItemFlow.collectAsState()

    when (selectedItem) {
        0 -> TablePage()
        1 -> CoffeeListPage()
    }
    ...
    BottomNavigationItem(
        selected = selectedItem == index,
        onSelected = { selectedItemFlow.value = index }
    )
}


StateFlow ( Flow) collectAsState().  , .



, selectedItemFlow.value.



, collectAsState() . . (val selectedItem by selectedItemFlow.collectAsState()) , MutableStateFlow (selectedItemFlow.value) — .



, , , StateFlow:



val yearMonthFlow = MutableStateFlow(YearMonth.now())
val dateFlow = MutableStateFlow(-1)
val daysCoffeesFlow: DaysCoffeesFlow = MutableStateFlow(mapOf())


yearMonthFlow .

dateFlow — : -1 — — TablePage. — CoffeeListPage .

daysCoffeesFlow — , . .



TablePage CoffeeListPage, ( ) , daysCoffeesFlow. CoffeeList . , , daysCoffeesFlow. , Flow .



, DayCoffee.kt. , .



UI-. MVI-. , MVICore, RxJava . Android MVI with Kotlin Coroutines & Flow article. MVI . Store:



abstract class Store<Intent : Any, State : Any>(private val initialState: State) {
    protected val _intentChannel: Channel<Intent> = Channel(Channel.UNLIMITED)
    protected val _state = MutableStateFlow(initialState)

    val state: StateFlow<State>
        get() = _state

    fun newIntent(intent: Intent) {
        _intentChannel.offer(intent)
    }

    init {
        GlobalScope.launch {
            handleIntents()
        }
    }

    private suspend fun handleIntents() {
        _intentChannel.consumeAsFlow().collect { _state.value = handleIntent(it) }
    }

    protected abstract fun handleIntent(intent: Intent): State
}


Store Intent- StateFlow<State> . , Reducer handleIntent() . Store state, StateFlow; newIntent().



NavigationStore, :



class NavigationStore : Store<NavigationIntent, NavigationState>(
        initialState = NavigationState.TablePage(YearMonth.now())
    ) {

    override fun handleIntent(intent: NavigationIntent): NavigationState {
        return when (intent) {
            NavigationIntent.NextMonth -> {
                increaseMonth(_state.value.yearMonth)
            }
            NavigationIntent.PreviousMonth -> {
                decreaseMonth(_state.value.yearMonth)
            }
            is NavigationIntent.OpenCoffeeListPage -> {
                NavigationState.CoffeeListPage(
                    LocalDate.of(
                        _state.value.yearMonth.year,
                        _state.value.yearMonth.month,
                        intent.dayOfMonth
                    )
                )
            }
            NavigationIntent.ReturnToTablePage -> {
                NavigationState.TablePage(_state.value.yearMonth)
            }
        }
    }

    private fun increaseMonth(yearMonth: YearMonth): NavigationState {
        return NavigationState.TablePage(yearMonth.plusMonths(1))
    }

    private fun decreaseMonth(yearMonth: YearMonth): NavigationState {
        return NavigationState.TablePage(yearMonth.minusMonths(1))
    }
}

sealed class NavigationIntent {
    object NextMonth : NavigationIntent()
    object PreviousMonth : NavigationIntent()
    data class OpenCoffeeListPage(val dayOfMonth: Int) : NavigationIntent()
    object ReturnToTablePage : NavigationIntent()
}

sealed class NavigationState(val yearMonth: YearMonth) {
    class TablePage(yearMonth: YearMonth) : NavigationState(yearMonth)
    data class CoffeeListPage(val date: LocalDate) : NavigationState(
        YearMonth.of(date.year, date.month)
    )
}


. sealed-, , Store. UI. — .



initialState NavigationStore -, , .



handleIntent() - .

DaysCoffeesStore, , , .



Jetpack Compose , Android-. , , (, , ) . , , , Compose ( ) .



UI-, Compose, Flutter SwiftUI, Web. , , .




All Articles