androidkotlinandroid-jetpack-compose

How to update arraylist in jetpack compose


I'm stuck trying to understand how to get a variable initialised, and later updated, in kotlin to update in Jetpack Compose and cause a composition that uses it to recompose. The variable is an arraylist of bitmaps set up like so (all of this works fine so I have not included the getOutputDirectory() and imageReaderNew() functions):

// set up an empty bitmaps arraylist
// should I be using mutableListOf() here? if so how?
var bitmaps: ArrayList<Bitmap> = ArrayList()

// get the uri of the folder to save images to
val outputDirectory = getOutputDirectory()

// read in a list of images in the images folder
var fileList: ArrayList<File> = imageReaderNew(outputDirectory)

// get a list of bitmaps of the images
bitmaps = getBitmapList(fileList)

When I initialise my composition I do this:

BottomSheetScaffold(
    scaffoldState = scaffoldState,
    sheetPeekHeight = 0.dp,
    sheetContent = {
        PhotoBottomSheetContent(
            bitmaps = bitmaps,
            //should I be using remember here?
            //bitmaps = remember { bitmaps },
            modifier = Modifier.fillMaxWidth()
        )
   }
)
{ }

Finally, in my composable I do this:

fun PhotoBottomSheetContent(
    bitmaps: List<Bitmap>,
    // should I be using mutableListOf() or remember { } here? again if so how?
    modifier: Modifier = Modifier
)
{
}

Solution

  • The way you're doing it doesn’t work because Jetpack Compose doesn’t automatically track changes to regular variables like ArrayList. Compose only recomposes when it detects changes to state objects it’s explicitly observing (like mutableStateOf or mutableStateListOf). Since bitmaps is just a plain ArrayList, updating it won’t trigger recomposition.

    To make Compose track changes to bitmaps and trigger recomposition, you need to wrap it in a Compose state. Here’s how:

    Use mutableStateListOf to ensures Compose tracks changes to individual items (adding/removing items):

    val bitmaps = remember { mutableStateListOf<Bitmap>() }
    

    Use LaunchedEffect to load bitmaps without blocking the UI. Update the state when the data is ready:

    LaunchedEffect(Unit) {
        val outputDirectory = getOutputDirectory()
        val fileList = imageReaderNew(outputDirectory)
        val loadedBitmaps = getBitmapList(fileList)
        
        bitmaps.clear()
        bitmaps.addAll(loadedBitmaps) 
    }
    

    Pass the state to your composable. No need for remember here—Compose will automatically observe the state:

    BottomSheetScaffold(
        sheetContent = {
            PhotoBottomSheetContent(bitmaps = bitmaps)
        }
    )
    

    In PhotoBottomSheetContent, use the bitmaps list as usual. It will automatically recompose when the state changes:

    @Composable
    fun PhotoBottomSheetContent(bitmaps: List<Bitmap>) {
        // This will recompose when bitmaps changes
    }
    

    If you’re replacing the entire list (like loading a new set of bitmaps), you can use mutableStateOf instead of mutableStateListOf. It would look like

    var bitmaps by remember { mutableStateOf<List<Bitmap>>(emptyList()) }
    
    LaunchedEffect(Unit) {
        val loadedBitmaps = getBitmapList(fileList)
        bitmaps = loadedBitmaps // Reassign the entire list
    }