Search code examples
androidandroid-recyclerviewandroid-alertdialogandroid-jetpack-compose

Navigate to AlertDialog from LazyColumn


I've been experimenting a bit with the new Jetpack Compose for the last few days and it's been great but now I'm stuck on something that should be quite simple. All I want to do is from a LazyColumn (RecyclerView) show an AlertDialog when the user clicks (or long presses) an item in the list AND pass the clicked item as an argument to the AlertDialog. I've managed to do it without passing any arguments and just showing an AlertDialog with preset info. It also works fine to show a Toast message with the clicked item. Here is my code (it is basically the same as the Rally app from the compose-samples on GitHub):

@ExperimentalFoundationApi
@Composable
fun AccountsBody(navController: NavController, viewModel: AccountsViewModel) {

    val accountsFromVM = viewModel.accounts.observeAsState()

    accountsFromVM.value?.let { accounts ->
        StatementBody(
            items = accounts, // this is important for the question
            // NOT IMPORTANT
            amounts = { account -> account.balance },
            colors = { account -> HexToColor.getColor(account.colorHEX) },
            amountsTotal = accounts.map { account -> account.balance }.sum(),
            circleLabel = stringResource(R.string.total),
            buttonLabel = DialogScreen.NewAccount.route,
            navController = navController,

            onLongPress = {  } // show alert
        ) { account, _ -> // this is important for the question
            AccountRow(
                name = account.name,
                bank = account.bank,
                amount = account.balance,
                color = HexToColor.getColor(account.colorHEX),
                account = account,
                onClick = { },
                onlongPress = { clickedItem ->
                    // Show alert dialog here and pass in clickedItem
                }
            )
        }
    }
}
@Composable
fun <T> StatementBody(
    items: List<T>,
    // NOT IMPORTANT
    colors: (T) -> Color,
    amounts: (T) -> Float,
    amountsTotal: Float,
    circleLabel: String,
    buttonLabel: String,
    navController: NavController,

    onLongPress: (T) -> Unit,
    rows: @Composable (T, (T) -> Unit) -> Unit
) {
    Column {
        // Animating circle and balance box
        // NOT IMPORTANT - (see last few rows for the important part)
        Box(Modifier.padding(16.dp)) {
            val accountsProportion = items.extractProportions { amounts(it).absoluteValue }
            val circleColors = items.map { colors(it) }
            AnimatedCircle(
                accountsProportion,
                circleColors,
                Modifier
                    .height(300.dp)
                    .align(Alignment.Center)
                    .fillMaxWidth()
            )
            Column(modifier = Modifier.align(Alignment.Center)) {
                Text(
                    text = circleLabel,
                    style = MaterialTheme.typography.body1,
                    modifier = Modifier.align(Alignment.CenterHorizontally)
                )
                Text(
                    text = formatAmount(amountsTotal),
                    style = MaterialTheme.typography.h2,
                    modifier = Modifier.align(Alignment.CenterHorizontally)
                )
                Button(onClick = { navController.navigate("${buttonLabel}/Nytt sparkonto") }) {
                    Text(text = buttonLabel)
                }
            }
        }
        Spacer(Modifier.height(10.dp))

        // Recycler view
        // THIS IS THE IMPORTANT PART
        Card {
            LazyColumn(modifier = Modifier.padding(12.dp)) {
                itemsIndexed(items) { idx, item ->
                    rows(item, onLongPress)  // rows is the Composable you pass in
                }
            }
        }

    }
}
@ExperimentalFoundationApi
@Composable
fun AccountRow(
    name: String,
    bank: String,
    amount: Float,
    color: Color,
    account: AccountData,
    onClick: () -> Unit,
    onlongPress: (AccountData) -> Unit
) {
    BaseRow(
        color = color,
        title = name,
        subtitle = bank,
        amount = amount,
        rowType = account,
        onClick = onClick,
        onLongPress = onlongPress
    )
}

@ExperimentalFoundationApi
@Composable
private fun <T> BaseRow(
    color: Color,
    title: String,
    subtitle: String,
    amount: Float,
    rowType: T,
    onClick: () -> Unit,
    onLongPress: (T) -> Unit
) {
    val formattedAmount = formatAmount(amount)
    Row(
        modifier = Modifier
            .height(68.dp)
            .combinedClickable(
                onClick = onClick,
                onLongClick = { onLongPress(rowType) }  //HERE IS THE IMPORTANT PART
            ),
        verticalAlignment = Alignment.CenterVertically
    ) {
        val typography = MaterialTheme.typography

        AccountIndicator(color = color, modifier = Modifier)
        Spacer(Modifier.width(12.dp))

        Column(Modifier) {
            Text(text = title, style = typography.body1)
            CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
                Text(text = subtitle, style = typography.subtitle1)
            }
        }

        Spacer(Modifier.weight(1f))

        Row(horizontalArrangement = Arrangement.SpaceBetween) {
            Text(
                text = "$formattedAmount kr",
                style = typography.h6,
                modifier = Modifier.align(Alignment.CenterVertically)
            )
        }

        Spacer(Modifier.width(16.dp))

    }
    RallyDivider()
}

Any help on how to accomplish this would be appreciated. I'm kind of new to android development and programming in general so I have probably made this more complex than it has to be heh.


Solution

  • This can be easily done by saving the selected item as a MutableState and whether to show the dialog or not is another MutableState.

    Here is simplified example that could work as a starting point:

    val items = emptyList<String>()
    val currentSelectedItem = remember { mutableStateOf(items[0]) }
    val showDialog = remember { mutableStateOf(false) }
    
    if (showDialog.value) ShowDialog(currentSelectedItem.value) 
    
    Card {
      LazyColumn(modifier = Modifier.padding(12.dp)) {
        itemsIndexed(items) { idx, item ->
          Row() {
            Text(
              text = item,
              Modifier.clickable { 
                currentSelectedItem.value = item
                showDialog.value=true
              }
            )
          } // rows is the Composable you pass in
        }
      }
    }