0

I'm currently thinking about creating an initiative tracker app for a friend of mine and since I've been out of touch with app development for quite a while I'm trying to evaluate which tools would best suit my needs.

Since the App is going to be developed for Android, I'm pretty sure I'll be using Kotlin and therefore Jetpack Compose caught my eye. After making a bit of research and going through the docs though, I'm unsure if it is capable of what I want to achieve: I want to be able to create a dynamic list of entry cards sorted by a certain value asigned to each card. (Which I'm relatively certain LazyColumns will be able to handle). The catch is this: Each of these cards needs to have buttons and several additional values that can be manipulated with these buttons.
I haven't been able to find descriptions of something like this in the docs or any examples using Jetpack Compose LazyColumns.

I created a (badly made) mockup to hopefully better describe what it is I want to do:

Mockup

Would anyone be able to share insights on if Jetpack Compose is capable of these features or if not could share advice on what tool to use instead?

Thanks alot and Kind regards.

Phil Dukhov
  • 67,741
  • 15
  • 184
  • 220
Thormgrim
  • 145
  • 1
  • 1
  • 12

1 Answers1

2

Yes, with Compose, you can quite easily make such an application.

Here is a basic example of such a UI.

class MainActivity : FragmentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            AppTheme {
                ItemsScreen()
            }
        }
    }
}

class Item(val title: String, value1: Int, value2: Int, value3: Int) {
    val value1 = mutableStateOf(value1)
    val value2 = mutableStateOf(value2)
    val value3 = mutableStateOf(value3)
    val valueToSortBy: Int
        get() = value1.value + value2.value + value3.value
}

class ScreenViewModel : ViewModel() {
    val items = List(3) {
        Item(
            "Item $it",
            Random.nextInt(10),
            Random.nextInt(10),
            Random.nextInt(10),
        )
    }.toMutableStateList()

    fun sortItems() {
        items.sortByDescending { it.valueToSortBy }
    }

    fun addNewItem() {
        items.add(
            Item(
                "Item ${items.count()}",
                Random.nextInt(10),
                Random.nextInt(10),
                Random.nextInt(10),
            )
        )
    }
}

@Composable
fun ItemsScreen() {
    val viewModel: ScreenViewModel = viewModel()
    LaunchedEffect(viewModel.items.map { it.valueToSortBy }) {
        viewModel.sortItems()
    }
    Box(
        Modifier
            .fillMaxSize()
            .padding(10.dp)
    ) {
        LazyColumn(
            contentPadding = PaddingValues(vertical = 10.dp),
            verticalArrangement = Arrangement.spacedBy(10.dp)
        ) {
            items(viewModel.items) { item ->
                ItemView(item)
            }
        }
        FloatingActionButton(
            onClick = viewModel::addNewItem,
            modifier = Modifier.align(Alignment.BottomEnd)
        ) {
            Text("+")
        }
    }
}

@Composable
fun ItemView(item: Item) {
    Card(
        elevation = 5.dp,
    ) {
        Column(modifier = Modifier.padding(10.dp)) {
            Row(
                verticalAlignment = Alignment.CenterVertically,
            ) {
                Text(
                    item.title,
                    style = MaterialTheme.typography.h5
                )
                Spacer(modifier = Modifier.weight(1f))
                Text(
                    "Value to sort by: ${item.valueToSortBy}",
                    style = MaterialTheme.typography.body1,
                    fontStyle = FontStyle.Italic,
                )
            }
            Spacer(modifier = Modifier.size(25.dp))
            Row(
                horizontalArrangement = Arrangement.SpaceBetween,
                modifier = Modifier.fillMaxWidth()
            ) {
                CounterView(value = item.value1.value, setValue = { item.value1.value = it })
                CounterView(value = item.value2.value, setValue = { item.value2.value = it })
                CounterView(value = item.value3.value, setValue = { item.value3.value = it })
            }
        }
    }
}

@Composable
fun CounterView(value: Int, setValue: (Int) -> Unit) {
    Row(verticalAlignment = Alignment.CenterVertically) {
        CounterButton("+") {
            setValue(value + 1)
        }
        Text(
            value.toString(),
            modifier = Modifier.padding(5.dp)
        )
        CounterButton("-") {
            setValue(value - 1)
        }
    }
}

@Composable
fun CounterButton(text: String, onClick: () -> Unit) {
    Box(
        contentAlignment = Alignment.Center,
        modifier = Modifier
            .size(45.dp)
            .background(Color.LightGray)
            .clickable(onClick = onClick)
    ) {
        Text(
            text,
            color = Color.White,
        )
    }
}

I'm sorting by sum of 3 values here. Note that sorting items like this may not do much performant in a production app, you should use a database to store items and request sorted items from it.

Phil Dukhov
  • 67,741
  • 15
  • 184
  • 220
  • @Thormgrim you can check out on [SQLDelight](https://cashapp.github.io/sqldelight/), it's pretty light and all work with the database is done with SQL, giving your a decent kotlin API for all SQL functions you create – Phil Dukhov Sep 09 '21 at 12:49
  • I seem to be having problems getting this to run like in the gif you posted. For me its very jittery, it's probably because I changed the AppTheme class and val viewModel: ScreenViewModel = viewModel() because those could not be resolved. Any advice where these come from? – Thormgrim Sep 10 '21 at 05:23
  • @Thormgrim changing AppTheme is fine, but you shouldn't change `viewModel()`. Make sure you have `import androidx.lifecycle.viewmodel.compose.viewModel`, if this didn't help, let me know the exact error – Phil Dukhov Sep 10 '21 at 08:23
  • thanks for the quick response. Although it seems that the import you mentioned cannot be resolved for me, did you use any specific dependencies for this mayhaps? – Thormgrim Sep 10 '21 at 08:28
  • @Thormgrim looks like it's from "androidx.activity:activity-compose:1.3.1" – Phil Dukhov Sep 10 '21 at 08:35
  • hm curious. I'm using "androidx.activity:activity-compose:1.4.0-alpha01" so I's expect it would be fine. Thanks anyway I'm sure I'll find out what's wrong eventually. – Thormgrim Sep 10 '21 at 13:13
  • @Thormgrim In my case it's shipped with `activity-compose`, but you can import it directly: `androidx.lifecycle:lifecycle-viewmodel-compose:1.0.0-alpha07` – Phil Dukhov Sep 10 '21 at 13:23