With your current set up what supposed to happen is imageWidth to be set to 0f after each recomposition because remember is run on composition or any of its key are changed.
/**
* Remember the value returned by [calculation] if all values of [keys] are equal to the previous
* composition, otherwise produce and remember a new value by calling [calculation].
*/
@Composable
inline fun <T> remember(
vararg keys: Any?,
calculation: @DisallowComposableCalls () -> T
): T {
var invalid = false
for (key in keys) invalid = invalid or currentComposer.changed(key)
return currentComposer.cache(invalid, calculation)
}
In LaunchedEffect you set value but when another recomposition happens it's reset to value in remember block.
However something i might have missed here is by rotation if you mean rotating device and recreating Activity, the answer below doesn't work, you can move your value to ViewModel to store latest value or rememberSavable.
If your Activity is not recreated but you want to recalculate block inside remember
add keys to check if there has to be new calculation.
You need to add currentRotation
as key for it to be set only when rotation changes
var imageWidth = remember(currentRotation) {
val ratio = imageBitmap.getRatio(currentRotation)
//some calculation result as float
}
remember with keys is commonly used in default Composable source codes, but i wonder why it' not mentioned in official documents.
Slider for instance use it as
@Composable
fun Slider(
value: Float,
onValueChange: (Float) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
valueRange: ClosedFloatingPointRange<Float> = 0f..1f,
/*@IntRange(from = 0)*/
steps: Int = 0,
onValueChangeFinished: (() -> Unit)? = null,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
colors: SliderColors = SliderDefaults.colors()
) {
require(steps >= 0) { "steps should be >= 0" }
val onValueChangeState = rememberUpdatedState(onValueChange)
val tickFractions = remember(steps) {
stepsToTickFractions(steps)
}
// rest of the code
}
And in painterResource code
@Composable
@ComposableOpenTarget(-1)
fun rememberVectorPainter(
defaultWidth: Dp,
defaultHeight: Dp,
viewportWidth: Float = Float.NaN,
viewportHeight: Float = Float.NaN,
name: String = RootGroupName,
tintColor: Color = Color.Unspecified,
tintBlendMode: BlendMode = BlendMode.SrcIn,
autoMirror: Boolean = false,
content: @Composable @VectorComposable (viewportWidth: Float, viewportHeight: Float) -> Unit
): VectorPainter {
val density = LocalDensity.current
val widthPx = with(density) { defaultWidth.toPx() }
val heightPx = with(density) { defaultHeight.toPx() }
val vpWidth = if (viewportWidth.isNaN()) widthPx else viewportWidth
val vpHeight = if (viewportHeight.isNaN()) heightPx else viewportHeight
val intrinsicColorFilter = remember(tintColor, tintBlendMode) {
if (tintColor != Color.Unspecified) {
ColorFilter.tint(tintColor, tintBlendMode)
} else {
null
}
}
return remember { VectorPainter() }.apply {
// These assignments are thread safe as parameters are backed by a mutableState object
size = Size(widthPx, heightPx)
this.autoMirror = autoMirror
this.intrinsicColorFilter = intrinsicColorFilter
RenderVector(name, vpWidth, vpHeight, content)
}
}
This is a very common approach in many default Composables and the most common one is LaunchedEffect
@Composable
@NonRestartableComposable
@OptIn(InternalComposeApi::class)
fun LaunchedEffect(
key1: Any?,
block: suspend CoroutineScope.() -> Unit
) {
val applyContext = currentComposer.applyCoroutineContext
remember(key1) { LaunchedEffectImpl(applyContext, block) }
}
which runs code block on first composition or when at least one of they keys change.