NullPointerException at androidx.compose.ui.platform.RenderNodeLayer.updateDisplayList - android-jetpack-compose

I have a Drawer on my MainScreen which navigates me through drawer screens, but for some reason when I try to navigate to two specific screens (this only happens at the first time when I open the app), my app crashes with this error:
FATAL EXCEPTION: main
Process: com.tags.taglife, PID: 21478
java.lang.NullPointerException at androidx.compose.ui.platform.RenderNodeLayer.updateDisplayList(RenderNodeLayer.android.kt:245) at androidx.compose.ui.platform.AndroidComposeView.dispatchDraw(AndroidComposeView.android.kt:760) at android.view.View.draw(View.java:24398) at android.view.View.updateDisplayListIfDirty(View.java:23256) at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4732) at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4704) at android.view.View.updateDisplayListIfDirty(View.java:23203) at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4732) at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4704) at android.view.View.updateDisplayListIfDirty(View.java:23203)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4732)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4704)
at android.view.View.updateDisplayListIfDirty(View.java:23203)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4732)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4704)
at android.view.View.updateDisplayListIfDirty(View.java:23203)
at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:753)
at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:759)
at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:857)
at android.view.ViewRootImpl.draw(ViewRootImpl.java:5501)
at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:5194)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:4356)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2991)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:10665)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1301)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1309)
at android.view.Choreographer.doCallbacks(Choreographer.java:923)
at android.view.Choreographer.doFrame(Choreographer.java:852)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1283)
at android.os.Handler.handleCallback(Handler.java:942)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:226)
at android.os.Looper.loop(Looper.java:313)
at android.app.ActivityThread.main(ActivityThread.java:8741)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:571)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1067)
This is the code for one of those screens:
package com.tags.taglife.presentation.MainScreen.sidebar
import android.annotation.SuppressLint
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.InlineTextContent
import androidx.compose.foundation.text.appendInlineContent
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.Placeholder
import androidx.compose.ui.text.PlaceholderVerticalAlign
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import com.tags.taglife.presentation.MainScreen.MainViewModel
import com.tags.taglife.presentation.MainScreen.NavScreens
import com.tags.taglife.presentation.ProgressScreen
import com.tags.taglife.presentation.tutorial.ItemModel
import com.tags.taglife.presentation.tutorial.TutorialViewModel
import com.tags.taglife.ui.theme.bottomLine
import com.tags.taglife.ui.theme.white
import taglife.R
#SuppressLint("CoroutineCreationDuringComposition")
#Composable
fun Tutorial(
modifier: Modifier = Modifier,
viewModel: MainViewModel,
tutorialViewModel: TutorialViewModel = hiltViewModel()
) {
viewModel.setCurrentScreen(NavScreens.DrawerScreens.Tutorial)
val isDark = tutorialViewModel.isDarkTheme
val loading = tutorialViewModel.isLoaded
if (!loading.value)
ProgressScreen()
else {
Column(
modifier = modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState())
.padding(20.dp),
) {
//INSTRUCTIONS FOR USE
Title(R.string.instructions_for_use)
tutorialViewModel.instructions.mapIndexed { index, item ->
Item(isDark = isDark.value, item = item, index = index + 1)
}
Spacer(modifier = Modifier.height(15.dp))
//FUNCTIONS
Title(text = R.string.functions_of_homepage_buttons)
tutorialViewModel.functions.mapIndexed { index, item ->
Item(isDark = isDark.value, item = item, index = index + 1)
}
Spacer(modifier = Modifier.height(15.dp))
//QR CODE
Title(text = R.string.qr_code_functions)
tutorialViewModel.qrCodeFunctions.mapIndexed { index, item ->
Item(isDark = isDark.value, item = item, index = index + 1)
}
Spacer(modifier = Modifier.height(15.dp))
//NON NFC SCAN TAG
Title(text = R.string.non_nfc_scan_tag)
tutorialViewModel.nonNFCScanTag.mapIndexed { index, item ->
Item(isDark = isDark.value, item = item, index = index + 1)
}
}
}
}
#Composable
fun Title(text: Int) {
Text(
text = stringResource(id = text),
fontWeight = FontWeight.Bold,
fontSize = 20.sp,
color = MaterialTheme.colors.primary,
)
}
#Composable
fun Item(
isDark : Boolean,
item: List<ItemModel>,
index: Int
) {
val myId = index.toString()
var image: Int = R.drawable.vector
var mainText = buildAnnotatedString {
append("$index. ")
item.forEach {
if (it.text != null)
append(stringResource(id = it.text!!))
if (it.icon != null) {
image = it.icon!!
appendInlineContent(myId, "[icon]")
}
}
}
val inlineContent = mapOf(
Pair(
myId,
InlineTextContent(
Placeholder(
width = 30.sp,
height = 30.sp,
placeholderVerticalAlign = PlaceholderVerticalAlign.TextCenter
)
) {
Image(
painter = painterResource(image),
modifier = Modifier.padding(5.dp),
contentDescription = "",
colorFilter = if (isDark) ColorFilter.tint(white) else null
)
}
)
)
Column(
modifier = Modifier
.fillMaxWidth(), verticalArrangement = Arrangement.Center
) {
Text(
text = mainText,
inlineContent = inlineContent,
textAlign = TextAlign.Start,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 12.dp)
)
Spacer(
modifier = Modifier
.height(1.dp)
.fillMaxWidth()
.background(bottomLine)
)
}
}
I tried simply to navigate to those screens via drawer.

I figured it out.
I needed to update my whole project to a new jetpack compose version, and compileSdk to 33.

Related

jetpack compose lazycolumn items not shows deleted only after scroll

Could you pelase help me?
I want to use swipe to delete and tried a lot of things, but nothing works good. The best way is:
items(listactivity.size) { index ->
val activityItem = listactivity.getOrNull(index)
if (activityItem != null) {
key(activityItem) {
val dismissState = rememberDismissState()
LaunchedEffect(dismissState.currentValue) {
if (dismissState.currentValue == DismissValue.DismissedToStart) {
dashboardViewModel.activityForDelete = activityItem
println("ACT ITEM " + activityItem.activityType?.activityTypeName)
visibleDeleteDialog.value = true
dismissState.snapTo(DismissValue.Default)
}
}
if (visibleDeleteDialog.value) {
BaseAlertDialog(
onExit = {
visibleDeleteDialog.value = false
},
onSuccess = {
removeActivity()
visibleDeleteDialog.value = false
},
disclaimerFirst = R.string.confirm_delete_activity,
disclaimerSecond = R.string.confirm_delete_activity_text,
successName = R.string.delete_session
)
}
SwipeToDismiss(
state = dismissState,
directions = setOf(DismissDirection.EndToStart),
background = {
val direction =
dismissState.dismissDirection ?: return#SwipeToDismiss
val alignment = when (direction) {
DismissDirection.StartToEnd -> Alignment.CenterStart
DismissDirection.EndToStart -> CenterEnd
}
Box(
modifier = Modifier
.fillMaxSize()
.background(Black)
.padding(horizontal = Spacing_12),
contentAlignment = alignment
) {
Text(
text = stringResource(id = R.string.delete),
color = Color.Red,
fontSize = Text_16,
fontWeight = FontWeight.Bold
)
}
},
dismissContent = {
GameCard(activityItem)
}
)
}
}
}
However th lisitem do not update items after delete immidiately (only after scroll i can see what item was deleted).
What am i doing wrong?
I tried SwipeToDismiss and other internet things.
I know google has a bug in this function items() some times,
but please try to replace your function with itemsIndexed and add another parameter
itemsIndexed(listactivity.size) { index , item ->
something like this
// import this at the top
import androidx.compose.foundation.lazy.itemsIndexed
//code
//*
//*
//*
itemsIndexed(listactivity.size) { index , item ->
val activityItem = listactivity.getOrNull(index)
if (activityItem != null) {
key(activityItem) {
val dismissState = rememberDismissState()
LaunchedEffect(dismissState.currentValue) {
if (dismissState.currentValue == DismissValue.DismissedToStart) {
dashboardViewModel.activityForDelete = activityItem
println("ACT ITEM " + activityItem.activityType?.activityTypeName)
visibleDeleteDialog.value = true
dismissState.snapTo(DismissValue.Default)
}
}
if (visibleDeleteDialog.value) {
BaseAlertDialog(
onExit = {
visibleDeleteDialog.value = false
},
onSuccess = {
removeActivity()
visibleDeleteDialog.value = false
},
disclaimerFirst = R.string.confirm_delete_activity,
disclaimerSecond = R.string.confirm_delete_activity_text,
successName = R.string.delete_session
)
}
SwipeToDismiss(
state = dismissState,
directions = setOf(DismissDirection.EndToStart),
background = {
val direction =
dismissState.dismissDirection ?: return#SwipeToDismiss
val alignment = when (direction) {
DismissDirection.StartToEnd -> Alignment.CenterStart
DismissDirection.EndToStart -> CenterEnd
}
Box(
modifier = Modifier
.fillMaxSize()
.background(Black)
.padding(horizontal = Spacing_12),
contentAlignment = alignment
) {
Text(
text = stringResource(id = R.string.delete),
color = Color.Red,
fontSize = Text_16,
fontWeight = FontWeight.Bold
)
}
},
dismissContent = {
GameCard(activityItem)
}
)
}
}
}
I don't have the time to go over all of your code to make more changes that maybe will make it a better fit for your code and needs. but I am sure you are able to do so!
you can see my code when I had a similar problem maybe it will help to solve your problem.
itemsIndexed(celebsSearchList) { index, item ->
if (celebsSearchList.isEmpty()) {
println("no celebs")
} else {
val celebie = item
// celebsSearchList.forEach { celebie ->
Card(
modifier = Modifier
.fillMaxWidth()
.padding(start = 20.dp, end = 20.dp, top = 10.dp, bottom = 10.dp),
elevation = 8.dp
) {
Row(
modifier = Modifier
.fillMaxWidth()
.clip(MaterialTheme.shapes.medium),
horizontalArrangement = Arrangement.SpaceAround,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = celebie.FirstName + " " + celebie.LastName,
style = MaterialTheme.typography.h6
)
Button(
onClick = {
CelebForCelebProfile = celebie
navController.navigate(Screen.CelebProfileScreen.route)
}, colors = ButtonDefaults.buttonColors(
backgroundColor = Color.White, contentColor = Color.Gray
)
) {
Text(text = "View Profile")
}
}
}
}
}```
if you like me please upvote me :) help me gain more points on stack over flow and comment if you have any questions.

LazyColumn list not showing in jetpack compose

I m new To Jetpack Compose , i'm trying to create a Proxy list app , used lazy column to list proxies but its now showing .. app runs successfully but result not getting , Give Me a Solution . Thx
Lib : https://github.com/gumify/hiper
#Preview
#Composable
fun App(){
val hiper = Hiper.getInstance().async() // for asynchronous requests
val Proxies = remember {mutableStateListOf<String>() }
Surface(modifier = Modifier
.fillMaxHeight()
.fillMaxWidth()) {
Column(modifier = Modifier
.background(Color.Blue)
.height(20.dp),
horizontalAlignment = Alignment.CenterHorizontally) {
Button (
modifier = Modifier.padding(all = Dp(10F)),
onClick = {
hiper.get("http://spys.me/proxy.txt") { response ->
if (response.isSuccessful) {
val lines = response.text.toString().split("\n")
for (line in lines) {
Proxies.add(line)
}
}
}
}) { Text(text = "Get Proxies") }
LazyColumn( modifier = Modifier.background( color = Color.Gray)){
items(Proxies){item ->
Text(text = item)
}
}
}
}
}
}

Strange behaviour with horizontalArrangement using JetPack Compose

I don't understand why my switches are not aligned, why it is expanding outside the screenview ? of course I didn't succeed so far to find a proper solution to align my switches with jetpack compose.
the kotlin code :
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Switch
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
class ToolsActivityK2 : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
ToolContent_()
}
}
}
}
#Preview
#Composable
fun Preview_ToolContent_() {
ToolContent_()
}
#Composable
fun SettingsSwitch_(
subtitle: String
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp, end = 16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
subtitle,
Modifier.padding(end = 16.dp)
)
Switch(checked = false, onCheckedChange = null)
}
}
#Composable
fun ToolContent_() {
Column {
SettingsSwitch_("launch something 1")
SettingsSwitch_("launch launch launch launch again and again and again something 2")
SettingsSwitch_("launch something 3")
}
}
if I replace the switches by images, it's worse the image for line 2 does not show on screen ! I for sure miss something but I cannot figure what
Configuration infos:
Android Studio Bumblebee | 2021.1.1
classpath 'com.android.tools.build:gradle:7.1.0'
ext.kotlin_version = '1.6.10'
kotlinCompilerExtensionVersion '1.1.0-rc02'
androidx.compose.ui:ui:1.0.5
Use weight to achieve something like this,
Complete code
#Composable
fun SettingsSwitch_(
subtitle: String
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp, end = 16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
subtitle,
Modifier.padding(end = 16.dp).weight(1f)
)
Switch(checked = false, onCheckedChange = null)
}
}
Reason
I have applied background color to all the components in your code to show how they are placed.
#Composable
fun SettingsSwitch_(
subtitle: String
) {
Row(
modifier = Modifier
.fillMaxWidth()
.background(Red)
.padding(start = 16.dp, end = 16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
subtitle,
Modifier.background(Cyan).padding(end = 16.dp)// .weight(1f)
)
Switch(checked = false, onCheckedChange = null, modifier = Modifier.background(Green))
}
}
And this code gives the following layout.
So, you can see that the Text has occupied the full space available in the Row. Hence the Switch appears in the wrong position.
More info
If we look into the docs for Arrangement.SpaceBetween we can see,
Place children such that they are spaced evenly across the main axis,
without free space before the first child or after the last child.
Visually: 1##2##3 for LTR or 3##2##1 for RTL.
All the Arrangement options only affect the extra free space between the children. Since the is no extra free space in the second Row the Arrangement does not have any effect there.

Jetpack Compose Lazy Column is stretching items when end of list is reached

Why my LazyColumn items are being stretched?
class RecipeListFragment: Fragment() {
private val viewModel: RecipeListViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply {
setContent {
val recipes = viewModel.recipes.value
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "RecipeList",
style = TextStyle(
fontSize = 21.sp
)
)
Spacer(modifier = Modifier.padding(10.dp))
Button(
onClick = {
findNavController().navigate(R.id.viewRecipe)
}
) {
Text(text = "TO RECIPE FRAGMENT")
}
Spacer(modifier = Modifier.padding(10.dp))
LazyColumn{
items(recipes) { recipe ->
// RecipeCard(recipe = recipe, onClick = {})
Text(
text = recipe.title ?: "None",
style = MaterialTheme.typography.h5
)
}
}
}
}
}
}
}
android 12 preview versions prior to ~august build have this problem.
More info can be found here: https://issuetracker.google.com/issues/208694349
Try on the stable android12 and/or on the real device

Android Jetpack Compose Row's height (IntrinsicSize.Min) is not stretched when children column generate more composables

The following is my code snippet. I pass in editClick that adds a data class object into chargingViewModel.contractSelfPay, which is observed as itemList state. When I click the icon, I can tell itemList state receives update by having more edit icons that are spaced evenly. However, BasicGrid Row's height is not stretched with Intrinsic.Min.
If I remove IntrinsicSize.Min, even though row's height is stretched, dividers no longer can fillMaxHeight as well as icon columns. without Intrinsic.Min
#Composable
fun ContractSelfPay(chargingViewModel: ChargingViewModel, editClick: () -> Unit = {}) {
val itemList by chargingViewModel.contractSelfPay.observeAsState()
val composeList: List<#Composable () -> Unit> = itemList?.map {
#Composable {
Row {
TempFunc { StyledText(text = it.itemTitle) }
TempFunc { StyledText(text = it.originalPrice.toString()) }
TempFunc { StyledText(text = it.selfPay.toString(), color = self_pay_blue) }
TempFunc { StyledText(text = it.count.toString()) }
TempFunc { StyledText(text = (it.selfPay * it.count).toString()) }
}
}
} ?: listOf()
val total = itemList?.map { (it.selfPay.toInt() * it.count.toInt()) }?.sum() ?: 0
BasicGrid("全自費", composeList, total = total.toString(), editClick = editClick)
}
#Composable
fun BasicGrid(
gridTitle: String,
itemList: List<#Composable () -> Unit>,
total: String = "0",
editClick: () -> Unit = {}
) {
Row(modifier = Modifier.height(IntrinsicSize.Min), verticalAlignment = Alignment.CenterVertically) {
StyledTextBold(text = gridTitle, modifier = Modifier.weight(15f).wrapContentWidth())
VerticalDivider()
Column(
modifier = Modifier.weight(60f)
) {
itemList.forEachIndexed { index, compose ->
compose()
if (index != itemList.size - 1)
HorizontalDivider()
}
if (itemList.isEmpty())
StyledText(text = "尚未有任何紀錄", modifier = Modifier.weight(1f).wrapContentSize())
}
VerticalDivider()
StyledTextBold(text = total, modifier = Modifier.weight(15f).wrapContentWidth())
VerticalDivider()
Column(
modifier = Modifier
.weight(10f)
.fillMaxHeight(),
verticalArrangement = Arrangement.SpaceEvenly
) {
itemList.forEachIndexed { index, detail ->
Image(
painter = painterResource(R.drawable.icon_mode_edit),
contentDescription = "",
modifier = Modifier
.align(Alignment.CenterHorizontally)
.clickable { editClick() },
)
if (itemList.isNotEmpty() && index != itemList.size - 1)
HorizontalDivider()
}
}
}
}
I have created issue here https://issuetracker.google.com/issues/217910352. Hopefully it gets solved.
One of the work-arounds I could think of is keeping track of height and removing IntrinsicSize.Min.
As in:
// _key_ is something that causes change of the height of the row
var height by remember(_key_) { mutableStateOf(0) }
Row(Modifier.onSizeChanged { height = it.height }) {
VerticalDivider(Modifier.height(height))
}
In your case I suppose key would be size of itemList.
Thank you Majkeee. It's been a while. The way I fixed it at the time was with custom layout modifier. Not sure if it still works today though.
fun Modifier.expandHeight() = this.then(layout { measurable, constraints ->
val placeable =
measurable.measure(constraints.copy(maxHeight = Constraints.Infinity))
layout(placeable.width, placeable.height) {
placeable.placeRelative(0, 0)
}
})
and to use it you can do
Column(modifier = Modifier.expandHeight())

Resources