Home > Enterprise >  How to use sealed class data to display alphabet index scroller
How to use sealed class data to display alphabet index scroller

Time:06-17

After creating a sealed class for my LazyColumn, how can I use the inital of every item for an alphabet scroller? it.? is where my problem occurs as for some reason, it does not let me accesss my sealed class and use it, i.e. itemName.

sealed class Clothes {
    data class FixedSizeClothing(val itemName: Int, val sizePlaceholder: Int): Clothes()

    data class MultiSizeClothing(val itemName: Int, val sizePlaceholders:
    List<Int>): Clothes()
}

val clothingItems = remember { listOf(
        Clothes.FixedSizeClothing(itemName = R.string.jumper, itemPlaceholder = 8),
        Clothes.MultiSizeClothing(itemName = R.string.dress, itemPlaceholders = listOf(0, 2))
    )
}

val headers = remember { clothingItems.map { getString(it.?).first().uppercase() }.toSet().toList() }

val listState = rememberLazyListState()
LazyColumn(
    state = listState,
    modifier = Modifier
        .weight(1f)
        .padding(it)
) {
    items(clothingItems) {
        val text1 = when (it) {
            is Clothes.FixedSizeClothing ->
                stringResource(id = it.itemName)
            is Clothes.MultiSizeClothing ->
                stringResource(id = it.itemName)
        }

        val text2 = when (it) {
            is Clothes.FixedSizeClothing ->
                stringResource(id = R.string.size_placeholder, it.sizePlaceholder)
            is Clothes.MultiSizeClothing ->
                stringResource(id = R.string.size_placeholder_and_placeholder, it.itemPlaceholders[0], it.itemPlaceholders[1])
        }

        Column(modifier = Modifier
            .fillMaxWidth()
            .clickable {}) {...}
    }
}

val offsets = remember { mutableStateMapOf<Int, Float>() }
var selectedHeaderIndex by remember { mutableStateOf(0) }
val scope = rememberCoroutineScope()

fun updateSelectedIndexIfNeeded(offset: Float) {
    val index = offsets
        .mapValues { abs(it.value - offset) }
        .entries
        .minByOrNull { it.value }
        ?.key ?: return
    if (selectedHeaderIndex == index) return
    selectedHeaderIndex = index
    val selectedItemIndex = clothingItems.indexOfFirst { getString(it.?).first().uppercase() == headers[selectedHeaderIndex] }
    scope.launch {
        listState.scrollToItem(selectedItemIndex)
    }
}

Column(
    verticalArrangement = Arrangement.SpaceEvenly,
    modifier = Modifier
        .fillMaxHeight()
        .background(Color.Gray)
        .pointerInput(Unit) {
            detectTapGestures {
                updateSelectedIndexIfNeeded(it.y)
            }
        }
        .pointerInput(Unit) {
            detectVerticalDragGestures { change, _ ->
                updateSelectedIndexIfNeeded(change.position.y)
            }
        }
) {
    headers.forEachIndexed { i, header ->
        Text(
            header,
            modifier = Modifier
                .onGloballyPositioned {
                    offsets[i] = it.boundsInParent().center.y
                },
            color = Color.White
        )
    }
}

CodePudding user response:

You can define the val itemName: Int in the parent Clothes class and override it in you other subclasses. If you do that, you then do not need to use a when expression if you just want to access the itemName property.

The parent class can be a sealed interface instead of a sealed class. That way it is a bit more flexible and a bit less verbose when overriding its properties. Also : Clothes() then becomes just : Clothes

sealed interface Clothes {
    val itemName: Int

    data class FixedSizeClothing(override val itemName: Int, val sizePlaceholder: Int): Clothes
    data class MultiSizeClothing(override val itemName: Int, val sizePlaceholders: List<Int>): Clothes
}

And the line where you create the headers becomes

val headers = clothingItems.map { stringResource(it.itemName).first().uppercase() }.toSet().toList()

The remember {} does not make much sense because if your data changes so can the set of initial letters. You also cannot use the stringResouce() function inside remember {}, because stringResouce() has to be used inside a @Composable function.

There is a different way of obtaining Resources and then using resources.getString(...) if you would like to retrieve resource strings inside a remember {} block. But in this case the remember {} block does not make sense due to the data potentially changing. The optimization to cache initial letters would have to be done in a different way.

  • Related