How can i fix error: kotlin.UninitializedPropertyAccessException: lateinit property ticker has not been initialized
My Kotlin Code for this Activity / Button is here:
package io.ushowcasedev.classic_tetris.ui
import android.view.MotionEvent.*
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.indication
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.PressInteraction
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.input.pointer.pointerInteropFilter
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import io.ushowcasedev.classic_tetris.ui.theme.Purple200
import io.ushowcasedev.classic_tetris.ui.theme.Purple500
import kotlinx.coroutines.ObsoleteCoroutinesApi
import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.coroutines.channels.ticker
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun GameButton(
modifier: Modifier = Modifier,
size: Dp,
onClick: () -> Unit = {},
autoInvokeWhenPressed: Boolean = false,
content: @Composable (Modifier) -> Unit = {}
) {
val backgroundShape = RoundedCornerShape(size / 2)
lateinit var ticker: ReceiveChannel<Unit>
val coroutineScope = rememberCoroutineScope()
val pressedInteraction = remember { mutableStateOf<PressInteraction.Press?>(null) }
val interactionSource = MutableInteractionSource()
Box(
modifier = modifier
.shadow(5.dp, shape = backgroundShape)
.size(size = size)
.clip(backgroundShape)
.background(
brush = Brush.verticalGradient(
colors = listOf(
Purple200,
Purple500
),
startY = 0f,
endY = 80f
)
).indication(interactionSource = interactionSource, indication = rememberRipple())
.run {
if (autoInvokeWhenPressed) {
pointerInteropFilter {
when (it.action) {
ACTION_DOWN -> {
coroutineScope.launch {
// Remove any old interactions if we didn't fire stop / cancel properly
pressedInteraction.value?.let { oldValue ->
val interaction = PressInteraction.Cancel(oldValue)
interactionSource.emit(interaction)
pressedInteraction.value = null
}
val interaction = PressInteraction.Press(Offset(50f, 50f))
interactionSource.emit(interaction)
pressedInteraction.value = interaction
}
ticker = ticker(initialDelayMillis = 300, delayMillis = 60)
coroutineScope.launch {
ticker
.receiveAsFlow()
.collect { onClick() }
}
}
ACTION_CANCEL, ACTION_UP -> {
coroutineScope.launch {
pressedInteraction.value?.let {
val interaction = PressInteraction.Cancel(it)
interactionSource.emit(interaction)
pressedInteraction.value = null
}
}
ticker.cancel()
if (it.action == ACTION_UP) {
onClick()
}
}
else -> {
if (it.action != ACTION_MOVE) {
ticker.cancel()
}
return@pointerInteropFilter false
}
}
true
}
} else {
clickable { onClick() }
}
}
) {
content(Modifier.align(Alignment.Center))
}
}
Error:
2022-09-10 17:13:53.656 14169-14169/io.ushowcasedev.classic_tetris E/AndroidRuntime: FATAL EXCEPTION: main
Process: io.ushowcasedev.classic_tetris, PID: 14169
kotlin.UninitializedPropertyAccessException: lateinit property ticker has not been initialized
at io.ushowcasedev.classic_tetris.ui.GameButtonKt$GameButton$2$1.invoke(GameButton.kt:100)
at io.ushowcasedev.classic_tetris.ui.GameButtonKt$GameButton$2$1.invoke(GameButton.kt:69)
at androidx.compose.ui.input.pointer.PointerInteropFilter$pointerInputFilter$1$dispatchToView$3.invoke(PointerInteropFilter.android.kt:309)
at androidx.compose.ui.input.pointer.PointerInteropFilter$pointerInputFilter$1$dispatchToView$3.invoke(PointerInteropFilter.android.kt:294)
at androidx.compose.ui.input.pointer.PointerInteropUtils_androidKt.toMotionEventScope-ubNVwUQ(PointerInteropUtils.android.kt:81)
at androidx.compose.ui.input.pointer.PointerInteropUtils_androidKt.toMotionEventScope-d-4ec7I(PointerInteropUtils.android.kt:35)
at androidx.compose.ui.input.pointer.PointerInteropFilter$pointerInputFilter$1.dispatchToView(PointerInteropFilter.android.kt:294)
at androidx.compose.ui.input.pointer.PointerInteropFilter$pointerInputFilter$1.onPointerEvent-H0pRuoY(PointerInteropFilter.android.kt:229)
at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:292)
at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:297)
at androidx.compose.ui.input.pointer.NodeParent.dispatchMainEventPass(HitPathTracker.kt:179)
at androidx.compose.ui.input.pointer.HitPathTracker.dispatchChanges(HitPathTracker.kt:98)
at androidx.compose.ui.input.pointer.PointerInputEventProcessor.process-BIzXfog(PointerInputEventProcessor.kt:97)
at androidx.compose.ui.platform.AndroidComposeView.sendMotionEvent-8iAsVTc(AndroidComposeView.android.kt:1280)
at androidx.compose.ui.platform.AndroidComposeView.handleMotionEvent-8iAsVTc(AndroidComposeView.android.kt:1230)
at androidx.compose.ui.platform.AndroidComposeView.dispatchTouchEvent(AndroidComposeView.android.kt:1169)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3810)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:3492)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3810)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:3492)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3810)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:3492)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3810)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:3492)
at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:756)
at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1881)
at android.app.Activity.dispatchTouchEvent(Activity.java:3501)
at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:708)
at android.view.View.dispatchPointerEvent(View.java:13730)
at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:6243)
at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:6021)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:5470)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:5523)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:5489)
at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:5648)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:5497)
at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:5705)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:5470)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:5523)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:5489)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:5497)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:5470)
at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:8611)
2022-09-10 17:13:53.659 14169-14169/io.ushowcasedev.classic_tetris E/AndroidRuntime: at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:8476)
at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:8429)
at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:8726)
at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:198)
at android.os.MessageQueue.nativePollOnce(Native Method)
at android.os.MessageQueue.next(MessageQueue.java:326)
at android.os.Looper.loop(Looper.java:183)
at android.app.ActivityThread.main(ActivityThread.java:7211)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:494)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:975)
Without having debug logs, I cannot assure when its throwing exception. But as per the information provided, it is happening because, it does not comes to ACTION_DOWN
block at first. Here only you are first time initializing values.
When it comes to ACTION_CANCEL
, ACTION_UP
or else
block without coming to ACTION_DOWN
block in when condition, it will throwing this exception because you haven't initialized yet.
Several things you can try.
1)Here you can declare ticker as null and add null safe calls to ticker.
var ticker: ReceiveChannel<Unit>?=null //(1)
...
Box(
...
if (autoInvokeWhenPressed) {
pointerInteropFilter {
when (it.action) {
ACTION_DOWN -> {
...
ticker = ticker(initialDelayMillis = 300, delayMillis = 60)
coroutineScope.launch {
ticker?.receiveAsFlow()
?.collect { onClick() } //(2)
}
}
ACTION_CANCEL, ACTION_UP -> {
coroutineScope.launch {
pressedInteraction.value?.let {
val interaction = PressInteraction.Cancel(it)
interactionSource.emit(interaction)
pressedInteraction.value = null
}
}
ticker?.cancel() //(3)
if (it.action == ACTION_UP) {
onClick()
}
}
else -> {
if (it.action != ACTION_MOVE) {
ticker?.cancel() //(4)
}
return@pointerInteropFilter false
}
}
true
}
} else {
clickable { onClick() }
}
}
If you dont want to make ticker as nullable value(having lateinit variable), everytime you do isInitialized
checks while accessing it, something like below.
if(::ticker.isInitialized){
ticker.cancel()
}
Another thing you can try is to declare ticker using lazy initialization.So it will be initialized when it is accessed first time.
val ticker: ReceiveChannel<Unit> by lazy { ticker(initialDelayMillis = 300, delayMillis = 60) }
If you are sure its coming to ACTION_DOWN first, it might also be one reason that at first time composition it will come to ACTION_DOWN. But during recompose it might come to other when blocks. So while recomposition, the initialzed value in previous composition value might get lost. So you can add remember like any of the things below.
Nullable ticker:
var ticker: ReceiveChannel<Unit>? = remember{
mutableStateOf<ReceiveChannel<Unit>?>(null)
}
Non -Nullable ticker:
var ticker: ReceiveChannel<Unit> = remember{
mutableStateOf<ReceiveChannel<Unit>>(ticker(initialDelayMillis = 300, delayMillis = 60))
}
If none of these worked out, please add debug statements inside when block conditions and post the logs here.