-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Compose/basic/codelab #14
Merged
Merged
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
- Material Components such as Surface are opinionated
…tTag or semantics
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
study compose basic codelab
You use
setContent
to define your layout,but instead of using an XML file as you'd do in the traditional View system(like
setContentView
),you call Composable functions within it.
LearningTestTheme
is a way to style Composable functions.(Theme.kt)Surface
You can set a different background color for the
Greeting
by wrapping the Text composable with aSurface
.Surface takes a color, so use
MaterialTheme.colorScheme.primary
The components nested inside
Surface
will be drawn on top of that background color.GreetingV2.kt:
GreetingV2
useSurface
.Look at the text color.
The text is now white even we didn't define it.
The Material Components such as
androidx.compose.material3.Surface
, are built to make yourexperience better
by taking taking care of common features that you probably want in your app,
such as choosing an appropriate color for text.
We say Material is opinionated because it provides good defaults and patterns that are common to
most apps.
The Material components in Compose are built ont top of other foundational components (in
androidx.compose.foundation
),which are also accessible from your app components in case you need more flexibility.
In this case,
Surface
understands that, when the background is set to theprimary
color,any text on top of it should use the
onPrimary
color, which is also defined in the theme.Modifier
Most Compose Ui elements such as
Surface
andText
accept an optional modifier parameter.Modifier
s tell a UI element how to lay out, display, or behave within its parent layout.For example, the padding modifier will apply an amount of space around the element it decorates.
You can create a padding modifier with
Modifier.padding()
.You can also add multiple modifiers by chaining them,
so in our case we can add the padding modifier to the default one:
modifier.padding(24.dp)
.Now, add padding to your Text on the screen: GreetingV3.kt: GreetingV3 use
Modifier.padding()
andSurface
.Modifiers allow you to decorate or augment a composable. Modifiers let you do these sorts of things:
composable
's size, layout, behavior, and appearanceReusing composables
By making small reusable components it's easy to build up a library of UI elements used in your
app.
Each one is responsible for one small part of the screen and can be edited independently.
As a best practice, ur function should include a Modifier parameter that is assigned an empty
Modifier by default.
Forward this modifier to the first composable you call inside your function.
This way, the calling site can adapt instructions and behaviors from outside of your composable
function.
Create a Composable called MyApp that includes the greeting.
BasicComposeActivity.kt
and GreetingV3Preview.kt reuse
MyApp
composable function.Columns and Rows
The three basic standard layout elements in Compose are
![img.png](Basic-standard-layout-elements-in-Compose.png)
Column
,Row
andBox
.They are Composable functions that take Composable content, so you can place items inside.
For example, each child inside of a
Column
will be placed vertically.GreetingV4.kt: use
Column
.Composable functions can be used like any other functions in Kotlin.
This makes building UIs powerful since you can add statements to influence how the UI will be
displayed.
For example, you can use a
for
loop to add elements to theColumn
: BasicComposeActivity.kt: just likeMyApp2
Column's children Test:
use
onParent
.if it has only one composable layout, you can use
Modifier.testTag(...)
.https://developer.android.com/develop/ui/compose/modifiers
Add ElevatedButton
GreetingV5.kt
The
Column
is part of a Row, which contains:Modifier.weight(1f)
).ElevatedButton
(with no weightapplied).ElevatedButton Reference
Effect of weight(1f) on the Column:
Row
after theElevatedButton
is measured and laid out.ElevatedButton
doesn't have a weight, it only takes up as much space as it needs todisplay its content (
Text("Show more")
).The
Column
expands to fill all available space not occupied by theElevatedButton
.There's no
alignEnd
modifier so, instead, you give someweight
to the composable at the start.I wrote a test code for GreetingV5.kt like below.
But it failed.
I expected that the children of the
Row
areColumn
andElevatedButton
.But actually, the
assertCountEquals
counts all children of the Column.I think that the layout of Row and Column is merged in the actual Compose tree, not nested as in the
Kotlin Composable code.
Basically, layout containers like Row and Column only play a "visual arrangement" role, and if they
do not have separate semantic information, they can be omitted (or merged with other nodes) in the
merged tree.
In other words, if Row itself is to be held as a separate semantic node, the following actions are
required.
is needed separately in the test" or
movement).
That is, if Row or Column is to be displayed separately, you must provide information that says "
Include this Row in the semantic tree" directly.
GreetingV5WithTestTag
orGreetingV5WithSemantic
function has a test tag for theRow and Column.
Now i can test the Row and Column separately.
So, this is it?
It is not very good to add The code only for the test code.
How did we test the traditional View system using xml?
There are several ways to manage these identifiers, but it was generally considered easiest to get
them through ID (view identifier).
reference: https://developer.android.com/training/testing/espresso/basics#finding-view
Setting an identifier in the View system was not awkward because it was used in various situations,
but Compose is designed declaratively, so the need to create identifiers is less felt, so it may be
natural to experience this inconvenience.
State in Compose
ElevatedButton has content parameter as composable trailing lambda.
This doesn't work as expected.
Setting a different value for the
expanded
variable won't make Compose detect it as a state changeso nothing will happen.
Compose apps transform data into UI by calling composable functions.
If your data changes, Compose re-executes these functions with the new data, creating an updated
UI—this is called recomposition.
Compose also looks at what data is needed by an individual composable so that it only needs to
recompose components whose data has changed and skip recomposing those that are not affected.
The reason why mutating this variable does not trigger recompositions is that it's not being tracked
by Compose. Also, each time Greeting is called, the variable will be reset to false.
To add internal state to a composable, you can use the mutableStateOf function, which makes Compose
recompose functions that read that State.
State and MutableState are interfaces that hold some value and trigger UI updates (recompositions)
whenever that value changes.
However you can't just assign mutableStateOf to a variable inside a composable.
As explained before, recomposition can happen at any time which would call the composable again,
resetting the state to a new mutable state with a value of false.
Composable functions can execute frequently and in any order,
you must not rely on the ordering in which the code is executed,
or on how many times this function will be recomposed.
To preserve state across recompositions, remember the mutable state using
remember
.Note that if you call the same composable from different parts of the screen you will create
different UI elements,
each with its own version of the state.
You can think of internal state as a private variable in a class.
The composable function will automatically be "subscribed" to the state.
If the state changes, composables that read these fields will be recomposed to display the updates.
You don't need to remember extraPadding against recomposition because it's doing a simple
calculation.
State Hoisting
OnboardingScreenV1.kt
What if i wanna show OnBoardingScreen if
shouldShowOnBoarding
is true or Greeting if it's false?You don't have access to the
shouldShowOnBoarding
variable in the parent composable.You need to share the state between the two composables.
Instead, you can hoist the state up to the parent composable.
Let's see Greetings.kt, OnboardingScreenV2.kt.
We hoist the shouldShowOnBoarding state up to the parent composable, MyAppV4.
And we didn't hoist the expanded state up to the parent composable, Greetings.
Basic hoisting recommendation:
When the state is used together in multiple composables,
or when you need to modify, test, or control that state from the parent (or outside),
hoist that state up.
If you hoist more than you need, the parent code becomes more complex,
and it can be inconvenient when the child needs more than just "UI representation".
This looks like a difficult concept, but in fact, there is a similar concept
in simple classes that are not composable functions.
I think DI is a similar concept to this.
if you inject too many properties from the outside into a class,
you may encounter the class explosion problem.
So, when creating composable functions or classes,
I think it is important to inject only the necessary parts from the outside.
Performant lazy list
If you set the
namee
list like below,You can see that the UI is slow to load.
And You can not even see the end of the list.
Compose provides a
LazyColumn
andLazyRow
composable that can display a large list of itemsefficiently.
It is like the RecyclerView in the xml View system.
LazyColumn
doesn't recycle its children likeRecyclerView
.It emits new Composables as you scroll through it and is still performant,
as emitting Composables is relatively cheap compared to instantiating Android Views.
GreetingsV2.kt use
LazyColumn
instead ofColumn
.You can see that the UI is loaded quickly and you can see the end of the list.
Persisting state
Let's run the BasicComposeActivity.kt and click the "Continue" button.
You can see greetings in your screen.
Now, rotate the screen.
You can see that the greetings are gone and the "Continue" button is shown again.
This is because the state is not persisted across configuration changes.
The remember function works only as long as the composable is kept in the Composition.
When you rotate, the whole activity is restarted so all state is lost.
This also happens with any configuration change and on process death.
Instead of using remember you can use
rememberSaveable
.This will save each state surviving configuration changes (such as rotations) and process death.
Animating list
In Compose, there are multiple ways to animate your UI: from high-level APIs for simple animations
to low-level methods for full control and complex transitions. You can read about them in the
documentation.
The spring spec does not take any time-related parameters.
Instead it relies on physical properties (damping and stiffness) to make animations more natural.
It takes a target value and a spring spec, and returns an animated value that you can use in your
composable.
https://developer.android.com/codelabs/jetpack-compose-basics#5