skydoves / Balloon

:balloon: Modernized and sophisticated tooltips, fully customizable with an arrow and animations for Android.
https://skydoves.github.io/libraries/balloon/html/balloon/com.skydoves.balloon/index.html
Apache License 2.0
3.68k stars 287 forks source link

Performance impact when using in LazyColumn #520

Open OlehHaidaienko opened 10 months ago

OlehHaidaienko commented 10 months ago

Please complete the following information:

Describe the Bug: When you're building a large screen and use LazyColumn with multiple Balloon you'll get performance impact - slight freeze during scrolling even on release build

Expected Behavior:

No performance reduction

Example

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApplicationTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    val builder = rememberBalloonBuilder {
                        setArrowSize(10)
                        setArrowPosition(0.5f)
                        setArrowPositionRules(ArrowPositionRules.ALIGN_ANCHOR)
                        setWidth(BalloonSizeSpec.WRAP)
                        setHeight(BalloonSizeSpec.WRAP)
                        setPadding(12)
                        setMarginHorizontal(12)
                        setCornerRadius(8f)
                        setBackgroundColor(Color(0xFF673AB7))
                        setBalloonAnimation(BalloonAnimation.ELASTIC)
                    }
                    LazyColumn(modifier = Modifier.fillMaxSize()) {
                        item(key = 1) { Item(builder = builder) }
                        item(key = 2) { Item(builder = builder) }
                        item(key = 3) { Section(color = Color.Blue, text = "Section #1") }
                        item(key = 4) { Section(color = Color.Magenta, text = "Section #2") }
                        item(key = 5) { Section(color = Color.Black, text = "Section #3") }
                        item(key = 6) { Section(color = Color.Gray, text = "Section #4") }
                    }
                }
            }
        }
    }

    @Composable
    private fun Section(color: Color, text: String) {
        Box(
            modifier = Modifier
                .fillMaxWidth()
                .height(400.dp)
                .background(color = color)
        ) {
            Text(
                modifier = Modifier.align(Alignment.Center),
                text = text,
                color = Color.White
            )
        }
    }

    @Composable
    private fun Item(builder: Balloon.Builder) {
        Row(modifier = Modifier.fillMaxWidth()) {
            Spacer(modifier = Modifier.width(16.dp))
            Tooltip(builder = builder)
            Spacer(modifier = Modifier.weight(1F))
            Tooltip(builder = builder)
            Spacer(modifier = Modifier.weight(1F))
            Tooltip(builder = builder)
            Spacer(modifier = Modifier.width(16.dp))
        }
    }

    @Composable
    private fun Tooltip(builder: Balloon.Builder) {
        Box {
            Balloon(
                modifier = Modifier.align(Alignment.Center),
                builder = builder,
                balloonContent = {
                    Text(text = "Tooltip text", color = Color.White)
                }
            ) { balloonWindow ->
                Button(
                    onClick = {
                        balloonWindow.showAlignBottom()
                    }
                ) {
                    Text(text = "Show")
                }
            }
        }
    }
}
skydoves commented 9 months ago

Hey @OlehHaidaienko, sorry for the late response. I'm wondering if this still happens with the latest version 1.6.3. Thanks!

OlehHaidaienko commented 9 months ago

@skydoves Hi, unfortunately still reproducible on 1.6.3. Please check the screen recordings. On the second screen recording, I just commented Balloon composable function Tooltip enabled.webm Tooltip disabled.webm

eduardb commented 7 months ago

Hi @skydoves, I've been also looking into some performance issues with Balloon tooltips in Compose, and one thing I noticed is that the Balloon composable takes a com.skydoves.balloon.Balloon.Builder argument which is considered unstable by the Compose compiler. See example report from the app I am working on:

restartable fun WithTooltip(
  stable showTooltipEffectKey: String
  unstable balloonBuilder: Builder
  stable balloonContent: Function2<Composer, Int, Unit>
  stable onTooltipShown: Function0<Unit>
  stable content: Function2<Composer, Int, Unit>
)

(generated using these instructions)

I am not sure how the builder is used internally, so I can't give any advice on how to solve this (e.g. if annotating with @Stable would help). Also I haven't done any profiling beyond this, so I can't tell for sure if this is the source of the performance issue, but I suspect that making the composable stable would help either way.

skydoves commented 7 months ago

Hey @eduardb, I don't think it's directly related to the stable marker, but I just released a snapshot version (1.6.5-SNAPSHOT), which annotates the Balloon.Builder as stable. You can import the snapshot version following this instruction: https://github.com/skydoves/Balloon?tab=readme-ov-file#snapshot

SammYWin commented 6 months ago

I confirm also have the same problem, and it's actually pretty bad. I have a LazyColumn in my app and each item has two balloons for tooltips and scrolling even in release build gets SOO laggy. Once I remove the balloons - everything gets back to perfectly smooth scrolling. I guess maybe thats because of creating of the builder for each item even though it's remembered...I wanna try and see if I can create the builder just once for the entire app and provide it in items through the LocalComposition... But yeah the problem is quiet severe and not obvious, It took me a few hours to realize it's because of the balloons

UPD: Moving the creation of builder outside of each lazy item didnt help (I moved it on the screen level composition and provided down the item composable - just for a quick test if this will even work) The snapshot version didn't fix the problem either

JeremyThivet commented 1 month ago

Hi,

I observed the same behavior in my application, updating to last version (1.6.5) didn't improve the performance issue. Have you eventually come up with a workaround @OlehHaidaienko ?

OlehHaidaienko commented 1 month ago

@JeremyThivet Hi, for this case, I created a simple tooltip implementation based on Popup composable function