I'm starting to try Jetpack Compose and was wondering why a simple list was so poorly optimised when I scrolled. Even in release build.
So I started adding logs and I realized that sometimes the list seemed to be recreating itself.
Here is my composable :
#OptIn(ExperimentalMaterialApi::class)
#Composable
fun MissionsList() {
val list = remember { (0..75).map { it.toString() } }
LazyColumn(
verticalArrangement = Arrangement.spacedBy(22.5.dp)
) {
Log.e("LazyColumn", "CREATE")
items(
count = list.size,
key = { list[it] }
) {
Card(
modifier = Modifier
.fillMaxWidth()
.height(100.dp),
elevation = 0.dp,
shape = RoundedCornerShape(20.dp),
onClick = { /*TODO*/ }
) {
Log.e("card", "${list[it]}")
Text(
text = list[it],
modifier = Modifier.padding(15.dp)
)
}
}
}
}
Here is output :
15:20:30.738 LazyColumn E CREATE
15:20:30.810 card E 0
15:20:30.828 card E 1
15:20:30.841 card E 2
15:20:30.866 card E 3
15:20:30.879 card E 4
15:20:31.250 Chip E 1
15:20:36.404 card E 6
15:20:36.496 card E 7
15:20:36.560 card E 8
15:20:36.645 card E 9
15:20:36.730 card E 10
15:20:36.840 card E 11
15:20:37.043 card E 12
15:20:38.324 card E 13
15:20:38.381 card E 14
15:20:38.417 card E 15
15:20:38.449 card E 16
15:20:38.478 card E 17
15:20:38.504 card E 18
15:20:38.529 card E 19
15:20:38.560 card E 20
15:20:38.570 card E 21
15:20:38.579 card E 22
15:20:38.610 card E 23
15:20:38.653 card E 24
15:20:38.685 card E 25
15:20:38.694 card E 26
15:20:38.717 card E 27
15:20:38.761 card E 28
15:20:38.782 card E 29
15:20:38.793 card E 30
15:20:38.820 card E 31
15:20:38.859 card E 32
15:20:38.892 card E 33
15:20:38.921 card E 34
15:20:38.972 card E 35
15:20:39.006 card E 36
15:20:39.020 LazyColumn E CREATE
15:20:39.027 card E 37
15:20:39.045 card E 30
15:20:39.047 card E 31
15:20:39.048 card E 34
15:20:39.066 card E 35
15:20:39.069 card E 32
15:20:39.070 card E 33
15:20:39.072 card E 36
15:20:39.131 card E 38
15:20:39.169 card E 39
15:20:39.218 card E 40
15:20:39.281 card E 41
15:20:39.354 card E 42
...
I noticed that about every 30 items the list is recreated.
What did I miss?
The LazyColumn uses a sliding window to track the key for each index, which can cause the lambda that contains your log statement to be recalculated. That doesn't mean that all the work Compose/LazyColumn did to generate your UI is thrown out though, so this shouldn't cause any significant performance impact.
You mentioned seeing performance issues in a release build. If that's optimized with R8 and you're using baseline profiles, scrolling a list like this should be plenty performant.
Related
I am trying to make the tutorials from a Jetpack Compose book but the app crashes when I start it:
This is the error code
2022-09-15 22:03:05.983 14360-14360/com.raywenderlich.android.jetreddit E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.raywenderlich.android.jetreddit, PID: 14360
java.lang.IllegalArgumentException: Only VectorDrawables and rasterized asset types are supported ex. PNG, JPG
at androidx.compose.ui.res.PainterResources_androidKt.loadImageBitmapResource(PainterResources.android.kt:99)
at androidx.compose.ui.res.PainterResources_androidKt.painterResource(PainterResources.android.kt:71)
at com.raywenderlich.android.jetreddit.components.PostKt.ImageContent(Post.kt:196)
at com.raywenderlich.android.jetreddit.components.PostKt$ImagePost$1.invoke(Post.kt:74)
at com.raywenderlich.android.jetreddit.components.PostKt$ImagePost$1.invoke(Post.kt:73)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
That's the position from the error code:
#Composable
fun ImageContent(image: Int) {
val imageAsset = ImageBitmap.imageResource(id = image)
Image(
bitmap = imageAsset,
contentDescription = stringResource(id = R.string.post_header_description),
modifier = Modifier
.fillMaxWidth()
.aspectRatio(imageAsset.width.toFloat() / imageAsset.height),
contentScale = ContentScale.Crop
)
}
#Composable
fun ImagePost(post: PostModel) {
Post(post) {
ImageContent(post.image ?: R.drawable.compose_course)
}
}
From the exception, It seems you are trying to load an image that is not supported by ImageBitmap.imageResource.
java.lang.IllegalArgumentException: Only VectorDrawables and rasterized asset types are supported ex. PNG, JPG.
Check that if the type of post.image or R.drawable.compose_course is supported by the API you are using.
I made an android app (Android Studio + Kotlin) that uses CameraX. So this is my code to start the camera:
private fun startCamera() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener(Runnable {
// Used to bind the lifecycle of cameras to the lifecycle owner
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
// Preview
val preview = Preview.Builder()
.build()
.also {
it.setSurfaceProvider(viewFinder.surfaceProvider)
}
// Select back camera as a default
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
try {
// Unbind use cases before rebinding
cameraProvider.unbindAll()
// Bind use cases to camera
cameraProvider.bindToLifecycle(
this, cameraSelector, preview
)
} catch (exc: Exception) {
Log.e(TAG, "Use case binding failed", exc)
}
}, ContextCompat.getMainExecutor(this))
}
Now I wanted to get a bitmap of the viewFinder so that I can determine the color of the pixel in the center:
val bitmap = viewFinder.bitmap
val pixel = bitmap?.getPixel(width / 2, height / 2 - 30)
val pixel2 = if (pixel != null) pixel else -1
bitmap is Bitmap? and pixel is Int?
When I run the app everything works completely fine but then sometimes it crashes and shoots out this message:
at com.example.colorblind.MainActivity$onCreate$r$1.run(MainActivity.kt:116)
Line 116 is the line with val pixel = bitmap?.getPixel(width / 2, height / 2 - 30). If I then add a line before the code so that it hops to line 117 everything works perfectly fine again until the same error occurs. Then I need to change the line again. How can I fix this problem because I can't publish the app like this if it sometimes randomly crashes while opening.
I hope I was able to explain the problem properly.
EDIT
This is the only message I get when it crashes:
2022-02-16 20:43:53.123 7337-7337/com.example.colorblind E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.colorblind, PID: 7337
java.lang.IllegalArgumentException: y must be < bitmap.height()
at android.graphics.Bitmap.checkPixelAccess(Bitmap.java:1958)
at android.graphics.Bitmap.getPixel(Bitmap.java:1863)
at com.example.colorblind.MainActivity$onCreate$r$1.run(MainActivity.kt:116)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
I'm trying to overlay two bitmaps.
This code works fine on below android 9. On android 9 and above the application crash.
private fun overlay(base: Bitmap, blend: Bitmap): Bitmap {
return try {
val result: Bitmap =
base.copy(Bitmap.Config.ARGB_8888, true)
val p = Paint()
p.xfermode = PorterDuffXfermode(PorterDuff.Mode.ADD)
p.shader = BitmapShader(blend, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
val c = Canvas()
c.setBitmap(result)
c.drawBitmap(base, 0f, 0f, null)
c.drawRect(0f, 0f, base.width.toFloat(), base.height.toFloat(), p)
result
} catch (e: Exception) {
println(e.message.toString())
base
}
}
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.ex.image, PID: 26562
java.lang.IllegalArgumentException: Software rendering doesn't support hardware bitmaps
at android.graphics.BaseCanvas.onHwBitmapInSwMode(BaseCanvas.java:632)
at android.graphics.BaseCanvas.throwIfHwBitmapInSwMode(BaseCanvas.java:639)
at android.graphics.BaseCanvas.throwIfCannotDraw(BaseCanvas.java:73)
at android.graphics.MiuiCanvas.throwIfCannotDraw(MiuiCanvas.java:329)
at android.graphics.BaseCanvas.drawBitmap(BaseCanvas.java:113)
at android.graphics.MiuiCanvas.drawBitmap(MiuiCanvas.java:98)
at android.graphics.Canvas.drawBitmap(Canvas.java:1613)
at com.ex.image.fragments.FiltersFragment.overlay(FiltersFragment.kt:103)
at com.ex.image.fragments.FiltersFragment.applyFilter(FiltersFragment.kt:86)
at com.ex.image.adapters.FilterRecyclerAdapter.onBindViewHolder$lambda-0(FilterRecyclerAdapter.kt:39)
at com.ex.image.adapters.FilterRecyclerAdapter.$r8$lambda$KexXVg24JaLUI6vIkMTUBOYKnlY(Unknown
Source:0)
at com.ex.image.adapters.FilterRecyclerAdapter$$ExternalSyntheticLambda0.onClick(Unknown
Source:4)
at android.view.View.performClick(View.java:7509)
at android.view.View.performClickInternal(View.java:7486)
at android.view.View.access$3600(View.java:841)
at android.view.View$PerformClick.run(View.java:28710)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:236)
at android.app.ActivityThread.main(ActivityThread.java:8056)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:656)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:967)
Note:
I have already turn off the HARDWARE_ACCELERATED for android 9 above.
My app is a single activity app with different screens represented by composables. I am using navigation component to handle screen navigation.
My navigation component has AnimatedNavHost as:
AnimatedNavHost(
navController = navController,
startDestination = Constants.SPLASH,
enterTransition = { initial, _ ->
slideInHorizontally(initialOffsetX = { 1000 })
},
exitTransition = { _, target ->
slideOutHorizontally(targetOffsetX = { -1000 })
},
popEnterTransition = { initial, _ ->
slideInHorizontally(initialOffsetX = { -1000 })
},
popExitTransition = { _, target ->
slideOutHorizontally(targetOffsetX = { 1000 })
}
) {
composable(Constants.SPLASH) {
Splash(navController = navController)
}
composable(Constants.HIW) {
LogoPage(
false,
true,
navController = navController
) { HowItWorks(navController = navController, false) }
.
.
.
//Other 26 'composable' elements
}
When I add even 1 more 'composable' element, my app crashes, and the logcat says:
java.lang.ArrayIndexOutOfBoundsException: length=29; index=29
at androidx.collection.SparseArrayCompat.valueAt(SparseArrayCompat.java:379)
at androidx.navigation.NavController.setGraph(NavController.kt:948)
at androidx.navigation.NavController.setGraph(NavController.kt:95)
at com.google.accompanist.navigation.animation.AnimatedNavHostKt.AnimatedNavHost(AnimatedNavHost.kt:146)
at com.google.accompanist.navigation.animation.AnimatedNavHostKt.AnimatedNavHost(AnimatedNavHost.kt:85)
How do I solve this?
Been trying a lot of things, but not able to solve.
Also, there doesn't seem to be any online information regarding this.
Please help!
It is reported in this issue.
RelNote: "There will no longer be an ArrayIndexOutOfBoundsException
when calling setGraph with a graph with 13 or 29 destinations."
It should be fixed with Navigation 2.4.0-alpha08
So I've been working on this all day and no matter what, I keep getting a null pointer to my CheckBox. I'm attempting to create an AlertDialog that has a list of filters to be saved using SharedPreferences. The Dialog will contain both CheckBoxes as well as a couple Number Pickers. I've searched Stack Overflow for answers for many hours and I have found a few Java solutions of using an OnClickListener within the .setPositiveButton of the dialog. These solutions don't seem to work in Kotlin though.
fab_filter_match.setOnClickListener({
val matchSettingsDialog = AlertDialog.Builder(context)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
matchSettingsDialog.setView(R.layout.dialog_match_settings)
}
var matchGender = 0
var minAge = 0
var maxAge = 0
var matchLanguage = arrayListOf<String>()
matchSettingsDialog.setTitle("Match Settings")
.setPositiveButton("Ok") { _, _ ->
var matchGender = 0
var minAge = 0
var maxAge = 0
var matchLanguage = arrayListOf<String>()
checkbox_male_match_settings.setOnClickListener({
isChecked -> matchGender += 1
})
checkbox_female_match_settings.setOnClickListener({
isChecked -> matchGender += 2
})
checkbox_other_match_settings.setOnClickListener({
isChecked -> matchGender += 4
})
// Get Gender Selection
if (checkbox_male_match_settings!!.isChecked) {
matchGender += 1
} else
matchGender += 0
if (checkbox_female_match_settings.isChecked) {
matchGender += 2
}
if (checkbox_other_match_settings.isChecked) {
matchGender += 4
}
if (!checkbox_male_match_settings.isChecked &&
!checkbox_female_match_settings.isChecked &&
!checkbox_other_match_settings.isChecked) {
matchGender = 7
}
// Get Age Selection
text_select_minAge_filter.setOnClickListener({
createNumberPicker(0)
})
text_select_maxAge_filter.setOnClickListener({
createNumberPicker(1)
})
minAge = text_select_minAge_filter.text.toString().toInt()
maxAge = text_select_maxAge_filter.text.toString().toInt()
// Get Language Selection
if (checkbox_english_match_settings.isChecked)
matchLanguage.add("English")
if (checkbox_swedish_match_settings.isChecked)
matchLanguage.add("Swedish")
editor.putInt("matchGender", matchGender)
editor.putInt("minAge", minAge)
editor.putInt("maxAge", maxAge)
editor.putStringSet("languages", matchLanguage as Set<String>)
editor.apply()
Toast.makeText(context, "Settings Changed", Toast.LENGTH_SHORT).show()
}
.setNegativeButton("Cancel") { _, _ ->
Toast.makeText(context, "Cancelled", Toast.LENGTH_SHORT).show()
}
.create()
.show()
})
}
This is what I currently have but I keep getting the error
Process: com.p.fiveminutefriend, PID: 12352
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.CheckBox.setOnClickListener(android.view.View$OnClickListener)' on a null object reference
at com.p.fiveminutefriend.MainTabs.MatchFragment$onViewCreated$3$1.onClick(MatchFragment.kt:128)
at com.android.internal.app.AlertController$ButtonHandler.handleMessage(AlertController.java:177)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6938)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374)
I have been testing different solutions all day but I am at my wit's end. If anyone has any advice on this, it would be much appreciated.