得到
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
# Codelabs
|
||||
* [Jetpack Compose basics](https://codelabs.developers.google.com/codelabs/jetpack-compose-basics/index.html?index=..%2F..index#0)
|
||||
* [Migrating to Jetpack Compose](https://codelabs.developers.google.com/codelabs/jetpack-compose-migration)
|
||||
* [Jetpack Compose Theming](https://codelabs.developers.google.com/codelabs/jetpack-compose-theming/index.html?index=..%2F..index#0)
|
||||
* [Layouts in Jetpack Compose](https://codelabs.developers.google.com/codelabs/jetpack-compose-layouts/index.html?index=..%2F..index#0)
|
||||
* [Using State in Jetpack Compose](https://codelabs.developers.google.com/codelabs/jetpack-compose-state/index.html?index=..%2F..index#0)
|
||||
* [Advanced State Side Effects](https://developer.android.com/codelabs/jetpack-compose-advanced-state-side-effects#0)
|
||||
@@ -0,0 +1,66 @@
|
||||
# Compiler Plugin
|
||||
|
||||
Compose works by transforming all Kotlin functions that are annotated with **@Composable** and adding code for the Compose Runtime.
|
||||
To do that it uses a Kotlin Compiler Plugin.
|
||||
|
||||
|
||||
For instance, this Composable:
|
||||
```kotlin
|
||||
@Composable
|
||||
fun Hello(name: String) {
|
||||
Text(name)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
will be transformed and compiled to Jvm ByteCode. Below you can see the decompiled code as Java code
|
||||
|
||||
```java
|
||||
@Metadata(
|
||||
mv = {1, 5, 1},
|
||||
k = 2,
|
||||
xi = 48,
|
||||
d1 = {"\u0000\u0010\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0010\u000e\n\u0002\b\u0002\u001a\u0015\u0010\u0000\u001a\u00020\u00012\u0006\u0010\u0002\u001a\u00020\u0003H\u0007¢\u0006\u0002\u0010\u0004¨\u0006\u0005"},
|
||||
d2 = {"Hello", "", "name", "", "(Ljava/lang/String;Landroidx/compose/runtime/Composer;I)V", "app_debug"}
|
||||
)
|
||||
public final class GreetingKt {
|
||||
@Composable
|
||||
public static final void Hello(@NotNull final String name, @Nullable Composer $composer, final int $changed) {
|
||||
Intrinsics.checkNotNullParameter(name, "name");
|
||||
$composer = $composer.startRestartGroup(274849561);
|
||||
ComposerKt.sourceInformation($composer, "C(Hello)7@166L10:Greeting.kt#tlkiwl");
|
||||
int $dirty = $changed;
|
||||
if (($changed & 14) == 0) {
|
||||
$dirty = $changed | ($composer.changed(name) ? 4 : 2);
|
||||
}
|
||||
|
||||
if (($dirty & 11 ^ 2) == 0 && $composer.getSkipping()) {
|
||||
$composer.skipToGroupEnd();
|
||||
} else {
|
||||
TextKt.Text-fLXpl1I(name, (Modifier)null, 0L, 0L, (FontStyle)null, (FontWeight)null, (FontFamily)null, 0L, (TextDecoration)null, (TextAlign)null, 0L, 0, false, 0, (Function1)null, (TextStyle)null, $composer, 14 & $dirty, 0, 65534);
|
||||
}
|
||||
|
||||
ScopeUpdateScope var4 = $composer.endRestartGroup();
|
||||
if (var4 != null) {
|
||||
var4.updateScope((Function2)(new Function2() {
|
||||
public final void invoke(@Nullable Composer $composer, int $force) {
|
||||
GreetingKt.Hello(name, $composer, $changed | 1);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Where can i find the source code
|
||||
https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-master-dev/compose/compiler/compiler-hosted/
|
||||
|
||||
|
||||
## See also:
|
||||
* Leland Richardson(@intelligibabble) started a series about the Compose Compiler https://www.youtube.com/watch?v=bg0R9-AUXQM
|
||||
* [Under the hood of Jetpack Compose — part 2 of 2](https://medium.com/androiddevelopers/under-the-hood-of-jetpack-compose-part-2-of-2-37b2c20c6cdd)
|
||||
* [A Hitchhiker's Guide to Compose Compiler: Composers, Compiler Plugins, and Snapshots](https://www.droidcon.com/2022/06/28/ha-hitchhikers-guide-to-compose-compiler-composers-compiler-plugins-and-snapshots/)
|
||||
* [Explained: Compose Compiler and Runtime](https://www.droidcon.com/2022/08/02/explained-compose-compiler-and-runtime/)
|
||||
* [Using Compose Runtime to create a client library](https://www.droidcon.com/2022/08/02/using-compose-runtime-to-create-a-client-library/)
|
||||
@@ -0,0 +1,53 @@
|
||||
<!---
|
||||
This is the API of version 1.2.0
|
||||
-->
|
||||
# Lifecycle
|
||||
|
||||
|
||||
Compose has some "effects"-functions that can be used in Composables to track the lifecycle of a function.
|
||||
|
||||
* LaunchedEffect {}
|
||||
will be called the first time a compose function is applied.
|
||||
|
||||
* DisposableEffect { }
|
||||
Has a onDispose() which will be called when the compose function isn't part of the composition anymore.
|
||||
|
||||
The example below has a Button that will count up everytime it gets clicked.
|
||||
When the count value gets 3, the Text() function will not be added anymore.
|
||||
|
||||
The first time the LifecycleDemo will be executed, the SideEffect in the if-clause will be executed.
|
||||
When the count value gets 3 +onDispose{} inside the if-clause will be called.
|
||||
|
||||
|
||||
```kotlin
|
||||
@Composable
|
||||
fun LifecycleDemo() {
|
||||
val count = remember { mutableStateOf(0) }
|
||||
|
||||
Column {
|
||||
Button(onClick = {
|
||||
count.value++
|
||||
}) {
|
||||
Text("Click me")
|
||||
}
|
||||
|
||||
if (count.value < 3) {
|
||||
LaunchedEffect(Unit)
|
||||
Log.d("Compose", "onactive with value: " + count.value)
|
||||
}
|
||||
DisposableEffect(Unit) {
|
||||
onDispose {
|
||||
Log.d("Compose", "onDispose because value=" + count.value)
|
||||
}
|
||||
}
|
||||
|
||||
Text(text = "You have clicked the button: " + count.value.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## See also:
|
||||
* [Full Example Code]({{ site.samplefolder }}/general/LifecycleDemo.kt)
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
# Compose Confusion
|
||||
Because of similar naming of the Compose libraries, there is a some confusion between the different "variants" of Compose. Here is a quick overview that tries to give clarification:
|
||||
|
||||
## Compose Runtime
|
||||
|
||||
The [Compose Runtime](https://developer.android.com/jetpack/androidx/releases/compose-runtime) is a library for manipulating and managing trees of data.
|
||||
This library can be used to manage UI tree nodes, but it's a general purpose library, so it can be used with any kind of data.
|
||||
|
||||
It is developed as a Kotlin Multiplatform Library, which means that it could be ported to every target that Kotlin Multiplatform supports.
|
||||
Openly developed targets are Android, JVM and Web.
|
||||
[Square](https://www.reddit.com/r/androiddev/comments/r77o7d/comment/hmyqbta/?utm_source=share&utm_medium=web2x&context=3
|
||||
) is running an internal version of it on iOS.
|
||||
|
||||
This library contains basic things like @Composable, remember, mutableState, effects.
|
||||
|
||||
## Compose Compiler Plugin
|
||||
The Compose Compiler Plugin is a Kotlin Compiler Plugin that transforms all the **@Composable** functions and adds the needed calls to the **Compose Runtime**
|
||||
|
||||
## Compose UI
|
||||
[Compose UI](https://developer.android.com/jetpack/androidx/releases/compose-ui) is a set of UI libraries for Android using **Compose Runtime/Compose Compiler Plugin**
|
||||
|
||||
|
||||
# Jetpack Compose
|
||||
Jetpack Compose is a UI Toolkit for Android developed by Google.
|
||||
It is using **Compose Runtime/Compose Compiler Plugin** and **Compose UI**
|
||||
|
||||
# Compose HTML
|
||||
Compose HTML is a UI Toolkit for Web developed by JetBrains written in Kotlin/JS.
|
||||
It is using the **Compose Runtime** and the **Compose Compiler Plugin**. It does not use Compose UI, because it uses Compose Wrappers for the HTML DOM UI Elements.
|
||||
|
||||
# Compose for Desktop
|
||||
**Compose for Desktop** is a UI Toolkit developed by JetBrains. It runs on the JVM and it is using **Compose Runtime**, **Compose Compiler Plugin** and a Compose UI version for desktop which is using Skia to target Windows, macOS, Linux. Additional it provides components for desktop specific apps like scrollbars or mouse support and Swing interop.
|
||||
|
||||
# Compose Multiplatform
|
||||
[Compose Multiplatform](https://www.jetbrains.com/lp/compose-mpp/) is a Kotlin multiplatform project which is developed by JetBrains.
|
||||
It uses Jetpack Compose, Compose for Web and Compose for Desktop
|
||||
@@ -0,0 +1,85 @@
|
||||
<!---
|
||||
This is the API of version 1.2.0
|
||||
-->
|
||||
# CompositionLocal
|
||||
|
||||
|
||||
CompositionLocal is useful when you want to create a dependency in a higher node of the layout tree and use it on lower node without having to
|
||||
pass it down the tree through every child Composable.
|
||||
|
||||
## How to create an CompositionLocal?
|
||||
```kotlin
|
||||
data class User(val name: String, val age: Int)
|
||||
val LocalActiveUser = compositionLocalOf<User> { error("No user found!") }
|
||||
```
|
||||
Let's say you want to create an CompositionLocal with an User. You can use **compositionLocalOf**. Inside the function you can return an initial user object
|
||||
or you can throw an exception when the user is missing.
|
||||
|
||||
## How to provide a value for an CompositionLocal?
|
||||
|
||||
```kotlin
|
||||
@Composable
|
||||
private fun MyUserScreen() {
|
||||
val user = User("Jens", 31)
|
||||
CompositionLocalProvider(LocalActiveUser provides user) {
|
||||
UserInfo()
|
||||
}
|
||||
}
|
||||
```
|
||||
Somewhere above in your hierarchy you have to use CompositionLocalProvider to provide a value for your CompositionLocal.
|
||||
The syntax is: "**CompositionLocal`<T>`** provides **T**".
|
||||
All child @Composable of CompositionLocalProvider will implicitly be able to get the value of the CompositionLocals.
|
||||
|
||||
## How to use a value of an CompositionLocal?
|
||||
|
||||
```kotlin
|
||||
@Preview
|
||||
@Composable
|
||||
fun UserInfo() {
|
||||
Column {
|
||||
Text("Name: " + LocalActiveUser.current.name)
|
||||
Text("Age: " + LocalActiveUser.current.age)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now you can use your CompositionLocal in your @Composable. Every CompositionLocal has a **current** property that contains the current value.
|
||||
|
||||
## Predefined CompositionLocals
|
||||
The Compose libraries already contain some useful CompositionLocals. You can directly use them without needing add a Providers.
|
||||
|
||||
### LocalContext
|
||||
Provides a [Context] that can be used by Android applications.
|
||||
|
||||
### LocalConfiguration
|
||||
The [Configuration] is useful for determining how to organize the UI.
|
||||
|
||||
#### Device orientation
|
||||
One of the things you can get from the LocalConfiguration is the orientation of your device. This can be used to give the user a different ui when the device is rotated.
|
||||
|
||||
```kotlin
|
||||
val configuration = LocalConfiguration.current
|
||||
when (configuration.orientation) {
|
||||
Configuration.ORIENTATION_LANDSCAPE -> {
|
||||
Text("Landscape")
|
||||
}
|
||||
else -> {
|
||||
Text("Portrait")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### LocalLifecycleOwner
|
||||
The CompositionLocal containing the current [LifecycleOwner].
|
||||
|
||||
### LocalView
|
||||
The CompositionLocal containing the current Compose [View].
|
||||
|
||||
### LocalViewModelStoreOwner
|
||||
The CompositionLocal containing the current [ViewModelStoreOwner].
|
||||
|
||||
|
||||
## See also:
|
||||
* [Official Docs](https://developer.android.com/reference/kotlin/androidx/compose/runtime/CompositionLocal)
|
||||
* [Full Example Code]({{ site.samplefolder }}/general/CompositionLocalExample.kt)
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# CompositionLocalProvider
|
||||
CompositionLocalProvider are used to provide a value for an [CompositionLocal](../compositionlocal)
|
||||
|
||||
## How to provide a value for an CompositionLocal?
|
||||
|
||||
```kotlin
|
||||
@Composable
|
||||
private fun MyUserScreen() {
|
||||
val user = User("Jens", 31)
|
||||
CompositionLocalProvider(LocalActiveUser provides user) {
|
||||
UserInfo()
|
||||
}
|
||||
}
|
||||
```
|
||||
Somewhere above in your hierarchy you have to use CompositionLocalProviders to provide a value for your CompositionLocal. You can provide the values of multiple CompositionLocals inside Providers.
|
||||
The syntax is: "**CompositionLocal`<T>`** provides **T**".
|
||||
All child @Composable of CompositionLocalProvider will implicitly be able to get the value of the CompositionLocals.
|
||||
@@ -0,0 +1,49 @@
|
||||
# Project Setup
|
||||
|
||||
## Gradle Dependencies
|
||||
|
||||
Add this inside in the **android{}** block your build.gradle
|
||||
```groovy
|
||||
android{
|
||||
//YOUR OTHER CODE
|
||||
|
||||
buildFeatures {
|
||||
compose = true
|
||||
}
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = "{{compose.release}}"
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Below are some Compose dependencies that are online available, can find the others [here](https://maven.google.com/web/index.html?q=compose#androidx.compose.ui)
|
||||
|
||||
```kotlin
|
||||
|
||||
dependencies {
|
||||
val compose_version = "{{compose.release}}"
|
||||
|
||||
implementation("androidx.compose.animation:animation-core:$compose_version")
|
||||
implementation("androidx.compose.animation:animation:$compose_version")
|
||||
implementation("androidx.compose.ui:ui:$compose_version")
|
||||
implementation("androidx.compose.foundation:foundation:$compose_version")
|
||||
implementation("androidx.compose.ui:ui-geometry:$compose_version")
|
||||
implementation("androidx.compose.ui:ui-graphics:$compose_version")
|
||||
implementation("androidx.compose.foundation:foundation-layout:$compose_version")
|
||||
implementation("androidx.compose.runtime:runtime-livedata:$compose_version")
|
||||
implementation("androidx.compose.material:material:$compose_version")
|
||||
implementation("androidx.compose.material:material-icons-core:$compose_version")
|
||||
implementation("androidx.compose.material:material-icons-extended:$compose_version")
|
||||
implementation("androidx.compose.runtime:runtime-rxjava2:$compose_version")
|
||||
implementation("androidx.compose.ui:ui-text:$compose_version")
|
||||
implementation("androidx.compose.ui:ui-util:$compose_version")
|
||||
implementation("androidx.compose.ui:ui-viewbinding:$compose_version")
|
||||
implementation("androidx.compose.ui:ui-tooling:$compose_version")
|
||||
implementation("androidx.activity:activity-compose:1.3.1")
|
||||
|
||||
//Compose Constraintlayout
|
||||
implementation("androidx.constraintlayout:constraintlayout-compose:1.0.0")
|
||||
}
|
||||
|
||||
```
|
||||
@@ -0,0 +1,36 @@
|
||||
# Hello World Compose
|
||||
|
||||
### Setup the project
|
||||
First setup your [Project Setup](getting_started.md)
|
||||
|
||||
|
||||
### Write a simple Compose function
|
||||
|
||||
A basic Compose View is using a normal Kotlin function which is annotated with @Composable
|
||||
|
||||
|
||||
```kotlin
|
||||
@Composable
|
||||
fun HelloWorld() {
|
||||
Text("Hello World!")
|
||||
}
|
||||
```
|
||||
|
||||
### Use a Compose function as a view in your android app
|
||||
|
||||
To use the HelloWorld() function in your App you have to use the setContent() extension function inside a onCreate() in an Activity.
|
||||
|
||||
|
||||
```kotlin
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContent {
|
||||
HelloWorld()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,93 @@
|
||||
<!---
|
||||
This is the API of version 1.2.0
|
||||
-->
|
||||
# Modifier
|
||||
|
||||
Modifiers can be used modify certain aspects of a Composable.
|
||||
To set them, a Composable needs to accept a modifier as a parameter.
|
||||
|
||||
## Combine modifiers
|
||||
```kotlin
|
||||
Column(modifier = Modifier.height(500.dp).padding(100.dp)) {
|
||||
Text("Hello")
|
||||
}
|
||||
```
|
||||
You can chain multiple modifiers.
|
||||
The order is important modifier elements to the left are applied before modifier elements to the right.
|
||||
|
||||
## Some Modifiers
|
||||
The Compose libraries already contain some useful modifiers.
|
||||
|
||||
### LayoutModifier
|
||||
|
||||
#### Modifier.width()
|
||||
You can use this to set the width of a Composable.
|
||||
|
||||
#### Modifier.height()
|
||||
You can use this to set the height of a Composable.
|
||||
|
||||
#### Modifier.size()
|
||||
You can use this to set the width and height of a Composable.
|
||||
|
||||
#### Modifier.fillMaxHeight()
|
||||
This will set the height of the Composable to the maximum available height. This is similar to **MATCH_PARENT** from the classic View system.
|
||||
|
||||
#### Modifier.fillMaxWidth()
|
||||
This will set the width of the Composable to the maximum available width. This is similar to **MATCH_PARENT** from the classic View system.
|
||||
|
||||
#### Modifier.fillMaxSize()
|
||||
This will set the height/width of the Composable to the maximum available height/width
|
||||
|
||||
#### Modifier.padding()
|
||||
You can use **Modifier.padding** to set padding to Composables that take a modifier as an argument.
|
||||
|
||||
<p align="left">
|
||||
<img src ="../../images/general/modifier/PaddingExample.png" height=100 width=300 />
|
||||
</p>
|
||||
|
||||
```kotlin
|
||||
@Composable
|
||||
fun PaddingDemo() {
|
||||
|
||||
Column {
|
||||
Text("TextWithoutPadding")
|
||||
Column(modifier = Modifier.padding(start = 80.dp)){
|
||||
Text("TextWith80dpOnlyLeftPadding")
|
||||
}
|
||||
|
||||
Column(Modifier.padding(all = 80.dp)){
|
||||
Text("TextWith80dpPadding")
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### DrawModifier
|
||||
|
||||
#### Modifier.background()
|
||||
With this modifier you can set a background color/shape for the Composable
|
||||
|
||||
#### Modifier.clip()
|
||||
This modifier can clip the Composable to rectangle, rounded, or circle
|
||||
|
||||
### GestureModifier
|
||||
|
||||
#### Modifier.clickable
|
||||
Configure component to receive clicks via input or accessibility "click" event.
|
||||
|
||||
|
||||
#### Modifier.scrollable
|
||||
You can use this to make a Composable scrollable
|
||||
|
||||
#### Modifier.draggable
|
||||
You can use this to make a Composable draggable
|
||||
|
||||
#### Modifier.swipeable
|
||||
You drag elements which, when released, animate towards typically two or more anchor points defined in an orientation
|
||||
|
||||
#### Multitouch: Panning, zooming, rotating
|
||||
To detect multitouch gestures used for panning, zooming and rotating, you can use the ``transformable`` modifier. This modifier does not transform elements by itself, it only detects the gestures.
|
||||
|
||||
|
||||
## See also:
|
||||
* [Official Docs](https://developer.android.com/reference/kotlin/androidx/compose/ui/Modifier)
|
||||
@@ -0,0 +1,12 @@
|
||||
# Navigation
|
||||
|
||||
## On Android
|
||||
In Android Projects you can use the [Jetpack Compose Navigation](https://developer.android.com/jetpack/compose/navigation)
|
||||
|
||||
[Codelab Jetpack Compose Navigation](https://developer.android.com/codelabs/jetpack-compose-navigation#0)
|
||||
|
||||
|
||||
## Multiplatform
|
||||
When you need a multiplaform solution for Routing, you can use [Decompose](https://github.com/arkivanov/Decompose)
|
||||
|
||||
[Decompose Navigation Overview](https://arkivanov.github.io/Decompose/navigation/overview/)
|
||||
@@ -0,0 +1,48 @@
|
||||
# Preview
|
||||
## Preview
|
||||
You can use the **@Preview** annotation to preview compose functions inside Android Studio.
|
||||
Preview can not be used on Composables that have parameters without a default parameter.
|
||||
|
||||
```kotlin
|
||||
@Preview
|
||||
@Composable
|
||||
fun TextDemo(){
|
||||
Text("Hello")
|
||||
}
|
||||
|
||||
@Preview(name = "MyPreviewName")
|
||||
@Composable
|
||||
fun TextDemo2(){
|
||||
Text("Hello")
|
||||
}
|
||||
|
||||
```
|
||||
Android Studio Preview
|
||||
<p align="center">
|
||||
<img src ="../../../images/general/preview/ComposePreview.png" height=100 width=300 />
|
||||
</p>
|
||||
|
||||
|
||||
## Group Previews
|
||||
|
||||
```kotlin
|
||||
@Preview(group = "TestGroup1")
|
||||
```
|
||||
The Preview annotation has a group parameter. You can use it to set a group name to your previews.
|
||||
<p align="center">
|
||||
<img src ="../../../images/general/preview/grouppreviews.png" />
|
||||
</p>
|
||||
|
||||
The layout preview will now have an option to filter all the previews by the group name.
|
||||
|
||||
## Interactive Previews
|
||||
<p align="center">
|
||||
<img src ="../../../images/general/preview/interactivepreview.png" />
|
||||
</p>
|
||||
|
||||
Above a generated preview in Android Studio you will find an "Interactive" Button. It will open your Composable in an interactive preview mode where can try your Composable directly inside
|
||||
Android Studio.
|
||||
|
||||
|
||||
## See also:
|
||||
* [Official Docs](https://developer.android.com/jetpack/compose/tooling#preview-features)
|
||||
@@ -0,0 +1,71 @@
|
||||
# PreviewParameter
|
||||
|
||||
You can use **@PreviewParameter** to provide sample data for your Composables.
|
||||
Let's say you have the following Composable and you want to generate a preview.
|
||||
|
||||
```kotlin
|
||||
data class User(val name :String,val age:Int)
|
||||
|
||||
@Composable
|
||||
fun UserInfo(user:User) {
|
||||
Text(user.name+ " "+user.age)
|
||||
}
|
||||
```
|
||||
|
||||
Because UserInfo needs a User you can't directly use @Preview.
|
||||
One way is to wrap your Composable in a Composable that provides a User
|
||||
|
||||
```kotlin
|
||||
@Preview
|
||||
@Composable
|
||||
fun UserPreview() {
|
||||
UserInfo(user = User("Jens", 31))
|
||||
}
|
||||
```
|
||||
|
||||
An other way is to use @PreviewParameter on the parameter. With PreviewParameter you can set a class
|
||||
which will provide values for the needed User
|
||||
|
||||
```kotlin
|
||||
@Preview
|
||||
@Composable
|
||||
fun UserInfo(@PreviewParameter(SampleUserProvider::class) user:User) {
|
||||
Text(user.name+ " "+user.age)
|
||||
}
|
||||
```
|
||||
### Create PreviewParameterProvider
|
||||
|
||||
**SampleUserProvider::class** will be the class which provides a User.
|
||||
To create a PreviewParameterProvider you need to implement the interface PreviewParameterProvider.
|
||||
The interface has two properties.
|
||||
|
||||
**values** is a sequence of your sample data.
|
||||
|
||||
```kotlin
|
||||
class SampleUserProvider: PreviewParameterProvider<User> {
|
||||
override val values = sequenceOf(User("Jens",31),User("Jim",44))
|
||||
}
|
||||
```
|
||||
|
||||
### Use PreviewParameterProvider
|
||||
You can annotate your parameter with @PreviewParameter and the class which provides the sample data.
|
||||
|
||||
```kotlin
|
||||
@Preview
|
||||
@Composable
|
||||
fun UserInfo(@PreviewParameter(SampleUserProvider::class) user:User) {
|
||||
Text(user.name+ " "+user.age)
|
||||
}
|
||||
```
|
||||
|
||||
Now Android Studio will generate a preview of the Composable for every value that your provider provides.
|
||||
|
||||
<p align="center">
|
||||
<img src ="../../../images/general/preview/previewParam1.png" height=100 width=300 />
|
||||
</p>
|
||||
|
||||
You can limit the amount of previews by settings a limit to PreviewParameter.
|
||||
|
||||
```kotlin
|
||||
@PreviewParameter(SampleUserProvider::class,1)
|
||||
```
|
||||
@@ -0,0 +1,3 @@
|
||||
# Roadmap
|
||||
|
||||
You can find the road map here: https://developer.android.com/jetpack/androidx/compose-roadmap
|
||||
@@ -0,0 +1,62 @@
|
||||
<!---
|
||||
This is the API of version 1.2.0
|
||||
-->
|
||||
# State
|
||||
|
||||
## Define a state
|
||||
```kotlin
|
||||
val textState = mutableStateOf("Hello")
|
||||
```
|
||||
|
||||
You can use the mutableStateOf function to create a mutable state.
|
||||
|
||||
|
||||
## Example
|
||||
In this example we will create a composable with a Text and a Button. On a click on the button, the count state will go up and the text of Text will be updated.
|
||||
|
||||
<p align="center">
|
||||
Initial state:<br>
|
||||
<img src ="{{ site.images }}/general/state/state1.png" />
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
After Button click:<br>
|
||||
<img src ="{{ site.images }}/general/state/state2.png" />
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
```kotlin
|
||||
@Composable
|
||||
fun StateDemo(){
|
||||
val countState = remember { mutableStateOf(0) }
|
||||
Column {
|
||||
Button(colors = ButtonDefaults.buttonColors(backgroundColor = MaterialTheme.colors.secondary), onClick = { countState.value++ }) {
|
||||
Text("count up")
|
||||
}
|
||||
Text("You have clicked the Button " + countState.value.toString() + " times")
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
```kotlin
|
||||
val countState = remember { mutableStateOf(0) }
|
||||
```
|
||||
|
||||
Here we define the state for the click counter with **mutableStateOf(0)**. 0 will be the initial value. Because 0 is an Int, the counterState will only allow values which are Int.
|
||||
|
||||
**remember** is used to remember the countstate. Without remember, every time the value of countstate would change, the StateDemo Composable will be recomposed and your state will also
|
||||
get recreated with the initial value. When you use remember it will remember the last value and not be recreated.
|
||||
|
||||
```kotlin
|
||||
countState.value
|
||||
```
|
||||
With the value property you can get/set the value of the counterstate. In the onClick() function of the Button, the value will be incremented. The Text shows the value of the counterstate. When counterstate is changed,
|
||||
"Text" will also change.
|
||||
|
||||
|
||||
## See also:
|
||||
* [Codelab: Using state in Jetpack Compose](https://developer.android.com/codelabs/jetpack-compose-state#0)
|
||||
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/PMMY23F0CFg" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
* [Invest in Real State: A workshop for State in Compose](https://www.droidcon.com/2022/08/02/invest-in-real-state-a-workshop-for-state-in-compose/)
|
||||
@@ -0,0 +1,134 @@
|
||||
# UI Testing
|
||||
|
||||
## Setup
|
||||
Add the Compose testing library to your dependencies in build.gradle
|
||||
|
||||
```kotlin
|
||||
androidTestImplementation("androidx.compose.ui:ui-test:$compose_version")
|
||||
debugImplementation("androidx.compose.ui:ui-test-manifest:$compose_version")
|
||||
```
|
||||
|
||||
## Example
|
||||
Let's say you have the following example code, it's a button with a text that says **Hello** and when you click on it, it turns to **Bye**
|
||||
|
||||
```kotlin
|
||||
@Composable
|
||||
fun TestingExample() {
|
||||
val state = remember { mutableStateOf("Hello") }
|
||||
Button(onClick = { state.value = "Bye" }) {
|
||||
Text(state.value)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## TestRule
|
||||
To run this Composable in your test Code you have 2 options:
|
||||
|
||||
### 1) createComposeRule()
|
||||
```kotlin
|
||||
@Rule
|
||||
@JvmField
|
||||
var composeTestRule: ComposeContentTestRule = createComposeRule()
|
||||
```
|
||||
|
||||
You can use this when you want to run your Composable without a specific Activity. You can then use the **setContent()** from the TestRule to host your Composable.
|
||||
|
||||
```kotlin
|
||||
composeTestRule.setContent {
|
||||
TestingExample()
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### 2) createAndroidComposeRule()
|
||||
```kotlin
|
||||
@Rule
|
||||
@JvmField
|
||||
var composeTestRule: ComposeContentTestRule = createAndroidComposeRule<UiTestingDemoActivity>()
|
||||
```
|
||||
|
||||
You can use this when you want to start your test with a specific Activity.
|
||||
|
||||
## Interaction with Composables
|
||||
|
||||
Now we want to test how the Composable reacts when we interact with it.
|
||||
Usually in Tests we would use Espresso for that, but you can use that only for classic Android Views and not for Composables.
|
||||
|
||||
The ComposeTestRule will offer a similar API
|
||||
|
||||
```kotlin
|
||||
class ExampleUiTestWithAndroidComposeRule {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
var composeTestRule: ComposeContentTestRule = createAndroidComposeRule<UiTestingDemoActivity>()
|
||||
|
||||
@Test
|
||||
fun whenIClickOnButton_TheTextShouldChange() {
|
||||
composeTestRule.onNodeWithText("Hello").assertExists()
|
||||
composeTestRule.onNodeWithText("Hello").performClick()
|
||||
composeTestRule.onNodeWithText("Hello").assertDoesNotExist()
|
||||
composeTestRule.onNodeWithText("Bye").assertExists()
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
As you can see above we used the TestRule methods to click on the node with the text **Hello** and asserted that the text changed to **Bye**
|
||||
|
||||
The TestRule offers a lot of different methods. You can find a cheatSheet [here](https://developer.android.com/jetpack/compose/testing-cheatsheet)
|
||||
|
||||
### TestTags
|
||||
The test above is only half correct, because it looks for the text inside the button and not the button itself.
|
||||
Composables have no resource ids so we cant just use **onView(withId(R.id.my_view))** to find a Composable in a test, also we can't find Composables of a specific "type"
|
||||
like a button, because everything is just a Composable function.
|
||||
|
||||
When you can't find a Composable by a text and you want to make it detectable in your test. Jetpack Compose offers the concept of a **TestTag**.
|
||||
testTag is a modifier that needs to be set to a Composable. It expects a string which will be used as a reference in your test.
|
||||
|
||||
```kotlin
|
||||
@Composable
|
||||
fun TestingExample() {
|
||||
val state = remember { mutableStateOf("Hello") }
|
||||
Button(onClick = { state.value = "Bye" }, modifier = Modifier.testTag("MyTestTag")) {
|
||||
Text(state.value)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now you can use **onNodeWithTag("MyTestTag")** to find the button Composable.
|
||||
|
||||
```kotlin
|
||||
@Test
|
||||
fun whenIClickOnButton_TheTextShouldChange() {
|
||||
composeTestRule.onNodeWithTag("MyTestTag").assertTextEquals("Hello")
|
||||
composeTestRule.onNodeWithTag("MyTestTag").performClick()
|
||||
composeTestRule.onNodeWithText("Hello").assertDoesNotExist()
|
||||
composeTestRule.onNodeWithTag("MyTestTag").assertTextEquals("Bye")
|
||||
}
|
||||
```
|
||||
|
||||
### PrintToLog
|
||||
When you want to get more information about how the node tree of a Composable looks like,
|
||||
you can use **printToLog()** on a node.
|
||||
|
||||
```kotlin
|
||||
composeTestRule.onNodeWithTag("MyTestTag").printToLog("XXX")
|
||||
```
|
||||
|
||||
will print to logcat:
|
||||
|
||||
```kotlin
|
||||
2022-01-07 22:58:55.048 9567-9587/de.jensklingenberg.jetpackcomposeplayground D/XXX: printToLog:
|
||||
Printing with useUnmergedTree = 'false'
|
||||
Node #2 at (l=0.0, t=325.0, r=195.0, b=424.0)px, Tag: 'MyTestTag'
|
||||
Role = 'Button'
|
||||
Text = '[Hello]'
|
||||
Actions = [OnClick, GetTextLayoutResult]
|
||||
MergeDescendants = 'true'
|
||||
```
|
||||
|
||||
## See also:
|
||||
* [Full Example Code](https://github.com/Foso/Jetpack-Compose-Playground/blob/master/app/src/androidTest/java/de/jensklingenberg/jetpackcomposeplayground/ExampleUiTestWithAndroidComposeRule.kt)
|
||||
* [Full Example Code](https://github.com/Foso/Jetpack-Compose-Playground/blob/master/app/src/androidTest/java/de/jensklingenberg/jetpackcomposeplayground/ExampleUiTestWithComposeRule.kt)
|
||||
Reference in New Issue
Block a user