Skip to content

Migrating from hooks to the KMP version hooks2

Junerver Hou edited this page Dec 2, 2024 · 7 revisions

Here are the key considerations for migrating from hooks to the KMP version hooks2:

1. optionsOf Deprecated

If you're using optionsOf directly in hooks that require configuration and are not using named parameters:

val (resume, pause, isActive) = useInterval(
    optionsOf {
        initialDelay = 2.seconds
        period = 1.seconds
    }
)

You need to modify it to:

val (resume, pause, isActive) = useInterval(
    optionsOf = {
        initialDelay = 2.seconds
        period = 1.seconds
    }
)

The RequestOptions parameters debounceOptions and throttleOptions should be replaced by their respective optionOf functions:

DebounceOptions.optionOf { }

ThrottleOptions.optionOf { }

Or use the new parameters: debounceOptionsOf, throttleOptionsOf. In the future version, the actual parameters will be changed to internal access

Steps for Replacement:

Step 1: Replace debounceOptions and throttleOptions

Replace debounceOptions = optionsOf { with debounceOptions = DebounceOptions.optionOf {

Replace throttleOptions = optionsOf { with throttleOptions = ThrottleOptions.optionOf {

Step 2: Globally replace options = optionsOf { with optionsOf = {

Step 3: Replace optionsOf { with optionsOf = {

2. defaultOption, Options.default() Removed

This function should rarely be called on the user side since all functions requiring default parameters are already assigned. If you do need a default option object, use Options.optionOf {}.

3. Pure Android Projects

For pure Android projects, use the following dependency:

implementation("xyz.junerver.compose:hooks2-android:<latest_release>")

4. Breaking Changes

In previous versions, most APIs directly returned the value of the state for ease of use, which was convenient but introduced potential performance issues. For example:

@Composable
private fun TestDeferReads() {
    var byState by useState("default1")
    val (state, setState, getState) = useGetState("getState")

    Column(modifier = Modifier.randomBackground().size(200.dp, 300.dp)) {
        // by delegate
        Button(onClick = {
            byState += "1"
        }) { Text(byState) }
        // triple destructuring
        Button(onClick = {
            setState(getState() + "2")
        }) { Text(state) }
    }
}

Behavior:

When clicking the first button, the background color of the Column doesn't change, indicating that the component hasn't been recomposed. However, clicking the second button changes the Column background color, indicating a recomposition occurred.

Reason:

The reason is simple: in Compose, reading the state triggers recomposition. Components only recompose if they read the State. If the State is declared but its value is not directly read, changes in State will not cause recomposition.

In complex pages with a large number of inline components like Column, such recomposition can lead to performance issues.

Thus, in hooks2, the value of the state is no longer returned directly (i.e., reading the value doesn't happen in the hook call). Instead, a read-only state is returned, and the value can be accessed using state.value in the component (lazy reading).

In short, the hook will no longer return the state value T, but instead return the state State.

Affected Hooks

Hooks affected by this change:

  • useGetState
  • useBoolean
  • useCounter
  • useCountdown
  • useDebounce
  • useResetState
  • useReducer
  • useSelector
  • useTimestamp
  • useInterval
  • useThrottle
  • usePrevious
  • useUndo
  • useNow
  • useRequest

For hooks that return a tuple, you need to modify the code to use state.value to access the state value. For hooks like useDebounce, useThrottle, useSelector, etc., which only return a state, you can simply change = to by.

Example:

// Before
val (state, setState, getState) = useGetState("getState")
Column(modifier = Modifier.randomBackground().size(200.dp, 300.dp)) {
    Button(onClick = {
        setState(getState() + "1")
    }) { Text(state) } // You can directly use the state value here
}

// After
val (state, setState, getState) = useGetState("getState")
Column(modifier = Modifier.randomBackground().size(200.dp, 300.dp)) {
    Button(onClick = {
        setState(getState() + "2")
    }) { Text(state.value) } // Change to state.value
}

5. SetValueFn -> SetValueFn<SetterEither>

SetValueFn

6. Reduce the use of tuple types

Kotlin only has two official tuple types, Pair and Triple. In the past projects, the tuple types provided by Arrow-KT were used, which were based on the built-in destructuring declaration of data class

In the use of destructuring, the destructuring capability provided by kt is weak and the readability is also poor. In view of the promise of kotlin conf 24 to provide better name-based destructuring in the future.

At present, the return value has been temporarily changed from the tuple class to the more readable XxxHolder class in hooks2

7. Sample

sample_mirgrating_to_hooks2

Clone this wiki locally