3

assume my code looks like this

@Composable
fun ExampleList() {
    val tickers by exampleViewModel.tickers.observeAsState()
    LazyColumn() {
        items(items = tickers) { ticker ->
            ExampleItem(ticker)
        }
    }
}
@Composable
fun ExampleItem(ticker: Ticker) {
    Text(text= ticker.lastPrice)
}

is there anyway to get previous value of ticker in ExampleItem Compose everytime ticker is updated?
I'm wondering if there's something like componentDidUpdate in React Native

cuongtd
  • 2,862
  • 3
  • 19
  • 37

2 Answers2

6

While the answer is technically correct, the first example renders too many times and I did not understand the second example unfortunately.

So I got back to React to see how it is done there and it is explained very good here:

This is what the hook (remember function as you will) looks like (for the curious):

function usePrevious<T>(value: T): T {
  // The ref object is a generic container whose current property is mutable ...
  // ... and can hold any value, similar to an instance property on a class
  const ref: any = useRef<T>();
  // Store current value in ref
  useEffect(() => {
    ref.current = value;
  }, [value]); // Only re-run if value changes
  // Return previous value (happens before update in useEffect above)
  return ref.current;
}

The same idea can be implemented in compose un a reusable way (it is important that the @Composable should not be rerendered when setting the previous value):

/**
 * Returns a dummy MutableState that does not cause render when setting it
 */
@Composable
fun <T> rememberRef(): MutableState<T?> {
    // for some reason it always recreated the value with vararg keys,
    // leaving out the keys as a parameter for remember for now
    return remember() {
        object: MutableState<T?> {
            override var value: T? = null

            override fun component1(): T? = value

            override fun component2(): (T?) -> Unit = { value = it }
        }
    }
}

and the actual rememberPrevious:

@Composable
fun <T> rememberPrevious(
    current: T,
    shouldUpdate: (prev: T?, curr: T) -> Boolean = { a: T?, b: T -> a != b },
): T? {
    val ref = rememberRef<T>()

    // launched after render, so the current render will have the old value anyway
    SideEffect {
        if (shouldUpdate(ref.value, current)) {
            ref.value = current
        }
    }

    return ref.value
}

key values can be added to the remember function, but I've found that the remember did not work in my case, as it always rerendered even when no keys were passed in.

Usage:


@Composable
fun SomeComponent() {

    ...
    val prevValue = rememberPrevious(currentValue)
}

andras
  • 3,305
  • 5
  • 30
  • 45
5

I figured out that I could get last value of ticker by using remember {mutableStateOf} as below:

var lastTicker by remember { mutableStateOf(ticker)}

SideEffect {
   if (lastTicker != ticker) {
     // compare lastTicker to current ticker before assign new value
   
     lastTicker = ticker
   }
}

by using remember { mutableStateOf(ticker)}, I can persist value of ticker throught recomposition.

then inside SideEffect I can use lastTicker value ( to compare last ticker and current ticker in my case) before assign it to new value to use for next composition

or using derivedStateOf to watch ticker change only, avoid recomposition

val compareValue by remember(ticker) {
    derivedStateOf {
        // compare lastTicker to current ticker before assign new value

        lastTicker = ticker
       // return value
    }
}
cuongtd
  • 2,862
  • 3
  • 19
  • 37
  • I'm pretty sure that `remember(ticker)` already computes only if the `ticker` changes (that's the whole idea of keys). So the `derivedStateOf` here does functionally nothing – Jakoss Jan 24 '22 at 14:47