Jetpack Compose basics codelab

  • Compose 는 무엇인지
  • Compose로 UI를 만드는 법
  • Composable 함수에서 state를 다루는 법
  • Compose에서 Data flow 원칙

Text의 배경에 색을 넣고 싶은 경우 Surface 정의

@Composable
fun Greeting(name: String) {
  Surface(color = Color.Yellow) {
    Text(text = "Hello $name!")
  }
}

Modifiers

대부분의 Compose UI 요소들은 옵셔너하게 modfier 파라미터를 받는다.

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

변수에 할당하고 재사용 가능

modifier들을 factory-extention 함수를 이용해 체이닝하거나 then으로 하나의 아규먼트로 합칠 수 있다.

/**
 * Composes the given composable into the given activity. The [content] will become the root view
 * of the given activity.
 *
 * [Composition.dispose] is called automatically when the Activity is destroyed.
 *
 * @param parent The parent composition reference to coordinate scheduling of composition updates
 * @param content A `@Composable` function declaring the UI contents
 */
fun ComponentActivity.setContent(
    // Note: Recomposer.current() is the default here since all Activity view trees are hosted
    // on the main thread.
    parent: CompositionReference = Recomposer.current(),
    content: @Composable () -> Unit
): Composition {
    GlobalSnapshotManager.ensureStarted()
    val composeView: AndroidOwner = window.decorView
        .findViewById<ViewGroup>(android.R.id.content)
        .getChildAt(0) as? AndroidOwner
        ?: AndroidComposeView(this).also {
            setContentView(it.view, DefaultLayoutParams)
        }
    return doSetContent(composeView, parent, content)
}

Making container functions

앱의 Common 설정들을 가지고 있는 컨테이너를 만드려면 어떻게 해야할까?

generic container를 만드려면, Unit 을 반환하는 Composable 함수를 파라미터로 받는 Composable 함수를 만든다. (Composable 함수는 UI 컴포넌트를 반환하지 않기때문에 반드시 Unit을 리턴해야 함)

@Composable
fun MyApp(content: @Composable () -> Unit) {
  MyAppTheme {
    Surface(color = Color.Yellow) {
      content()
    }
  }
}

가독성, 재사용성 향상

Calling Composable functions multiple times using Layouts

UI 컴포넌트를 Composable 함수로 만들었기때문에, 중복되는 코드 없이 재사용할 수 있음

Divier를 이용하면 수평 구분선을 만들 수 잇음

State in Compose

Compose에서는 앱의 데이터 변경을 observe하기위한 툴을 제공하며 자동으로 함수를 재 호출 함 - recomposing

composable에 internal state를 추가하기 위해 mutableStateOf 함수를 사용, composable mutable memory를 부여하는.

매 recomposition 마다 다른 상태를 가지지 않게 하기 위해서는, remember를 사용

composable이 화면 여러곳에 여러 인스턴스로 존재하는경우, 각 카피는 고유한 버전의 샅애를 얻음

internal state를 클래스의 private 변수라고 생각해도 됨

/**
 * Return a new [MutableState] initialized with the passed in [value]
 *
 * The MutableState class is a single value holder whose reads and writes are observed by
 * Compose. Additionally, writes to it are transacted as part of the [Snapshot] system.
 *
 * @param value the initial value for the [MutableState]
 * @param policy a policy to controls how changes are handled in mutable snapshots.
 *
 * @see State
 * @see MutableState
 * @see SnapshotMutationPolicy
 */
fun <T> mutableStateOf(
    value: T,
    policy: SnapshotMutationPolicy<T> = structuralEqualityPolicy()
): MutableState<T> = SnapshotMutableState(value, policy)

/**

Source of truth

Composable 함수에서, 함수 호출에 유용한 state는 반드시 노출되어야한다. 왜냐하면 소비하거나 제어할 수 있는 유일한 방법이기때문에 - 이러한 프로세스를 hoisting이라고 부름

State hoisting은 호출한 함수에의해서 내부 상태를 제어할 수 있게 만드는 방법

제어된 composable 함수의 파라미터를 통해서 상태를 노출하고, 제어하는 composable에서 외부적으로 이를 인스턴스와 하여 수행할 수 있다.

예를들어, Counter의 소비자가 state에 관심이 있는경우, Counter의 파라미터로 (count, updateCount)를 도입하여 호출자에게로 defer(연기)할 수 있다.

이렇게 하면 Counter는 본인의 상태를 hoisting할 수 있다.

@Composable
fun MyScreenContent(names: List<String> = listOf("Android", "there")) {
    val counterState = remember { mutableStateOf(0) }
    Column {
        for (name in names) {
            Greeting(name = name)
            Divider(color = Color.Black)
        }
        Divider(color = Color.Transparent, thickness = 32.dp)
        Counter(
            count = counterState.value,
            updateCount = { newCount ->
                counterState.value = newCount
            }
        )
    }
}

// hoisting을 위해 value(state)와 onValueChanged이벤트가 추가될 수 있다.
@Composable
fun Counter(count: Int, updateCount: (Int) -> Unit) {
    Button(onClick = { updateCount(count + 1) }) {
        Text("clicked $count times")
    }
}

hoisting 개념 - https://developer.android.com/jetpack/compose/state

Composable 에 상태가 있는경우 hoisting(상태 끌어올리기)를 통해 stateless로 만들 수 있음

hoisting은 컴포저블의 내부 상태를 파라미터와 이벤트로 대체하여 상태를 composable의 호출자로 옮기는 패턴

Theming your app

미리 정의된 스타일을 copy함수를 이용해서 변경할 수 있음 - 일반 kotlin data 클래스 이므로

ex) style = MaterialTheme.typography.body1.copy(color = Color.Yellow)