Jetpack Compose, Multiple recomposition of the view when updating progressbar - android

I new to Jetpack compose and i'am creating a view to download a file from my api, i have a call back to give me the % or the download and i would like it display it on a progress bar. The problem is that everytime i update the progressbar, it seems to update all my view causing multiples call to my api. Here is my code :
#Composable
fun DownloadView() {
val isDownloading = remember { mutableStateOf(false) }
var downloadProgress by remember { mutableStateOf(0.1f) }
val animatedProgress = animateFloatAsState(
targetValue = downloadProgress,
animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec
).value
println("Recompose view") //this is called 100+ times
Button(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp),
shape = RoundedCornerShape(10.dp),
colors = ButtonDefaults.buttonColors(backgroundColor = colorResource(id = R.color.blue)),
onClick = {
println("click download")
if (!isDownloading.value) {
isDownloading.value = true
Api.download() //stating the download from my api
downloadProgress = Api.callback() //call back of the download progress
//when api is done i also get a call back the i set isDownloading.value = false
}
}
},) {
var text = "Download"
if (isDownloading.value) {
text = "Downloading"
}
Text(text = text, color = Color.White)
if (isDownloading.value) {
CircularProgressIndicator(
animatedProgress,
color = colorResource(id = R.color.orange),
modifier = Modifier.size(28.dp).padding(start = 4.dp)
)
}
}
}
This is working, i got my button with the progress, it change the button text when i'am downloading and i update the progress bar. The problem is that my whole view is updated everytime the value of the progress is update. Is there a way to avoid this or did i missunderstand something ? Thanks for the help.

Related

Scroll to bottom listener Jetpack Compose

I have a page that I want to be scrollable (mostly text only, no list). At the bottom of that page I have a disabled button, but when I reach the bottom of the page I want that button to activate. How can I do this with JetpackCompose Kotlin androidx.compose.ui version 1.3.2?
Much appreciation in advance!
I can't use ScrollableColumn, Scrollable, LazyColumn, LazyRow because of the compose library.
It's not clear why you can't use these components since they are all part of the Compose library. However, this can be done using Column composable with verticalScroll modifier.
val scrollState = rememberScrollState()
Column(Modifier.verticalScroll(scrollState)) {
//Text
}
State for enabling Button, false by default:
val isButtonEnabled by remember {mutableStateOf(false)}
ScrollState has canScrollForward Boolean property, false means we cannot scroll forward (we are the end of the list). Once we have false here, button should be enabled
LaunchedEffect(scrollState.canScrollForward) {
if (!scrollState.canScrollForward) isButtonEnabled = true
}
And button:
Button(enabled = isButtonEnabled, ...)
#Composable
override fun Build() {
val scrollState = rememberScrollState()
val isButtonEnabled by remember {mutableStateOf(false)}
LaunchedEffect(scrollState.canScrollForward) {
if (!scrollState.canScrollForward) isButtonEnabled = true
}
ScreenScaffold(
header = {
TopBar(
title = R.string.insurance_choose_insurance_travel,
onBack = { sendNavEffect.invoke(Effect.Navigation.OnBack) },
actions = {},
backgroundColor = white
)
},
page = {
Column(
Modifier
.fillMaxSize()
.padding(20.dp)
.verticalScroll(scrollState)
){
repeat(100){
Text(
"Text",
color = Color.Black
)
}
}
},
footer = {
Row(modifier = Modifier.padding(20.dp)) {
PrimaryActionButton(
text = R.string.accept_terms_and_conditions_sca.getString(),
buttonState = if(isButtonEnabled) ButtonState.Active else ButtonState.Disabled,
onClick = {}
)
}
})
}
}

How can I animate between 0 and wrap content values ​using compose

I'm trying to make a swipe to reveal component using compose, but I want the width of the card that will appear after the swipe to grow to the size of the wrap content without using it, but I don't understand how to calculate the wrap content size.
var width by remember {
mutableStateOf(0.dp)
}
val lowerTransition = updateTransition(transitionState, "lowerCardTransition")
val lowerOffsetTransition by lowerTransition.animateFloat(
label = "lowerCardOffsetTransition",
transitionSpec = { tween(durationMillis = ANIMATION_DURATION) },
targetValueByState = { if (isRevealed) width.value else 0f },
)
How do I equate the width value used here to the wrap content value?
I'm trying to make the resulting delete button appear all without using a constant value
Try using AnimatedVisibility. For demo purpose I used OnClick, replace it with OnSwipe.
#Preview
#Composable
fun AnimateVisibility2() {
var visible by remember {
mutableStateOf(false)
}
Row(
modifier = Modifier.fillMaxSize(), horizontalArrangement = Arrangement.Center
) {
AnimatedVisibility(
visible = visible, enter = expandHorizontally(), exit = shrinkHorizontally()
) {
IconButton(onClick = { /*TODO*/ }) {
Icon(Icons.Default.Phone, contentDescription = null)
}
}
Button(onClick = { visible = !visible }, Modifier.weight(1f)) {
Text("Click Me")
}
}
}

How to Reset the state of compose views animating

I am building an onboarding fragment that gives users tips for each screen. there are multiple pages of a few lines:
Page 1
this icon does this
that icon does that
Button: Next
Page 2
this icon does this
that icon does that
Button: Finish
I want each view on the page to fade in progressively down the screen.
Then with a new page i want the items to reset and all fade in again from the top down.
I have tried using AnimatedVisibility but the problem is that elements keep their state so the fade effect doesnt play on the second page. Possibly AnimatedVisibility isnt the right choice for what i want to do?
So this is the code for a line. I want to reset the state object on each recomposition.
Or if someone has a better suggetion on how to do it - then that would also be excellent.
#Composable
private fun Line(line: ActionResources, modifier: Modifier) {
val state = remember {
MutableTransitionState(false).apply {
// Start the animation immediately.
targetState = true
}
}
AnimatedVisibility(state,
enter = fadeIn(animationSpec = tween(durationMillis = 1000)),
exit = fadeOut(animationSpec = tween(durationMillis = 1000))
) {
Row(
modifier = modifier
.padding(16.dp)
) {
val color = line.color?.let { colorResource(it) } ?: MaterialTheme.colors.onSurface
line.icon?.also {
Icon(
painter = painterResource(it),
tint = color,
contentDescription = stringResource(id = R.string.menu_search),
modifier = Modifier.padding(end = 8.dp).size(24.dp)
)
}
line.label?.also {
Text(
modifier = Modifier,
style = MaterialTheme.typography.body1,
color = color,
text = it
)
}
}
}
}
I can't compile your code especially ActionResources, but based on this
I want to reset the state object on each recomposition.
I can only suggest supplying a key to your remember {...} using the the line parameter.
val state = remember (line) {
MutableTransitionState(false).apply {
// Start the animation immediately.
targetState = true
}
}
I'm not sure if this would solve your problem, but if you want this state to be re-calculated assuming the line parameter will always be different on succeeding re-compositions, then using it as remember {...}'s key will guarantee a re-calculation.

Composable invocations can only happen from the context of a #Composable function

I have a composable function that i need to call after clicking a button but it keeps showing an error that composables cannot be executed from button clicks , can anyone please guide me to solve this issue Thank you
This is my code
Box(contentAlignment = Alignment.Center) {
Button(onClick = {
// This is a composable function that i need to call
SignUpNewUser(email,fullName,country,password)
}
,modifier = Modifier
.width(200.dp)
.background(color = Color.DarkGray)) {
Text(text = "Sign Up",style = TextStyle(color = Color.White,fontWeight = FontWeight.Bold))
}
}
You can only add a #Composable view to another #Composable view.
onClick is not marked #Composable, so you get this warning. You can only change the state with onClick.
For example, you can create a flag and display the UI depending on that flag:
var signUp by remember { mutableStateOf(false) }
if (signUp) {
SignUpNewUser(email, fullName, country, password)
} else {
Box(contentAlignment = Alignment.Center) {
Button(onClick = {
signUp = true
}, modifier = Modifier
.width(200.dp)
.background(color = Color.DarkGray)) {
Text(text = "Sign Up", style = TextStyle(color = Color.White, fontWeight = FontWeight.Bold))
}
}
}
I suggest you start with this youtube video which explains the basic principles of when you need to use state in compose. You can continue deepening your knowledge with state in Compose documentation.
Another option would be to use Compose Navigation.

Jetpack Compose Change Slider Thumb Size

Is there any way to change slider thumb size? I think for now we can only manipulate colors
var sliderPosition by remember { mutableStateOf(0f) }
Text(text = sliderPosition.toString())
Slider(
value = sliderPosition,
onValueChange = { sliderPosition = it },
valueRange = 0f..100f,
onValueChangeFinished = {
// launch some business logic update with the state you hold
// viewModel.updateSelectedSliderValue(sliderPosition)
},
steps = 5,
colors = SliderDefaults.colors(
thumbColor = MaterialTheme.colors.secondary,
activeTrackColor = MaterialTheme.colors.secondary
)
)
No, this size cannot be modified. The only thing you can do is copy the entire Slider.kt file into your project and modify it.
It is a good idea to give the new view a different name to avoid misunderstandings in the future.
You should change ThumbRadiusconstant, or make it a variable if you need different sizes in your application.
With M3 androidx.compose.material3.Slider you can use the thumb attribute to customize the size.
Something like:
var sliderPosition by remember { mutableStateOf(0f) }
Column {
Text(text = sliderPosition.toString())
Slider(
modifier = Modifier.semantics { contentDescription = "Localized Description" },
value = sliderPosition,
onValueChange = { sliderPosition = it },
valueRange = 0f..5f,
steps = 4,
interactionSource = interactionSource,
onValueChangeFinished = {
// launch some business logic update with the state you hold
},
thumb = {
SliderDefaults.Thumb( //androidx.compose.material3.SliderDefaults
interactionSource = interactionSource,
thumbSize = DpSize(40.dp,40.dp)
)
},
)
}
Note: it requires for material3 at least the version 1.0.0-beta03
I've created a library for easy customization of Slider, since Slider from Material package is not flexible.
https://github.com/krottv/compose-sliders. Below is the code example of how to use it to make thumb size smaller:
var stateSlider by remember { mutableStateOf(0.5f) }
SliderValueHorizontal(
stateSlider, { stateSlider = it },
modifier = Modifier
.fillMaxWidth(),
// desired size of Slider's thumb
thumbSize = DpSize(8.dp, 8.dp)
)
Also you can specify custom composables for thumb and track.
Yes, but only wrapping it with AndroidView and wait for the better future, when Google team release another update in Material lib.
Here is an example
AndroidView(
modifier = Modifier...//,
factory = { context ->
Slider(
ContextThemeWrapper(context, context.theme)
).apply {
// set listeners
it.addOnSliderTouchListener(object : SliderView.OnSliderTouchListener {
#SuppressLint("RestrictedApi")
override fun onStartTrackingTouch(slider: Slider) = Unit
#SuppressLint("RestrictedApi")
override fun onStopTrackingTouch(slider: Slider) {
onValueChangeFinished.invoke()
}
})
it.addOnChangeListener { _, value, _ ->
onValueChanged.invoke(value)
}
// your thumb customization
// your track customization
}
}, update = {
// set value
it.value = currentValue
})
Should be placed inside #Composable
AndroidView in Compose
Slider in Material

Categories

Resources