2

I have some custom composable animation that is changes value in the range [minTemperature, maxTemperature] using LaunchedEffect, and I want to trigger the animation manually. This triggered when I change the minTemperature and maxTemperature values, so I have LaunchedEffect(minTemperature, maxTemperature). But the problem is that the mutable state is triggered, ONLY WHEN THE VALUE IS CHANGED TO NEW VALUE.

So if I call the method setTemperature() the animation would not be triggered. So what I did is create new state value 'temperatureAnimationSwitch', which is switch manually with the public method startTemperatureAnimation() from the view model. So that will trigger relaunch of the LaunchedEffect.

My questions are:

  1. Is that the best approach using switch value?
  2. Is there a way to trigger the animation, when the minTemperature or maxTemperature values are changes event when set to their previous value?
  3. Is there a object similar to 'state, flow, livedata..', but it can be triggered, even when we change its current value to it self.
@HiltViewModel
class CityWeatherViewModel @Inject constructor(
    private val getCityWeather: GetCityWeather
) : ViewModel() {

    //region Temperature Component
    private val _temperatureAnimationSwitch = mutableStateOf(true)
    val temperatureAnimationSwitch: State<Boolean> = _temperatureAnimationSwitch
 
    private val _minTemperature = mutableStateOf(-22f)
    val minTemperature: State<Float> = _minTemperature

    private val _maxTemperature = mutableStateOf(12f)
    val maxTemperature: State<Float> = _maxTemperature

    fun startTemperatureAnimation() {
        _temperatureAnimationSwitch.value = !_temperatureAnimationSwitch.value
    }

    fun setTemperature(minTemperature: Float = _minTemperature.value, maxTemperature: Float = _maxTemperature.value) {
        _minTemperature.value = minTemperature
        _maxTemperature.value = maxTemperature 
    }
    //endregion
}
@Composable
fun TemperatureCanvas( 
    viewModel: CityWeatherViewModel, 
    minTemperature: Float = viewModel.minTemperature.value,
    maxTemperature: Float = viewModel.maxTemperature.value,
    temperatureAnimationSwitch: Boolean = viewModel.temperatureAnimationSwitch.value
) {

    var temperatureForText by remember { mutableStateOf(0) }
    LaunchedEffect(temperatureAnimationSwitch) {
        val animation = TargetBasedAnimation(
            animationSpec = tween(1500),
            typeConverter = Int.VectorConverter,
            initialValue = minTemperature,
            targetValue = maxTemperature
        )
        val startTime = withFrameNanos { it }
        do {
            val playTime = withFrameNanos { it } - startTime
            temperatureForText = animation.getValueFromNanos(playTime)
        } while (temperatureForText < maxTemperature)
    }
    
    // the text will be update with the animated value 'temperatureForText'
    Text(text = temperatureForText) 
}

slaviboy
  • 1,407
  • 17
  • 27

1 Answers1

1

I think you need to set SnapshotMutationPolicy of State object to neverEqualPolicy(). This will report all the writes to that object irrespective of whether the value is changed or not.

    private val _minTemperature = mutableStateOf(-22f, neverEqualPolicy())
    private val _maxTemperature = mutableStateOf(12f, neverEqualPolicy())

Shreyash.K
  • 487
  • 4
  • 14