Sonu Sourav
12/04/2024, 1:16 PMfun example() {
intent {
fun1()
}
Log.d("sonusourav","intent 1")
intent {
fun2()
}
Log.d("sonusourav","intent 2")
intent {
fun3()
}
Log.d("sonusourav","intent 3")
}
Will the three functions be launched in parallel like launch? Sorry if this is very naive question, couldn't found a clear answer for this written anywhereSonu Sourav
12/05/2024, 10:10 AMMikolaj Leszczynski
12/05/2024, 12:32 PMMikolaj Leszczynski
12/05/2024, 12:33 PMintent
Sonu Sourav
12/05/2024, 2:55 PM* override val container = scope.container<TestState, String>(initialState) {
* coroutineScope {
* launch {
* sendSideEffect1()
* }
* launch {
* sendSideEffect2()
* }
* }
* }
*
* @OptIn(OrbitExperimental::class)
* private suspend fun sendSideEffect1() = subIntent {
* flow1.collect {
* postSideEffect(it)
* }
* }
*
* @OptIn(OrbitExperimental::class)
* private suspend fun sendSideEffect1() = subIntent {
* flow2.collect {
* postSideEffect(it)
* }
* }
Vishnu Ravi
12/06/2024, 8:43 PMintent {...}
behaves similarly to viewModelScope.launch {...}
, essentially working as a fire-and-forget operation. Be careful when nesting these commands, whether in parallel or sequentially, and switching the coroutine context
within the child functions, you may run into issues with execution order. In particular, the first intent
to finish will be the one that calls reduce
first.
Think of subIntent
as being similar to regular suspend functions, but with access to the reduce
block and the state
property. It typically doesn't launch a new coroutine; instead, it uses the same scope. This allows us to break down large intents into smaller, more manageable methods.
---
It's easier to figure out the behavior than to explain it. But anyway, I'll give it a shot 😛
fun doSomething() {
// 1 Non-blocking and launches a new coroutine
intent {
// 2 Non-blocking and launches a new coroutine
// This is a nested example, but the behavior is quite similar for sequential usage as well.
intent {
// Some large block that should be extracted out for maintenance/readability sake,
// but we can't do since we don't have access to parent state and reduce block.
reduce { ... }
}
// 3 Use the same scope but may not wait for #2
// Some large block of code that can be extracted into a different private method
subIntent {
reduce { ... } // access to parent state within the same scope
}
// 4 Non-blocking and launches a new coroutine
// This will be executed after #3, but will not wait for #2
intent {
someInfiniteFlow.collect { ... }
}
// 5 Equivalent of #4
subIntent {
launch {
someInfiniteFlow.collect { ... }
}
}
// 6 Non terminating block
subIntent {
// This will be blocking here forever till the parent scope is cancelled
someInfiniteFlow.collect { ... }
}
// 7 Parallel decomposition (Usual setup)
coroutineScope {
launch {
// Some large block that should be extracted out for maintenance/readability sake,
// but we can't do since we will access to parent state and reduce block.
reduce { ... }
}
launch {
// Some large block that should be extracted out for maintenance/readability sake,
// but we can't do since we will access to parent state and reduce block.
reduce { ... }
}
}
// 8 Parallel decomposition with SubIntent
coroutineScope {
launch {
subIntent { ... }
}
launch {
subIntent { ... }
}
}
// 9 This will be executed after the `subIntents` that don't change the coroutine context or launch other coroutines, but it won't wait for those intents to complete.
reduce { ... }
// 10 At this point, the child intents relies on the `viewModelScope`. If you're testing this on a device/emulator and remain on the same screen, you'll be able to see the result of the child intent. However if you cancel the scope (or in a unit test environment), the child intent would be cancelled once the parent job is completed.
}
}
UPDATE: Note that large intents can still be refactored into smaller ones without using subIntent
, by creating private extensions on Orbit's SimpleSyntax
However, newcomers (myself included, as well as some of my teammates) often miss this and end up creating private intents without realizing that it will launch a new coroutine.
@Mikolaj Leszczynski Please feel free to correct me if I'm mistaken, as I'm also working through this myself.
I've added numbers to each section of the code block to make it easier for you to reference specific parts, in case you want to add or modify anything.