https://kotlinlang.org logo
#getting-started
Title
# getting-started
u

ursus

03/13/2023, 4:47 PM
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

CLOVIS

03/13/2023, 4:49 PM
I think it just captures the field, but I'm not an expert.
u

ursus

03/13/2023, 4:50 PM
I probably should see that in byte code right? if it access it via
this.someUseCase
l

Loney Chou

03/13/2023, 4:51 PM
Captures
this
.
u

ursus

03/13/2023, 4:52 PM
(I'm looking whether this does leak view model instance if the `doWhatever* outlives the viewmodel
c

CLOVIS

03/13/2023, 4:52 PM
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

ursus

03/13/2023, 4:53 PM
well if it captures this, then it does leak it temporarily, until the coroutine completes
l

Loney Chou

03/13/2023, 4:53 PM
If the property is mutable, it can read the actual value when the lambda runs, not captured value at the time.
c

CLOVIS

03/13/2023, 4:54 PM
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

Loney Chou

03/13/2023, 4:56 PM
Hmm, don't know then. 🙁
u

ursus

03/13/2023, 4:57 PM
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

Loney Chou

03/13/2023, 5:06 PM
That lambda is created with
XxxViewModel
, so yes it captures
this
.
😥 1
u

ursus

03/13/2023, 5:07 PM
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

Loney Chou

03/13/2023, 5:11 PM
If it's concerned, get the value in advance and let the lambda to capture the local variable.
u

ursus

03/13/2023, 5:12 PM
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
4 Views