Append to LazyList from a flow in Jetpack Compose

Viewed 364

I am trying to read a file line by line and each line is a new item in a LazyList. I have a flow that I would subscribe to, to listen for each new line

class LogcatService(private val coroutineScope: CoroutineScope) {
    private var _mutableLogcatListener: MutableSharedFlow<String> = MutableSharedFlow(0, 1, BufferOverflow.DROP_OLDEST)
    val logcatListener: SharedFlow<String> = _mutableLogcatListener

    private var _logcatJob: Job? = null

    fun startLogcat(){
        _logcatJob = coroutineScope.launch {
            Runtime.getRuntime().exec("logcat -c")
            Runtime.getRuntime().exec("logcat")
                .inputStream
                .bufferedReader()
                .useLines { lines ->
                    lines.forEach { newLine ->
                        _mutableLogcatListener.tryEmit(newLine)
                    }
                }
        }

    }

    fun stopLogcat(){
        _logcatJob?.cancel()
    }
}

Then in the activity I would listen to the flow to append each line that was read

@Composable
fun LogcatList(logcatService: LogcatService){
    val value = logcatService.logcatListener.collectAsState(initial = "")
    val content by remember { mutableStateOf(mutableListOf<String>())}
    content.add(value.value)
    LazyColumn {
        items(content) { message ->
            Text(text = message,
            )
        }
    }
}

It however I dont seem to get any flow updates in the composable.

I am pretty new to jetpack compose so I dont know what I am doing wrong. Using regular xml android I would just append the item to recyclerview and that would be that but I am trying to learn compose.

1 Answers

This line:

val content by remember { mutableStateOf(mutableListOf<String>()) }

Should give you the following warning:

Creating a MutableState object with a mutable collection type

Mutable state is a value holder. It can only trigger recomposition when the value itself changes. But it cannot track when the value state is changes - that's what you're doing when mutating a mutable list.

Instead you can use mutableStateListOf:

val content = remember { mutableStateListOf<String>() }

After this change the text should appear, ofc if you don't forget to run startLogcat.

An other problem in your code is with this line:

content.add(value.value)

In Compose recomposition can happen often, up to once for each frame in case of animations. It shouldn't gonna cause problems with your code, as you only have a single state changing, but generally it's a bad practice to mutate state directly from @Composable, check out read more in side effects documentation and thinking in compose.

The correct way of updating content in your case can look like this:

LaunchedEffect(Unit) {
    logcatService.logcatListener
        .collect(content::add)
}

An other solution is not using Flow in the first place, and move mutableStateListOf directly into your LogcatService.

Related