Compose

My small goal for today is to finish up Compose’s freshman tutorial. hh

Compose programming idea

There is no hierarchy difference between the View architecture and Compose.

One of Compose’s biggest and most different aspects is his programming philosophy.

The View system is imperative, and Compose is declarative.

Declarative programming specifications

Imperative as its name suggests, we use code to command it to change. Such as setText, setImageResource, addView, etc.

Declarative is when you need to change state, leave it alone. Yes, just leave it alone, it will change automatically, I just declare this component, and then I won’t tell you to change it again, so that the responsibility of updating the interface falls on the interface itself, which is much easier.

Automatic? Isn’t that reactive programming? Observer mode. I’m familiar with that.

As anyone who has used LiveData knows, using The Observe method greatly facilitates our development and reduces the chances of bugs. Basically, after we write the interface, we do not care about the interface state, by changing the corresponding Livedata, the View automatically adjusts. This also seems to achieve the imperative effect.

But Compose has even stronger support.

@Composable

That’s the way Compose’s Hello World is written

@Composable
fun Greeting(str:String){
	Text(text =str)    
}
Copy the code

There are a couple of caveats

  • This function comes with the @composable annotation (which tells the compiler that the function is intended to transform data into an interface)

  • Functions annotated with @composable are capitalized.

    For example, Compose is a new Compose function. For example, Compose is a new Compose function.

  • This function accepts data. Composable functions can take parameters that allow the application logic to describe the interface. In this case, our Greeting accepts a String.

  • Composition functions can call other composition functions, such as Greeting, which calls the composition function Text

  • This function does not return anything. The interface’s Compose function does not need to return anything.

The transformation of declarative UI

In many object-oriented imperative interface toolkits, you can initialize an interface by instantiating a widget tree. You typically do this by bloating XML layout files. Each widget maintains its own internal state and provides getter and setter methods that allow application logic to interact with the widget.

In Compose’s declarative method, the widget is relatively stateless and provides no setter or getter functions.

This makes it easy to provide state to architectural patterns such as viewModels, as described in the Application Architecture Guide. Composable items are then responsible for converting the current application state to the interface each time observable data is updated.

When the user interacts with the interface, the interface initiates events such as onClick. These events should notify the application logic, which can then change the state of the application. When the state changes, the system calls the composable function again with the new data. This causes interface elements to be redrawn, a process known as “refactoring.”

Summary:

  • The logical layer sends a state to the View, and the View itself observes the state data and updates the interface.
  • When the user clicks the event, the event will be sent to the logical layer. The logical layer will change the data after processing, and the data change will call the combinable function, and the view will be redrawn.

It seems not too different from MVVM.

restructuring

In the imperative interface model, if you need to change a widget, you can call setters on that widget to change its internal state. In Compose, you can call the composable function again with the new data. Doing so causes the function to be reorganized — the system redraws the widgets emitted by the function using new data as needed. The Compose framework intelligently reorganizes only the components that have changed.

For example, suppose you have the following composable function that displays a button:

@Composable
fun ClickCounter(clicks: Int, onClick: () -> Unit) {
    Button(onClick = onClick) {
        Text("I've been clicked $clicks times")}}Copy the code

Each time the button is clicked, the caller updates the value of CLICKS. Compose calls the lambda and Text functions again to display the new values; This process is called “reorganization”. Other functions that do not depend on this value are not reassembled.

(It was so scary.) That is, native support for local refreshes. This……

It feels like a refresh. Recombination of interface elements, um recombination.

Recombination is the process of calling a composable function again when the input changes.

Note: The following 3 things cannot be done due to the nature of the recombination process.

  • Writing to a property of a shared object Changes a shared variable
  • Updating an observable in ViewModelUpdate the ViewModel observables
  • Updating shared Preferences Updates sharedPreferences

Additional notes on composable functions

1. Composable functions can be executed in any order

Look at the code below and our first idea is to draw StartScreen, then MiddleScreen, EndScreen.

That’s not really the case.

They can be drawn in any order.

It could be EndScreen, MiddleScreen, StartScreen, but… (The main reason for that is that Compose prioritizes some elements so that the higher-priority elements are drawn first and the lower-priority elements are drawn later.)

So don’t update some shared variables in this code, which is prone to unpredictable problems where each function needs to remain independent.

@Composable
fun ButtonRow(a) {
    MyFancyNavigation {
        StartScreen()
        MiddleScreen()
        EndScreen()
    }
}
Copy the code

2. Combinable functions can be executed in parallel

Compose optimizes recombination by running composable functions in parallel. In this way, Compose can leverage multiple cores and run composable functions at a lower priority (not on screen).

This parallel optimization means that threads are not safe, and if we change the observable in the ViewModel, we get weird bugs.

To ensure that the application works properly, all composable functions should not have collateral effects, but should be triggered by callbacks such as onClick that are always executed on the interface thread.

The side effect here is that some logic operations, composable functions should only have some display logic inside, operations and other logic should not be, because parallel threads are not safe.

Like this is ok

Van ♂ Ruigudd, there is no problem when running

@Composable
fun ListComposable(myList: List<String>) {
    Row(horizontalArrangement = Arrangement.SpaceBetween) {
        Column {
            for (item in myList) {
                Text("Item: $item")
            }
        }
        Text("Count: ${myList.size}")}}Copy the code

That’s not going to work

this code will not be thread-safe or correct

@Composable
@Deprecated("Example with bug")
fun ListWithBug(myList: List<String>) {
    var items = 0

    Row(horizontalArrangement = Arrangement.SpaceBetween) {
        Column {
            for (item in myList) {
                Text("Item: $item")
                items++ // Avoid! Side-effect of the column recomposing.
            }
        }
        Text("Count: $items")}}Copy the code

Items++ is the problem, the thread is not safe.

3. Composable functions do their best to avoid things that don’t need to be recombined

/** * Display a list of names the user can click with a header */
@Composable
fun NamePicker(
    header: String,
    names: List<String>,
    onNameClicked: (String) - >Unit
) {
    Column {
        // this will recompose when [header] changes, but not when [names] changes
        Text(header, style = MaterialTheme.typography.h5)
        Divider()

        // LazyColumn is the Compose version of a RecyclerView.
        // The lambda passed to items() is similar to a RecyclerView.ViewHolder.
        LazyColumn {
            items(names) { name ->
                // When an item's [name] updates, the adapter for that item
                // will recompose. This will not recompose when [header] changes
                NamePickerItem(name, onNameClicked)
            }
        }
    }
}

/** * Display a single name the user can click. */
@Composable
private fun NamePickerItem(name: String, onClicked: (String) - >Unit) {
    Text(name, Modifier.clickable(onClick = { onClicked(name) }))
}
Copy the code

Each of these scopes may be the only scope executed during reorganization. When the header changes, Compose might jump to the Column lambda without executing any of its parents. In addition, Compose may choose to skip LazyColumnItems if names are not changed when Column is executed.

Similarly, executing all composable functions or lambdas should have no side effects. When you need to perform a spin-off effect, it should be triggered by a callback.

4. Restructuring is optimistic

Whenever Compose thinks the parameters of a composable item may have changed, it will begin the reorganization. The reorganization is an optimistic operation, which means Compose expects to complete the reorganization before the parameters change again. If a parameter changes before the reorganization is complete, Compose may cancel the reorganization and start over with the new parameter.

When the recombination is canceled, Compose will drop the interface tree from the recombination. If there are any spin-offs that depend on the displayed interface, the spin-offs are applied even if the composition operation is cancelled. This can lead to inconsistent application states.

Ensure that all composable functions and lambdas are idempotent and have no side effects to handle optimistic recombination.

Idempotent: The result of one or more requests for the same operation is the same without side effects caused by multiple clicks.

For example, database increments are non-idempotent, and queries are idempotent.

5. Composable functions can be executed very frequently

In some cases, you might run a composable function for each frame of an interface animation. If this function performs expensive operations (such as reading data from device storage), the interface can get stuck.

For example, if we were to do a read operation on a device, if placed in a combinatorics function it would read hundreds of times a second, which would have a terrible impact on performance.

summary

The function annotated with @composable is a special function.

  • The order in which it performs the drawing is arbitrary.
  • It might be called by multiple threads.
  • When the data changes, it refreshes where it has changed and stays the same where it has not.
  • Be optimistic when reorganizing
  • It may be called frequently.

Compose basic Codelabe

Set up the environment

I’m not going to talk about that, but there’s one thing that needs to be noted

We need to delete the kotlinCompilerVersion of as

composeOptions {
    kotlinCompilerExtensionVersion compose_version
    kotlinCompilerVersion '1.5.21'
}
Copy the code

ComposeOptions that’s enough

composeOptions {
    kotlinCompilerExtensionVersion compose_version
}
Copy the code

The concept of the Compose composition function

This is the function labelled by @composable, which has some limitations and is described recently by the Composable.

Compose’s declarative UI

Modifier

Modifier touched before, the official definition is the Modifier is a layout operation. You can use the Modifier to set the width, height, Shape, and padding.

Modifier parameters tell a UI element how to layout, display, or behave within its parent layout. Modifiers are regular Kotlin objects.

The other seems to be talking about a Modifier. Padding. Unusual bird

Placement of combinatorial functions

There is no need to put all of the Compose UI logic into the MainActivity. Like this.

The Activity expands inexplicably.

package com.example.compose

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.example.compose.ui.theme.ComposeTheme

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeTheme {
                // A surface container using the 'background' color from the theme
                Surface(color = MaterialTheme.colors.background) {
                    //Greeting("Android")
                    //ManageLayout()
                    ComposeRecyclerView(messages = (0.100.).map {
                        Message("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"."BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBB")})}}}}}//Part 8
@Preview
@Composable
fun Pre(a) {
    ComposeRecyclerView(messages = (0.100.).map {
        Message("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"."BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBB")})}//Part 7
@Composable
fun ComposeRecyclerView(messages: List<Message>) {
    LazyColumn {
        items(messages) { message ->
            ManageLayout(message = message)
        }
    }
}


//Part 6
@Composable
//@Preview
fun ManageLayout(message: Message) {
    Row(
        modifier = Modifier.padding(all = 8.dp),
    ) {

        Image(
            painter = painterResource(id = R.drawable.img),
            contentDescription = "This is a picture of a little sister.",
            modifier = Modifier
                .size(40.dp)
                .clip(CircleShape)
                .border(
                    width = 1.5.dp,
                    color = MaterialTheme.colors.secondary,
                    shape = CircleShape
                )
        )

        Spacer(modifier = Modifier.width(8.dp))

        var isExpandable by remember {
            mutableStateOf(false)}val surfaceColor by animateColorAsState(
            if (isExpandable) MaterialTheme.colors.primary elseMaterialTheme.colors.surface ) Column(modifier = Modifier.clickable { isExpandable = ! isExpandable }) { Text( text = message.str1, color = MaterialTheme.colors.secondaryVariant, style = MaterialTheme.typography.subtitle2 ) Spacer(modifier = Modifier.height(8.dp))
            Surface(
                shape = MaterialTheme.shapes.medium,
                elevation = 1.dp,
                color = surfaceColor,
                modifier = Modifier
                    .animateContentSize()
                    .padding(1.dp)) {
                Text(
                    text = message.str2,
                    style = MaterialTheme.typography.body2,
                    maxLines = if (isExpandable) Int.MAX_VALUE else 1,)}}}}//Part 5
@Composable
@Preview
fun UseImage(a) {
    val painter = painterResource(id = R.drawable.img)
    Row {
        Image(
            painter = painter,
            contentDescription = "This is a picture of a little sister."
        )
        Column {
            Text(text = "I don't know what to write.")
            Text(text = "I really don't know what to write.")}}}//Part 4
@Preview
@Composable
fun UseColumn(a) {
    Column(
        verticalArrangement = Arrangement.Bottom,
        horizontalAlignment = Alignment.Start,
    ) {
        Text(text = "Hello")
        Text(text = "World")}}//Part 3
data class Message(
    val str1: String,
    val str2: String,
)

@Preview
@Composable
fun MessagePreview(a) {
    MessageCard(message = Message("Hello"."World"))}@Composable
fun MessageCard(message: Message) {
    Row(
        horizontalArrangement = Arrangement.Center,
        verticalAlignment = Alignment.CenterVertically,
    ) {
        Text(text = message.str1)
        Text(text = message.str2)
    }
}

//Part 2
@Preview(name = "The first",
    widthDp = 100,
    heightDp = 50,
    showBackground = true,
    backgroundColor = 0x12ffff,
    showSystemUi = true,
    locale = "fr-rFR")
@Composable
fun Greeting(name: String = "World") {
    val resource = painterResource(id = R.drawable.abc_vector_test)

    Row() {
        Text(text = "Hello $name!")
        Image(
            painter = resource,
            contentDescription = "Test Logo")}},// Part 1
@Preview(showBackground = true)
@Composable
fun DefaultPreview(a) {
    ComposeTheme {
        Greeting("Android")}}Copy the code

Instead, we could put the Compose function directly in another file and call it from within the activity, like this

package com.example.compose

import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.example.compose.ui.theme.ComposeTheme

/ * * *@author ZhiQiang Tu
 *@time2021/8/21 and *@signatureWe don't know where we're going, but we're on our way
//Part 8
@Preview
@Composable
fun Pre(a) {
    ComposeRecyclerView(messages = (0.100.).map {
        Message("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"."BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBB")})}//Part 7
@Composable
fun ComposeRecyclerView(messages: List<Message>) {
    LazyColumn {
        items(messages) { message ->
            ManageLayout(message = message)
        }
    }
}


//Part 6
@Composable
//@Preview
fun ManageLayout(message: Message) {
    Row(
        modifier = Modifier.padding(all = 8.dp),
    ) {

        Image(
            painter = painterResource(id = R.drawable.img),
            contentDescription = "This is a picture of a little sister.",
            modifier = Modifier
                .size(40.dp)
                .clip(CircleShape)
                .border(
                    width = 1.5.dp,
                    color = MaterialTheme.colors.secondary,
                    shape = CircleShape
                )
        )

        Spacer(modifier = Modifier.width(8.dp))

        var isExpandable by remember {
            mutableStateOf(false)}val surfaceColor by animateColorAsState(
            if (isExpandable) MaterialTheme.colors.primary elseMaterialTheme.colors.surface ) Column(modifier = Modifier.clickable { isExpandable = ! isExpandable }) { Text( text = message.str1, color = MaterialTheme.colors.secondaryVariant, style = MaterialTheme.typography.subtitle2 ) Spacer(modifier = Modifier.height(8.dp))
            Surface(
                shape = MaterialTheme.shapes.medium,
                elevation = 1.dp,
                color = surfaceColor,
                modifier = Modifier
                    .animateContentSize()
                    .padding(1.dp)) {
                Text(
                    text = message.str2,
                    style = MaterialTheme.typography.body2,
                    maxLines = if (isExpandable) Int.MAX_VALUE else 1,)}}}}//Part 5
@Composable
@Preview
fun UseImage(a) {
    val painter = painterResource(id = R.drawable.img)
    Row {
        Image(
            painter = painter,
            contentDescription = "This is a picture of a little sister."
        )
        Column {
            Text(text = "I don't know what to write.")
            Text(text = "I really don't know what to write.")}}}//Part 4
@Preview
@Composable
fun UseColumn(a) {
    Column(
        verticalArrangement = Arrangement.Bottom,
        horizontalAlignment = Alignment.Start,
    ) {
        Text(text = "Hello")
        Text(text = "World")}}//Part 3
data class Message(
    val str1: String,
    val str2: String,
)

@Preview
@Composable
fun MessagePreview(a) {
    MessageCard(message = Message("Hello"."World"))}@Composable
fun MessageCard(message: Message) {
    Row(
        horizontalArrangement = Arrangement.Center,
        verticalAlignment = Alignment.CenterVertically,
    ) {
        Text(text = message.str1)
        Text(text = message.str2)
    }
}

//Part 2
@Preview(name = "The first",
    widthDp = 100,
    heightDp = 50,
    showBackground = true,
    backgroundColor = 0x12ffff,
    showSystemUi = true,
    locale = "fr-rFR")
@Composable
fun Greeting(name: String = "World") {
    val resource = painterResource(id = R.drawable.abc_vector_test)

    Row() {
        Text(text = "Hello $name!")
        Image(
            painter = resource,
            contentDescription = "Test Logo")}},// Part 1
@Preview(showBackground = true)
@Composable
fun DefaultPreview(a) {
    ComposeTheme {
        Greeting("Android")}}Copy the code

Pass the composition function as a function parameter

Content: @composable () -> Unit means that we pass a Composable higher-order function in the same way that a coroutine passes a suspend function.

@Composable
fun PassComposeContent(content: @Composable() - >Unit) {
    ComposeTheme {
        content()
    }
}
Copy the code