Transformando el desarrollo de Android con Jetpack Compose y Coroutines

Jetpack Compose es uno de los temas de los que más se habla en la serie de videos de Android 11 que reemplazó a Google IO. Muchos esperan que la biblioteca resuelva los problemas del marco de interfaz de usuario de Android actual, que contiene una gran cantidad de código heredado y decisiones arquitectónicas ambiguas. Otro marco igualmente popular, que discutiré en este artículo, es Kotlin Coroutines, y más específicamente, la API de flujo incluida en él, que puede ayudar a evitar la ingeniería excesiva al usar RxJava.

Le mostraré cómo usar estas herramientas usando una pequeña aplicación de administración de café escrita con Jetpack Compose para la interfaz de usuario y StateFlow como una herramienta de administración de estado. También utiliza la arquitectura MVI.





, , , . , —



. , ( ), . , . :  .



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- - , , , , . , .



. , .



imagen


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 ( ). .



imagen


, .



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-. :



imagen


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