How to i chain two parts of an animation together in jetpack compose so the the offset increases then decreases? - kotlin

Ive recently got into doing animations using jet pack compose and am wondering how you can make it so that when you increase a value in an offset, once the animation reaches that value it then changes the value to another value. So like update transition but instead of at the same time, one after the other.

Actually #RaBaKa's answer is partially correct, but it's missing information about how the animation should be run.
It should be done as a side effect. For example, you can use LaunchedEffect: it is already running in a coroutine scope. It is perfectly normal to run one animation after another - as soon as the first suspend function finishes, the second will be started:
val value = remember { Animatable(0f) }
LaunchedEffect(Unit) {
value.animateTo(
20f,
animationSpec = tween(2000),
)
value.animateTo(
10f,
animationSpec = tween(2000),
)
}
Text(value.value.toString())
If you want to do this in response to some action, such as pressing a button, you need to run the coroutine yourself. The main thing is to run the animations in the same coroutine so that they are chained.
val value = remember { Animatable(0f) }
val scope = rememberCoroutineScope()
Button(onClick = {
scope.launch {
value.animateTo(
20f,
animationSpec = tween(2000),
)
value.animateTo(
10f,
animationSpec = tween(2000),
)
}
}) {
}
Text(value.value.toString())

The correct answer is to use Kotlin coroutines. I managed to get it working fine. You have to use coroutines in order to launch the animations in the correct sequence like this:
animationRoutine.launch {
coroutineScope {
launch {
animate(
startingValue,
targetValue,
animationSpec = whatYouWant,
block = { value, _ -> whateverYouNeed = value }
)
}
launch {
animate(
initialValue,
targetValue,
animationSpec = whatYouWant,
block = { value, _ -> whateverYouNeed = value }
)
}
}
Each of launch scope launches everything in a non blocking way if you tell it to allowing you to run multiple animations at once at a lower level and to sequence the animations you add another coroutine for the next part of the animation.

Maybe you can use Animatable
val value = remember { Animatable(0f) } //Initial Value
Then in compose you can just use
value.animateTo(20f)
then
value.animateTo(10f)
For more information visit the official documentation

Related

Why doesn't App crash when I use collect a flow from the UI directly from launch in Jetpack Compose?

I have read the article. I know the following content just like Image B.
Warning: Never collect a flow from the UI directly from launch or the launchIn extension function if the UI needs to be updated. These functions process events even when the view is not visible. This behavior can lead to app crashes. To avoid that, use the repeatOnLifecycle API as shown above.
But the Code A can work well without wrapped with repeatOnLifecycle, why?
Code A
#Composable
fun Greeting(handleMeter: HandleMeter,lifecycleScope: LifecycleCoroutineScope) {
Column(
modifier = Modifier.fillMaxSize()
) {
var my by remember { mutableStateOf(5)}
Text(text = "OK ${my}")
var dataInfo = remember { handleMeter.uiState }
lifecycleScope.launch {
dataInfo.collect { my=dataInfo.value }
}
}
class HandleMeter: ViewModel() {
val uiState = MutableStateFlow<Int>(0)
...
}
Image B
Code A will not work in real life. If you need to run some non-UI code in a composable function, use callbacks (like onClick) or LaunchedEffect (or other side effects).
LaunchedEffect {
dataInfo.collect {my=dataInfo.value}
}
Side effects are bound to composables, there is no need to specify the owner of their lifecycle directly.
Also, you can easily convert any flow to state:
val my = handleMeter.uiState.collectAsState()

Can I use kotlin-coroutines run a task periodically?

It seems that I can use Code A to run a task periodically.
You know soundDb() can be fired every 100ms, it's just like to run periodically.
Is it a good way to use kotlin-coroutines run a task periodically?
Code A
fun calCurrentAsyn() {
viewModelScope.launch {
var b = 0.0
for (i in 1..5) {
b = b + soundDb()
delay(100)
}
b = b / 5.0
myInfo.value = b.toString() + " OK Asyn " + a++.toString()
}
}
suspend fun soundDb(): Double {
var k=0.0
for (i in 1..500000000){
k=k+i
}
return k
}
Added Content:
To Joffrey: Thanks!
1: I know that Code B is the better style, will the effect of execution be same between Code A and Code B ?
Code B
viewModelScope.launch {
val b = computeCurrent()
myInfo.value = "$b OK Asyn ${a++}"
}
suspend fun computeCurrent(): Double {
var b = 0.0
repeat(5) {
b += soundDb()
delay(100)
}
return b / 5.0
}
suspend fun soundDb(): Double {
var k=0.0
for (i in 1..500000000){
k=k+i
}
return k
}
2: I hope to get information regularly from a long running coroutine with a periodic task, how can I cancel the flow soundDbFlow().runningAverage() ?
Code C
viewModelScope.launch {
soundDbFlow().runningAverage().collect {
println("Average = $it") // do something with it
}
}
3: You know I can use Timer().scheduleAtFixedRate to get information regularly in background thread just like Code D, which is the between Timer().scheduleAtFixedRate and Flow ?
Code D
private fun startTimer() {
timer = Timer()
timer.scheduleAtFixedRate(timerTask {
recordingTime = recordingTime + 1
val temp = fromCountToTimeByInterval(recordingTime, 10)
_timeElapse.postValue(temp)
}, 0, 10)
}
private fun stopTimer() {
timer.cancel()
recordingTime = 0
_timeElapse.postValue("00:00.00")
}
The approach (launch + loop) to repeat a task is not bad in itself, but the question is rather about how you want this coroutine to affect the rest of the application.
It's hard to tell whether this is an example for the sake of the question, or your actual code. If it is your actual code, your use case is not a classic "periodic task run":
it has a fixed number of iterations
it only has side effects at the end of the execution
This is an indication that it may make more sense to write this code as a suspend function instead:
suspend fun computeCurrent(): Double {
var b = 0.0
repeat(5) {
b += soundDb()
delay(100)
}
return b / 5.0
}
And then use it like this, to make it clearer where the results are used:
viewModelScope.launch {
val b = computeCurrent()
myInfo.value = "$b OK Asyn ${a++}"
}
Maybe you won't actually need to launch that coroutine in an async way (it probably depends on how you make other similar calls).
If you needed to get information regularly (not just at the end) from a long running coroutine with a periodic task, you might want to consider building a Flow instead and collecting it to apply the side-effects:
import kotlinx.coroutines.flow.*
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
fun soundDbFlow(period: Duration = 100.milliseconds) = flow {
while (true) {
emit(soundDb())
delay(period)
}
}
fun Flow<Double>.runningAverage(): Flow<Double> = flow {
var valuesCount = 0
var sum = 0.0
collect { value ->
sum += value
valuesCount++
emit(sum / valuesCount)
}
}
And then the usage could be something like:
viewModelScope.launch {
soundDbFlow().take(5).runningAverage().collect {
println("Average = $it") // do something with it
}
}
About the amended question:
I know that Code B is the better style, will the effect of execution be same between Code A and Code B ?
Code A and Code B behave the same. My point was indeed half about the style, because making it a simple suspend function makes it clear (to you and readers) that it only returns a single value. This seems to be a mistake that you make also in your newly added soundDb() function and I'm not sure it's clear to you that loops are not streams, and that you're only returning one value from those functions (not updating anything several times).
The other half of my point was that, since it's only a single value that you updated, it may not even need to be run in a long-running coroutine. You might integrate it with other pieces of suspending code where needed.
how can I cancel the flow soundDbFlow().runningAverage() ?
The flow is automatically canceled if the collecting coroutine is cancelled (either via the job you launched or by cancelling the whole viewModelScope - which happens automatically when the component is not needed). The flow is also cancelled if you use terminal operators that end the collection early, such as first(), takeWhile(), take(n).collect { .. }, etc.
You know I can use Timer().scheduleAtFixedRate to get information regularly in background thread just like Code D, which is the between Timer().scheduleAtFixedRate and Flow ?
It's up to you honestly. If you're using coroutines already, I'd personally favor the flow approach. scheduleAtFixedRate will not be integrated with structured concurrency and will require manual management of the cancellation.

Do I need use remember only for a title in Scaffold?

I'm learning Compose, the following code is from the article.
The author use var toolbarTitle by remember { mutableStateOf("Home") } only for a title, is it necessary ?
I think var toolbarTitle= mutableStateOf("Home") is enough, right?
Source Code
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
JetpackComposeScaffoldLayoutTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
var toolbarTitle by remember {
mutableStateOf("Home")
}
val scaffoldState =
rememberScaffoldState(rememberDrawerState(initialValue = DrawerValue.Closed))
val scope = rememberCoroutineScope()
Scaffold(
modifier = Modifier.background(Color.White),
scaffoldState = scaffoldState,
topBar = {
AppToolbar(
scaffoldState = scaffoldState,
scope = scope,
toolbarTitle = toolbarTitle
)
}, drawerContent = {
DrawerContent(scaffoldState = scaffoldState, scope = scope)
},
...
)
}
}
}
}
}
If you don't use remember the value will reset again to Home on every recomposition by using remember the value will be persisted even after a recomposition
by the way recomposition means when the composable renders again which can happen a lot of times when something changes on the screen and needs to be rendered again
I think, if the article is from a reputed source, the variable might have some further usage in the project. The very reason for initialising the variable as a MutableStateis that the developer wants recompositions to occur upon the change of this value.
If this was not the case, it could have been just var title = "Home", or better instead just use "Home" in the parameter, no need of a variable at all. You see, if you are creating a MutableState, in most scenarios, it is useless to declare it without using remember. In fact, the only scenario I can think of, to declare a MutableState without remeber is to trigger recompositions manually using the var as a trigger.
Anyway, most of the times, you want to read the value of the var that is declared MutableState. If any modifications are made to the value of the var, then a recomposition is triggered. Now, if you declare it without any rememberance, the value will be re-initlaised to whatever you provided as the initial value. The updated value is gone for good in this case.
Hence, in the latest versions of the compiler, I think it will not even allow you to create a MutableState var without using remember. If not a compile-time error, I'm sure it gives at least a warning (though I am almost certain it won't allow you to compile, which makes me think Compose Developers do not want us to trigger dummy recompositions!)
PS: The recompositions can be triggered manually by using remember too, so I guess that was not their motto.
If you want to change toolbarTitle later in your code you would have to use remember { mutableStateOf("Home") }
If it is always supposed to be "Home" you can just use val toolbarTitle = "Home" or use "Home" directly in AppToolbar()

Flow - pause/resume flow

In RxJava there is the valve operator that allows to pause (and buffer) a flow and resumes the flow again (and also emit the buffered values as soon as it's resumed). It's part of the rx java extensions (https://github.com/akarnokd/RxJavaExtensions/blob/3.x/src/main/java/hu/akarnokd/rxjava3/operators/FlowableValve.java).
Is there something like this for kotlin flows?
My use case is that I want to observe a flow inside an activity and never lose an event (like I would do it with LiveData e.g. which stops observing data if the activity is paused). So while the activity is paused I want the flow to buffer observed values until the activity is resumed and emit them all as soon as the activity is resumed.
So while the activity is created (until it is destroyed) I want to observe the flow BUT I only want to emit values while the activity is active and buffer the values while it is not active (but still created) until it gets active again.
Is there something to solve this or has anyone ever written something to solve this?
A combination of Lifecycle.launchWhenX and a SharedFlow should do the trick. Here's a simple example using a flow that emits a number every second.
// In your ViewModel
class MainViewModel : ViewModel() {
val numbers = flow {
var counter = 0
while (true) {
emit(counter++)
delay(1_000L)
}
}
.shareIn(
scope = viewModelScope,
started = SharingStarted.Lazily
)
}
// In your Fragment.onViewCreated()
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
viewModel.numbers
.collect { number ->
Log.d("asdf", "number: $number")
}
}
This works because Lifecycle.launchWhenStarted pauses the coroutine when the Lifecycle enters a stopped state, rather than cancels it. When your Lifecycle comes back to a started state after pausing, it'll collect everything that happened while in the stopped state.
I know it is ugly solution but it works fine for me:
fun main() {
val flow = MutableSharedFlow<String>(extraBufferCapacity = 50, onBufferOverflow = BufferOverflow.DROP_OLDEST)
val isOpened = AtomicBoolean()
val startTime = System.currentTimeMillis()
GlobalScope.launch(Executors.newSingleThreadExecutor().asCoroutineDispatcher()) {
flow
.transform { value ->
while (isOpened.get().not()) { }
emit(value)
}
.collect {
println("${System.currentTimeMillis() - startTime}: $it")
}
}
Thread.sleep(1000)
flow.tryEmit("First")
Thread.sleep(1000)
isOpened.set(true)
flow.tryEmit("Second")
isOpened.set(false)
Thread.sleep(1000)
isOpened.set(true)
flow.tryEmit("Third")
Thread.sleep(2000)
}
Result:
So you can set isOpened to false when your activity lifecycle paused and to true when resumed.
You can use lifecycleScope.launchWhenStarted
https://developer.android.com/kotlin/flow/stateflow-and-sharedflow#stateflow

updating label with progress of http post not working. IllegalStateException

I am having trouble with binding a UI component to an observable that gets updated progress from a http post event. I get an IllegalStateException
As I understand it the issue is the bind update is not happening on the UI thread. The answers I have read say that I need to use runAsync and then specify a UI block to update the UI component, but I am at a loss for how to accomplish this.
// View class
private val controller: ZumController by inject()
item("_Upload") {
isMnemonicParsing = true
action {
controller.uploadToServer()
}
}
bottom = label() {
useMaxWidth = true
padding = Insets(5.0, 10.0, 5.0, 10.0)
this.bind(controller.progress)
}
// Controller class
var progress = SimpleStringProperty("Select images to upload")
fun uploadToServer() {
images.forEach{ p ->
Fuel.upload("http://127.0.0.1:8089")
.add {FileDataPart(File(p), name = "file")}
.progress { readBytes, totalBytes ->
progress.value = (readBytes.toFloat() / totalBytes.toFloat() * 100).toString()}
.response { _ -> }
}
}
How would I go about making sure the UI is updated during the application thread when I need progress before function call (uploadToServer()) returns? Sorry if this has already been answered, I still don't get exactly what is happening here.
I've solved my problem with the following changes. I pass the FXTask to function uploadToServer(). There I updateMessage() with the progress callback for the http POST request. I can't say its the best way but it works. feel free to update this answer with more clear and concise code
item("_Upload") {
isMnemonicParsing = true
action {
runAsync {
controller.uploadToServer(this)
} ui {
}
}
}
fun uploadToServer(task: FXTask<*>) {
images.forEach{ p ->
Fuel.upload("http://127.0.0.1:8089")
.add {FileDataPart(File(p), name = "file")}
.progress { readBytes, totalBytes ->
val perComplete = readBytes.toFloat() / totalBytes.toFloat() * 100
task.updateMessage("Uploading $p %.2f".format(perComplete).plus("%"))
}
.response { _ -> }
}
}
TornadoFX has a built in TaskStatus object which has properties for the progress of the task. You can bind one or more of the properties in the TaskStatus object to any UI element, and simply call updateProgress from within your controller function. You don't even need to pass in the TaskStatus object, as the default instance will be used if you don't.
There are a few test appa within the framework that does this:
https://github.com/edvin/tornadofx/blob/master/src/test/kotlin/tornadofx/testapps/AsyncProgressApp.kt
https://github.com/edvin/tornadofx/blob/master/src/test/kotlin/tornadofx/testapps/TaskStatusTest.kt
That said, a quick and dirty solution for updating the UI from any other thread is simply wrapping the UI manipulation code inside runLater {}. This will work equally well for just updating a label for example.