Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why this function is called multiple times in Jetpack Compose?

I'm currently trying out Android Compose. I have a Text that shows price of a crypto coin. If a price goes up the color of a text should be green, but if a price goes down it should be red. The function is called when a user clicks a button. The problem is that the function showPrice() is called multiple times (sometimes just once, sometimes 2-4 times). And because of that the user can see the wrong color. What can I do to ensure that it's only called once?

MainActivity:

@Composable
fun MainScreen() {
    val priceLiveData by viewModel.trackLiveData.observeAsState()
    val price = priceLiveData ?: return

    when (price) {
                is ViewState.Response -> showPrice(price = price.data)
                is ViewState.Error -> showError(price.text)
            }

    Button(onClick = {viewModel.post()} )
}

@Composable
private fun showPrice(price: Double) {
    lastPrice = sharedPref.getFloat("eth", 0f).toDouble()
    val color by animateColorAsState(if (price >= (lastPrice)) Color.Green else 
Color.Red)
    Log.v("TAG", "last=$lastPrice new = $price")
    editor.putFloat("eth", price.toFloat()).apply()
    Text(
          text = price.toString(),
          color = color,
          fontSize = 28.sp,
          fontFamily = fontFamily,
          fontWeight = FontWeight.Bold
      )
 }

ViewModel:

  @HiltViewModel
  class MyViewModel @Inject constructor(
    private val repository: Repository
): ViewModel() {

   private val _trackLiveData: MutableLiveData<ViewState<Double>> = MutableLiveData()
   val trackLiveData: LiveData<ViewState<Double>>
       get() = _trackLiveData

   fun post(
   ) = viewModelScope.launch(Dispatchers.Default) {
       try {
           val response = repository.post()
           _trackLiveData.postValue(ViewState.Response(response.rate.round(7)))
       } catch (e: Exception) {
           _trackLiveData.postValue(ViewState.Error())
           Log.v("TAG: viewmodelPost", e.message.toString())
    }
  }
}

ViewState:

sealed class ViewState<out T : Any> {
    class Response<out T : Any>(val data: T): ViewState<T>()
    class Error(val text:String = "Unknown error"): ViewState<Nothing>()
}

So when I press Button to call showPrice(). I can see these lines on Log:

2021-06-10 16:39:18.407 16781-16781/com.myapp.myapp V/TAG: last=2532.375732421875 new = 2532.7403716
2021-06-10 16:39:18.438 16781-16781/com.myapp.myapp V/TAG: last=2532.740478515625 new = 2532.7403716
2021-06-10 16:39:18.520 16781-16781/com.myapp.myapp V/TAG: last=2532.740478515625 new = 2532.7403716
like image 693
Paul Sizon Avatar asked Mar 01 '23 13:03

Paul Sizon


1 Answers

What can I do to ensure that it's only called once?

Nothing, that's how it's meant to work. In the View system you would not ask "Why is my view invalidated 3 times?". The framework invalidates (recomposes) the view as it needs, you should not need to know or care when that happens.

The issue with your code is that your Composable is reading the old value from preferences, that is not how it should work, that value should be provided by the viewmodel as part of the state. Instead of providing just the new price, expose a Data Class that has both the new and old price and then use those 2 values in your composable to determine what color to show, or expose the price and the color to use.

like image 58
Francesc Avatar answered Apr 09 '23 14:04

Francesc