What happens if I execute the following lines insi...
# orbit-mvi
s
What happens if I execute the following lines inside viewmodel as below:
Copy code
fun 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 anywhere
@Mikolaj Leszczynski could you please help?
m
Short answer - yes
a new coroutine is launched each time you call
intent
thank you color 1
s
Have one doubt, if we could do it like above, what is the use of subIntent? Or which one should be preferred?
Copy code
* 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)
 *     }
 * }
v
From what I understood:
intent {...}
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 😛
Copy code
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.