```class FooViewModel( private val someUseCase...
# getting-started
u
Copy code
class FooViewModel(
    private val someUseCase: UseCase
) {
    fun fooClicked() {
        applicationScope.launch {
            someUseCase.doWhatever() <-----------
        }
    }
}
Does the
launch
lambda capture
this
? or is it smart enough to just capture the field
c
I think it just captures the field, but I'm not an expert.
u
I probably should see that in byte code right? if it access it via
this.someUseCase
l
Captures
this
.
u
(I'm looking whether this does leak view model instance if the `doWhatever* outlives the viewmodel
c
Are you sure?
I hope it doesn't leak it, or a lot of code is wrong. I don't remember seeing this as part of the common gotchas, so I doubt it's the case.
u
well if it captures this, then it does leak it temporarily, until the coroutine completes
l
If the property is mutable, it can read the actual value when the lambda runs, not captured value at the time.
c
Good point. It does probably leak
this
then.
I'll add that to my list of things to look out for during code review…
Maybe the compiler is smart enough to see that in this case it's not mutable, and thus it doesn't need to capture it? No idea how to test, though.
l
Hmm, don't know then. 🙁
u
So if it were swift, then it does this
Copy code
fun fooClicked() {
   applicationScope.launch { [this] in
      this.someUseCase.doWhatever()
   }
}
instead of
Copy code
fun fooClicked() {
    applicationScope.launch { [someUseCase] in
        someUseCase.doWhatever()
    }
}
?
Copy code
// access flags 0x11
  public final onActivateClick()V
   L0
    LINENUMBER 77 L0
    ALOAD 0
    GETFIELD cz/yav/scratchcards/presentation/activation/ActivationScreenViewModel.coroutineScope : Lkotlinx/coroutines/CoroutineScope;
    ACONST_NULL
    ACONST_NULL
    NEW cz/yav/scratchcards/presentation/activation/ActivationScreenViewModel$onActivateClick$1
    DUP
    ALOAD 0
    ACONST_NULL
    INVOKESPECIAL cz/yav/scratchcards/presentation/activation/ActivationScreenViewModel$onActivateClick$1.<init> (Lcz/yav/scratchcards/presentation/activation/ActivationScreenViewModel;Lkotlin/coroutines/Continuation;)V
    CHECKCAST kotlin/jvm/functions/Function2
    ICONST_3
    ACONST_NULL
    INVOKESTATIC kotlinx/coroutines/BuildersKt.launch$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/Job;
    POP
   L1
    LINENUMBER 80 L1
    RETURN
   L2
    LOCALVARIABLE this Lcz/yav/scratchcards/presentation/activation/ActivationScreenViewModel; L0 L2 0
    MAXSTACK = 7
    MAXLOCALS = 1
here is the bytecode
l
That lambda is created with
XxxViewModel
, so yes it captures
this
.
😥 1
u
INVOKESPECIAL cz/yav/scratchcards/presentation/activation/ActivationScreenViewModel$fooClick$1.<init> this?
ALOAD 0
means load this, right?
👌 1
Copy code
// access flags 0x11
  public final invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
  @Lorg/jetbrains/annotations/Nullable;() // invisible
    // annotable parameter count: 1 (visible)
    // annotable parameter count: 1 (invisible)
    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
    INVOKESTATIC kotlin/coroutines/intrinsics/IntrinsicsKt.getCOROUTINE_SUSPENDED ()Ljava/lang/Object;
   L0
    LINENUMBER 80 L0
    ASTORE 2
    ALOAD 0
    GETFIELD cz/yav/scratchcards/presentation/activation/ActivationScreenViewModel$fooClick$1.label : I
    TABLESWITCH
      0: L1
      1: L2
      default: L3
   L1
    ALOAD 1
    INVOKESTATIC kotlin/ResultKt.throwOnFailure (Ljava/lang/Object;)V
   L4
    LINENUMBER 81 L4
    ALOAD 0
    GETFIELD cz/yav/scratchcards/presentation/activation/ActivationScreenViewModel$fooClick$1.this$0 : Lcz/yav/scratchcards/presentation/activation/ActivationScreenViewModel;
    INVOKESTATIC cz/yav/scratchcards/presentation/activation/ActivationScreenViewModel.access$getFooManager$p (Lcz/yav/scratchcards/presentation/activation/ActivationScreenViewModel;)Lcz/yav/scratchcards/presentation/activation/FooManager;
    ALOAD 0
    ALOAD 0
    ICONST_1
    PUTFIELD cz/yav/scratchcards/presentation/activation/ActivationScreenViewModel$fooClick$1.label : I
    INVOKEVIRTUAL cz/yav/scratchcards/presentation/activation/FooManager.doFoo (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
   L5
    DUP
    ALOAD 2
    IF_ACMPNE L6
   L7
    LINENUMBER 80 L7
    ALOAD 2
    ARETURN
   L2
    ALOAD 1
    INVOKESTATIC kotlin/ResultKt.throwOnFailure (Ljava/lang/Object;)V
    ALOAD 1
   L6
    LINENUMBER 82 L6
    POP
   L8
    GETSTATIC kotlin/Unit.INSTANCE : Lkotlin/Unit;
    ARETURN
   L3
    LINENUMBER 80 L3
    NEW java/lang/IllegalStateException
    DUP
    LDC "call to 'resume' before 'invoke' with coroutine"
    INVOKESPECIAL java/lang/IllegalStateException.<init> (Ljava/lang/String;)V
    ATHROW
    LOCALVARIABLE this Lcz/yav/scratchcards/presentation/activation/ActivationScreenViewModel$fooClick$1; L4 L3 0
    LOCALVARIABLE $result Ljava/lang/Object; L4 L3 1
    MAXSTACK = 4
    MAXLOCALS = 3
if I stand on the `fooManager.doFoo* line
so yea, it does, confirmed, thank you!
l
If it's concerned, get the value in advance and let the lambda to capture the local variable.
u
well, I'd mostly design this away, i.e. have some sort of a class that owns the scope
Copy code
class FooSender(private val useCase) {
    private val scope = CoroutineScope()
    fun doFoo() {
        scope.launch {
            useCase.doWhatever()
        }
    }
}

class FooViewModel(
    private val fooSender: FooSender
) {
    fun fooClicked() {
        fooSender.doFoo()
    }
}
like so now, nothing leaks, when view model dies