Unable to call #Composable function from remember block - android

I want to calculate height of Box in a function and I want compose to remember its result and not call that function again. That function has some composable content and therefore it is a #Composable function.
The problem is that, Compose won't let me call this function from remember block.
This is the code that I have:
val coverImageHeightInDp = remember {
viewModel.calculateTopCoverHeightInDp()
}
Box(modifier = Modifier
.then(modifier)
.fillMaxWidth()
.height(coverImageHeightInDp)
)
And the function in viewModel:
#Composable
fun calculateTopCoverHeightInDp(): Dp {
val originalImageDimens = Size(1440f, 828f)
val imageRatio = originalImageDimens.width / originalImageDimens.height
val configuration = LocalConfiguration.current
val screenWidthInDp = configuration.screenWidthDp.dp
return screenWidthInDp / imageRatio
}
How can I code it in a way that the result of this function is remembered and this function is not called again, until screen rotation?
Thanks.

When your computation functions requires some variable from #Composable scope you can just pass the variable you get in composition such as density or configuration
fun calculateTopCoverHeightInDp(configuration: Configuration): Dp {
val originalImageDimens = Size(1440f, 828f)
val imageRatio = originalImageDimens.width / originalImageDimens.height
val screenWidthInDp = configuration.screenWidthDp.dp
return screenWidthInDp / imageRatio
}
And use it as
val configuration: Configuration = LocalConfiguration.current
val calculatedHeight = remember(configuration){
calculateTopCoverHeightInDp(configuration)
}
Also you can check out this answer for difference between Composable and non-Composable functions.
What are differents between Composable function and normal function in Android?

Related

How can I save data to room just first time in kotlin?

I get some data from api in kotlin and save it to room. I do the saving to Room in the viewmodel of the splash screen. However, each application saves the data to the room when it is opened, I want it to save only once. In this case I tried to do something with shared preferences but I couldn't implement it. Any ideas on this or anyone who has done something similar to this before?
hear is my code
my splash screen ui
#Composable
fun SplashScreen(
navController: NavController,
viewModel : SplashScreenViewModel = hiltViewModel()
) {
val context: Context = LocalContext.current
checkDate(context,viewModel)
val scale = remember {
Animatable(0f)
}
LaunchedEffect(key1 = true) {
scale.animateTo(
targetValue = 0.3f,
animationSpec = tween(
durationMillis = 500,
easing = {
OvershootInterpolator(2f).getInterpolation(it)
}
)
)
delay(2000L)
navController.navigate(Screen.ExchangeMainScreen.route)
}
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Image(
painter = painterResource(id = R.drawable.ic_currency_exchange_logo_512),
contentDescription = "Logo"
)
}
}
fun checkDate(context:Context,viewModel: SplashScreenViewModel){
val sharedPreferences = context.getSharedPreferences("date",Context.MODE_PRIVATE)
val editor = sharedPreferences.edit()
val date = Date(System.currentTimeMillis())
val millis = date.time
editor.apply{
putLong("currentDate", millis)
}.apply()
val sharedDate = sharedPreferences.getLong("currentDate",1)
println(sharedDate)
val isDayPassed = (System.currentTimeMillis() - sharedDate) >= TimeUnit.DAYS.toMillis(1)
if(isDayPassed){
viewModel.update()
}
}
}
my splash screen view model
#HiltViewModel
class SplashScreenViewModel #Inject constructor(
private val insertExchangeUseCase: InsertExchangeUseCase,
private val updateExchangeUseCase: UpdateExchangeUseCase,
private val getAllExchangeUseCase: GetAllExchangeUseCase
) : ViewModel() {
init {
insertExchanges()
}
private fun insertExchanges() {
viewModelScope.launch {
insertExchangeUseCase.insert(DEFAULT_CURRENCY)
}
}
fun update(){
viewModelScope.launch {
updateExchangeUseCase.updateExchange(getAllExchangeUseCase.get(DEFAULT_CURRENCY))
}
}
}
In view model, insert is started automatically in init.
You can’t do something like this properly inside a Composable. Composables are only for creating UI. Remember the rule about no side effects. A function like your checkDate() should be called directly in onCreate() so it isn't called over and over as your Composition recomposes.
The problem with your current checkDate function: You save the "currentDate" every single this time this function is called, and you do it before checking what value was saved there before, so it will perpetually overwrite the old value before you can use it. You're also overcomplicating things by transforming the Long time into a Date and back to a Long for no reason.
Think about the logic of what you're doing:
Update the data if it has been a day since the last time you updated it.
So to implement this, we first check if the previously saved date is more than a day old. Only if it is old do we write the new date and update the data.
Here's a basic implementation. This is assuming you want to update when it has been at least 24 hours since the last update, since that seems like what you were trying to do.
fun checkDate(context: Context, viewModel: SplashScreenViewModel) {
val sharedPreferences = context.getSharedPreferences("date", Context.MODE_PRIVATE)
val lastTimeUpdatedKey = "lastTimeUpdated"
val now = System.currentTimeMillis()
val lastTimeUpdated = sharedPreferences.getLong(lastTimeUpdatedKey, 0)
val isDayPassed = now - lastTimeUpdated > TimeUnit.DAYS.toMillis(1)
if (isDayPassed) {
sharedPreferences.edit { // this is the shortcut way to edit and apply all in one
putLong(lastTimeUpdatedKey, today)
}
viewModel.update()
}
}
I would also change "date" to something longer and more unique so you don't accidentally use it somewhere else for different shared preferences. And I would rename your update() function to something more descriptive.
Don't forget to move the function call out of your Composable!

Best practice for hiltviewmodel in compose

So i have a few questions about using hiltviewmodels, states and remember in compose.
For some context, i have a ViewPager set up
HorizontalPager(
count = 4,
modifier = Modifier.fillMaxSize(),
state = pagerState,
) { page ->
when (page) {
0 -> PagerOne()
1 -> PagerTwo()
2 -> PagerThree()
3 -> PagerFour()
}
}
Lets say i have a State in my viewmodel declared like this
private val _data: MutableState<DataClass> = mutableStateOf(DataClass())
var data: State<DataClass> = _data
First, where do i inject my viewmodel? Is it fine to do it in the constructor of my pager composable?
#Composable
fun PagerOne(viewmodel : PagerOneViewmodel = hiltViewModel()) {
...
And if i want to get the value from that viewmodel state, do i need to wrap it into a remember lambda?
#Composable
fun PagerOne(viewmodel : PagerOneViewmodel = hiltViewModel()) {
val myState = viewmodel.data or var myState by remember { viewmodel.data }
Next question about flow and .collectasstate. Lets say i have a function in my viewmodel which returns a flow of data from Room Database.
fun getRoomdata() = roomRepository.getLatesData()
Is it correct to get the data like this in my composable?
val roomData = viewmodel.getRoomdata().collectasState(initial = emptyRoomdata())
Everything is working like expected, but im not sure these are the best approaches.

Can I replace produceState with mutableStateOf in the Compose sample project?

The following Code A is from the project.
uiState is created by the delegate produceState, can I use mutableStateOf instead of produceState? If so, how can I write code?
Why can't I use Code B in the project?
Code A
#Composable
fun DetailsScreen(
onErrorLoading: () -> Unit,
modifier: Modifier = Modifier,
viewModel: DetailsViewModel = viewModel()
) {
val uiState by produceState(initialValue = DetailsUiState(isLoading = true)) {
val cityDetailsResult = viewModel.cityDetails
value = if (cityDetailsResult is Result.Success<ExploreModel>) {
DetailsUiState(cityDetailsResult.data)
} else {
DetailsUiState(throwError = true)
}
}
when {
uiState.cityDetails != null -> {
...
}
#HiltViewModel
class DetailsViewModel #Inject constructor(
private val destinationsRepository: DestinationsRepository,
savedStateHandle: SavedStateHandle
) : ViewModel() {
private val cityName = savedStateHandle.get<String>(KEY_ARG_DETAILS_CITY_NAME)!!
val cityDetails: Result<ExploreModel>
get() {
val destination = destinationsRepository.getDestination(cityName)
return if (destination != null) {
Result.Success(destination)
} else {
Result.Error(IllegalArgumentException("City doesn't exist"))
}
}
}
data class DetailsUiState(
val cityDetails: ExploreModel? = null,
val isLoading: Boolean = false,
val throwError: Boolean = false
)
Code B
#Composable
fun DetailsScreen(
onErrorLoading: () -> Unit,
modifier: Modifier = Modifier,
viewModel: DetailsViewModel = viewModel()
) {
val cityDetailsResult = viewModel.cityDetails
val uiState=if (cityDetailsResult is Result.Success<ExploreModel>) {
DetailsUiState(cityDetailsResult.data)
} else {
DetailsUiState(throwError = true)
}
...
uiState is created by the delegate produceState, can I use mutableStateOf instead of produceState? If so, how can I write code?
No, you can't write it using the mutableStateOf (direct initialization not possible). In order to understand why it not possible we need to understand the use of produceState
According to documentation available here
produceState launches a coroutine scoped to the Composition that can
push values into a returned State. Use it to convert non-Compose state
into Compose state, for example bringing external subscription-driven
state such as Flow, LiveData, or RxJava into the Composition.
So basically it is compose way of converting non-Compose state to compose the state.
if you still want to use mutableStateOf you can do something like this
var uiState = remember { mutableStateOf(DetailsUIState())}
LaunchedEffect(key1 = someKey, block = {
uiState = if (cityDetailsResult is Result.Success<ExploreModel>) {
DetailsUiState(cityDetailsResult.data)
} else {
DetailsUiState(throwError = true)
}
})
Note: here someKey might be another variable which handles the recomposition of the state
What is wrong with this approach?
As you can see it's taking another variable someKey to recomposition. and handling it is quite tough compared to produceState
Why can't I use Code B in the project?
The problem with code B is you don't know whether the data is loaded or not while displaying the result. It's not observing the viewModel's data but its just getting the currently available data and based on that it gives the composition.
Imagine if the viewModel is getting data now you will be having UiState with isLoading = true but after some time you get data after a successful API call or error if it fails, at that time the composable function in this case DetailsScreen doesn't know about it at all unless you are observing the Ui state somewhere above this composition and causing this composition to recompose based on newState available.
But in produceState the state of the ui will automatically changed once the suspended network call completes ...

Remember LazyColumn Scroll Position - Jetpack Compose

I'm trying to save/remember LazyColumn scroll position when I navigate away from one composable screen to another. Even if I pass a rememberLazyListState to a LazyColumn the scroll position is not saved after I get back to my first composable screen. Can someone help me out?
#ExperimentalMaterialApi
#Composable
fun DisplayTasks(
tasks: List<Task>,
navigateToTaskScreen: (Int) -> Unit
) {
val listState = rememberLazyListState()
LazyColumn(state = listState) {
itemsIndexed(
items = tasks,
key = { _, task ->
task.id
}
) { _, task ->
LazyColumnItem(
toDoTask = task,
navigateToTaskScreen = navigateToTaskScreen
)
}
}
}
Well if you literally want to save it, you must store it is something like a viewmodel where it remains preserved. The remembered stuff only lasts till the Composable gets destroyed. If you navigate to another screen, the previous Composables are destroyed and along with them, the scroll state
/**
* Static field, contains all scroll values
*/
private val SaveMap = mutableMapOf<String, KeyParams>()
private data class KeyParams(
val params: String = "",
val index: Int,
val scrollOffset: Int
)
/**
* Save scroll state on all time.
* #param key value for comparing screen
* #param params arguments for find different between equals screen
* #param initialFirstVisibleItemIndex see [LazyListState.firstVisibleItemIndex]
* #param initialFirstVisibleItemScrollOffset see [LazyListState.firstVisibleItemScrollOffset]
*/
#Composable
fun rememberForeverLazyListState(
key: String,
params: String = "",
initialFirstVisibleItemIndex: Int = 0,
initialFirstVisibleItemScrollOffset: Int = 0
): LazyListState {
val scrollState = rememberSaveable(saver = LazyListState.Saver) {
var savedValue = SaveMap[key]
if (savedValue?.params != params) savedValue = null
val savedIndex = savedValue?.index ?: initialFirstVisibleItemIndex
val savedOffset = savedValue?.scrollOffset ?: initialFirstVisibleItemScrollOffset
LazyListState(
savedIndex,
savedOffset
)
}
DisposableEffect(Unit) {
onDispose {
val lastIndex = scrollState.firstVisibleItemIndex
val lastOffset = scrollState.firstVisibleItemScrollOffset
SaveMap[key] = KeyParams(params, lastIndex, lastOffset)
}
}
return scrollState
}
example of use
LazyColumn(
state = rememberForeverLazyListState(key = "Overview")
)
#Composable
fun persistedLazyScrollState(viewModel: YourViewModel): LazyListState {
val scrollState = rememberLazyListState(viewModel.firstVisibleItemIdx, viewModel.firstVisibleItemOffset)
DisposableEffect(key1 = null) {
onDispose {
viewModel.firstVisibleItemIdx = scrollState.firstVisibleItemIndex
viewModel.firstVisibleItemOffset = scrollState.firstVisibleItemScrollOffset
}
}
return scrollState
}
}
Above I defined a helper function to persist scroll state when a composable is disposed of. All that is needed is a ViewModel with variables for firstVisibleItemIdx and firstVisibleItemOffet.
Column(modifier = Modifier
.fillMaxSize()
.verticalScroll(
persistedScrollState(viewModel = viewModel)
) {
//Your content here
}
The LazyColumn should save scroll position out of the box when navigating to next screen. If it doesn't work, this may be a bug described here (issue tracker). Basically check if the list becomes empty when changing screens, e.g. because you observe a cold Flow or LiveData (so the initial value is used).
#Composable
fun persistedScrollState(viewModel: ParentViewModel): ScrollState {
val scrollState = rememberScrollState(viewModel.scrollPosition)
DisposableEffect(key1 = null) {
onDispose {
viewModel.scrollPosition = scrollState.value
}
}
return scrollState
}
Above I defined a helper function to persist scroll state when a composable is disposed of. All that is needed is a ViewModel with a scroll position variable.
Hope this helps someone!
Column(modifier = Modifier
.fillMaxSize()
.verticalScroll(
persistedScrollState(viewModel = viewModel)
) {
//Your content here
}

Jetpack Compose: react to Slider changed value

I want to show a slider that can be either updated by the user using drag/drop, or updated from the server in real-time.
When the user finishes their dragging gesture, I want to send the final value to the server.
My initial attempt is:
#Composable
fun LightView(
channel: UiChannel,
onDimmerChanged: (Float) -> Unit
) {
val sliderValue = channel....
Slider(value = sliderValue, onValueChange = onDimmerChanged)
}
And the onDimmerChanged method comes from my ViewModel, which updates the server value.
It works well, however onValueChange is called for each move, which bombards the server with unneeded requests.
I tried to create a custom slider:
#Composable
fun LightView(
channel: UiChannel,
onDimmerChanged: (Float) -> Unit
) {
val sliderValue = channel....
Slider(initialValue = sliderValue, valueSetter = onDimmerChanged)
}
#Composable
fun Slider(initialValue: Float, valueSetter: (Float) -> Unit) {
var value by remember { mutableStateOf(initialValue) }
Slider(
value = value,
onValueChange = { value = it },
onValueChangeFinished = { valueSetter(value) }
)
}
It works well on the app side, and the value is only sent once, when the user stops dragging.
However, it fails updating the view when there is an update from the server. I'm guessing this has something to do with remember. So I tried without remember:
#Composable
fun Slider(initialValue: Float, valueSetter: (Float) -> Unit) {
var value by mutableStateOf(initialValue)
Slider(
value = value,
onValueChange = { value = it },
onValueChangeFinished = { valueSetter(value) }
)
}
This time the view updates correctly when the value is updated by the server, but does not move anymore when the user drags the slider.
I'm sure I'm missing something with state hoisting and all, but I can't figure out what.
So my final question: how to create a Slider that can be either updated by the ViewModel, or by the user, and notifies the ViewModel of a new value only when the user finishes dragging?
EDIT:
I also tried what #CommonsWare suggested:
#Composable
fun LightView(
channel: UiChannel,
onDimmerChanged: (Float) -> Unit
) {
val sliderValue = channel....
val sliderState = mutableStateOf(sliderValue)
Slider(state = sliderState, valueSet = { onDimmerChanged(sliderState.value) })
}
#Composable
fun Slider(state: MutableState<Float>, valueSet: () -> Unit) {
Slider(
value = state.value,
onValueChange = { state.value = it },
onValueChangeFinished = valueSet
)
}
And it does not work either. When using drag and drop, sliderState is correctly updated, and onDimmerChanged() is called with the correct value. But for some reason, when tapping of the sliding (rather than sliding), valueSet is called and sliderState.value does not contain the correct value. I don't understand where this value comes from.
Regarding the original problem with local & server (or viewmodel) states conflicting with eachother:
I solved it for me by detecting wether or not we are interacting with the slider:
if yes, then show and update the local state value
or if not - then show the viewmodels value.
As you have said, we should never update the viewmodel from onValueChange - as this is only for updating the sliders value locally (documentation). Instead onValueChangeFinished is used for sending the current local state to the viewmodel, as soon as we are done interacting.
Regarding detection of current interaction, we can make use of InteractionSource.
Working example:
#Composable
fun SliderDemo() {
// In this demo, ViewModel updates its progress periodically from 0f..1f
val viewModel by remember { mutableStateOf(SliderDemoViewModel()) }
Column(
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
// local slider value state
var sliderValueRaw by remember { mutableStateOf(viewModel.progress) }
// getting current interaction with slider - are we pressing or dragging?
val interactionSource = remember { MutableInteractionSource() }
val isPressed by interactionSource.collectIsPressedAsState()
val isDragged by interactionSource.collectIsDraggedAsState()
val isInteracting = isPressed || isDragged
// calculating actual slider value to display
// depending on wether we are interacting or not
// using either the local value, or the ViewModels / server one
val sliderValue by derivedStateOf {
if (isInteracting) {
sliderValueRaw
} else {
viewModel.progress
}
}
Slider(
value = sliderValue, // using calculated sliderValue here from above
onValueChange = {
sliderValueRaw = it
},
onValueChangeFinished = {
viewModel.updateProgress(sliderValue)
},
interactionSource = interactionSource
)
// Debug interaction info
Text("isPressed: ${isPressed} | isDragged: ${isDragged}")
}
}
Hope that helps.
There are a couple of things going on here, so let's try to break it down.
The initial attempt where onDimmerChanged is called for every value change looks great!
Looking at the second attempt, creating a custom Slider component works, but there are a few issues.
#Composable
fun LightView(
channel: UiChannel,
onDimmerChanged: (Float) -> Unit
) {
val sliderValue = channel....
Slider(initialValue = sliderValue, valueSetter = onDimmerChanged)
}
#Composable
fun Slider(initialValue: Float, valueSetter: (Float) -> Unit) {
var value by remember { mutableStateOf(initialValue) }
Slider(
value = value,
onValueChange = { value = it },
onValueChangeFinished = { valueSetter(value) }
)
}
Let's talk about what happens in the Slider composable here:
We remember the state with the initialValue
The channel value is updated, LightView gets recomposed, so does Slider
Since we remembered the state, it is still set to the previous initialValue
You're right with your thought about remember being the culprit here. When memorizing a value, it won't be updated when recomposing unless we tell Compose to. But without memorization (as seen in your third attempt), a state model will be created with every recomposition (var value by mutableStateOf(initialValue)) using the initialValue. Since a recomposition is triggered every time value changes, we will always pass the initialValue instead of the updated value to the Slider, causing it to never update by changes from within this Composable.
Instead, we want to pass initialValue as a key to remember, telling Compose to recalculate the value whenever the key changes.
#Composable
fun Slider(initialValue: Float, valueSetter: (Float) -> Unit) {
var value by remember { mutableStateOf(initialValue) }
Slider(
value = value,
onValueChange = { value = it },
onValueChangeFinished = { valueSetter(value) }
)
}
You can probably also just pull the Slider into your LightView:
#Composable
fun LightView(
channel: UiChannel,
onDimmerChanged: (Float) -> Unit
) {
var value by remember(channel.sliderValue) { mutableStateOf(channel.sliderValue) }
Slider(
value = value,
onValueChange = { value = it },
onValueChangeFinished = { onDimmerChanged(value) }
)
}
Lastly, about the attempt that #commonsware suggested.
#Composable
fun LightView(
channel: UiChannel,
onDimmerChanged: (Float) -> Unit
) {
val sliderValue = channel....
val sliderState = mutableStateOf(sliderValue)
Slider(state = sliderState, valueSet = { onDimmerChanged(sliderState.value) })
}
#Composable
fun Slider(state: MutableState<Float>, valueSet: () -> Unit) {
Slider(
value = state.value,
onValueChange = { state.value = it },
onValueChangeFinished = valueSet
)
}
This is another way of doing the same thing, but passing around MutableStates is an anti-pattern and should be avoided if possible. This is where state hoisting helps (what you were trying to do in the earlier attempts :))
Finally, about the Slider's onValueChangeFinished using the wrong value.
And it does not work either. When using drag and drop, sliderState is correctly updated, and onDimmerChanged() is called with the correct value. But for some reason, when tapping of the sliding (rather than sliding), valueSet is called and sliderState.value does not contain the correct value. I don't understand where this value comes from.
This is a bug in the Slider component. You can check this by looking at the output of this code:
#Composable
fun Slider(initialValue: Float, valueSetter: (Float) -> Unit) {
var value by remember { mutableStateOf(initialValue) }
val key = Random.nextInt()
Slider(
value = value,
onValueChange = { value = it },
onValueChangeFinished = {
valueSetter(value)
println("Callback invoked. Current key: $key")
}
)
}
Since key should change with every recomposition, you can see that the onValueChangeFinished callback holds a reference to an older composition (hope I put that right). So you weren't going crazy, it's not your fault :)
Hope that helped clear things up a bit!
Yeah, I can reproduce your issue as well. I answered in the bug topic, but I'll copy-paste it here so other people can fix it if they're in a rush. This solution sadly involves copying the whole Slider class.
The problem is that the drag and click modifiers use different Position instances, although they should always be the same (as Position is being created inside of remember).
The issue is with the pointer input modifier.
val press = if (enabled) {
Modifier.pointerInput(Unit) {...}
Change the Modifier.pointerInput() to accept any other subject different than Unit so your Position instance in that Modifier is always up to date and updated when Position gets recreated. For example, you can change it to Modifier.pointerInput(valueRange)
I encountered this problem. As jossiwolf pointed out, using the progress as a key for the remember{} is necessary to ensure that the Slider progress is updated after recomposition.
I had an additional issue though, where, if I updated the progress mid-seek, the Slider would recompose, and the thumb would revert back to its old position.
To work around this, I'm using a temporary slider position, which is only used while a drag is in progress:
#Composable
fun MySlider(
progress: Float,
onSeek: (progress: Float) -> Unit,
) {
val sliderPosition = remember(progress) { mutableStateOf(progress) }
val tempSliderPosition = remember { mutableStateOf(progress) }
val interactionSource = remember { MutableInteractionSource() }
val isDragged = interactionSource.collectIsDraggedAsState()
Slider(
value = if (isDragged.value) tempSliderPosition.value else sliderPosition.value,
onValueChange = { progress ->
sliderPosition.value = progress
tempSliderPosition.value = progress
},
onValueChangeFinished = {
sliderPosition.value = tempSliderPosition.value
onSeek(tempSliderPosition.value)
},
interactionSource = interactionSource
)
}
I've elaborated on Steffen's answer a bit so it is possible to update the "external" "viewModel" value even while the user is currently dragging the slider (as there are many use cases when that is required).
The usage is the same as the compose "native" Slider, with the exception that the onValueChangeFinished parameter now accepts the finishing float value.
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsDraggedAsState
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.material.Slider
import androidx.compose.material.SliderColors
import androidx.compose.material.SliderDefaults
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
/**
* "Wrapper" around the Slider solving the issue of overwriting values during drag. See https://stackoverflow.com/questions/66386039/jetpack-compose-react-to-slider-changed-value.
* Use as normal Slider and feel free to update the underlying value in the onValueChange method.
*/
#Composable
fun ComposeSlider(
value: Float,
onValueChange: (Float) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
valueRange: ClosedFloatingPointRange<Float> = 0f..1f,
steps: Int = 0,
onValueChangeFinished: ((Float) -> Unit)? = null,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
colors: SliderColors = SliderDefaults.colors()
) {
// local slider value state with default value (from the "external" source)
var sliderValueRaw by remember { mutableStateOf(value) }
// getting current interaction with slider - are we pressing or dragging?
val isPressed by interactionSource.collectIsPressedAsState()
val isDragged by interactionSource.collectIsDraggedAsState()
val isInteracting = isPressed || isDragged
// calculating actual slider value to display depending on whether we are interacting or not
// using either the local value, or the provided one
val determinedValueToShow by remember(isInteracting, sliderValueRaw, value) {
derivedStateOf {
if (isInteracting) {
sliderValueRaw
} else {
value
}
}
}
Slider(
value = determinedValueToShow,
onValueChange = {
sliderValueRaw = it
onValueChange.invoke(it)
},
modifier = modifier,
enabled = enabled,
valueRange = valueRange,
steps = steps,
onValueChangeFinished = {
onValueChangeFinished?.invoke(sliderValueRaw)
},
interactionSource = interactionSource,
colors = colors
)
}

Categories

Resources