What You Will Build

A financial dashboard showing a balance summary card with income and expenses breakdown, plus a scrollable transaction list with color-coded amounts. This pattern is used in banking apps, expense trackers, and any app displaying financial summaries behind a paywall.

Why This Pattern Matters

Financial dashboards require precise number formatting, color-coded positive/negative values, and clear visual hierarchy between summary and detail views. These skills apply to any data-heavy dashboard: analytics, health metrics, or inventory management.

Key Concepts

  • Card with primary containerColor for hero summary sections.
  • String.format for currency formatting with commas and decimals.
  • Conditional coloring based on income vs expense boolean flags.

The Dashboard Screen

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PaywallViewScreen() {
    data class Transaction(
        val name: String,
        val amount: Double,
        val icon: ImageVector,
        val isIncome: Boolean
    )
    val transactions = listOf(
        Transaction("Salary", 5000.0, Icons.Default.ArrowDownward, true),
        Transaction("Rent", -1200.0, Icons.Default.Home, false),
        Transaction("Groceries", -85.50, Icons.Default.ShoppingCart, false),
        Transaction("Freelance", 750.0, Icons.Default.ArrowDownward, true),
        Transaction("Utilities", -120.0, Icons.Default.ElectricalServices, false)
    )
    val balance = transactions.sumOf { it.amount }

    Scaffold(
        topBar = { TopAppBar(title = { Text("Paywall View") }) }
    ) { padding ->
        Column(
            Modifier.fillMaxSize().padding(padding).padding(16.dp)
        ) {
            // Balance summary card
            Card(
                Modifier.fillMaxWidth(),
                colors = CardDefaults.cardColors(
                    containerColor = MaterialTheme.colorScheme.primary
                )
            ) {
                Column(Modifier.padding(24.dp)) {
                    Text("Total Balance",
                        color = Color.White.copy(alpha = 0.8f))
                    Text(
                        "${'$'}${'$'}{String.format("%,.2f", balance)}",
                        fontSize = 36.sp,
                        fontWeight = FontWeight.Bold,
                        color = Color.White
                    )
                    Spacer(Modifier.height(16.dp))
                    Row(
                        Modifier.fillMaxWidth(),
                        horizontalArrangement = Arrangement.SpaceBetween
                    ) {
                        Column {
                            Text("Income",
                                color = Color.White.copy(alpha = 0.7f))
                            Text(
                                "+${'$'}...",
                                color = Color(0xFF81C784),
                                fontWeight = FontWeight.SemiBold
                            )
                        }
                        Column {
                            Text("Expenses",
                                color = Color.White.copy(alpha = 0.7f))
                            Text(
                                "-${'$'}...",
                                color = Color(0xFFEF9A9A),
                                fontWeight = FontWeight.SemiBold
                            )
                        }
                    }
                }
            }
            Spacer(Modifier.height(16.dp))
            Text("Recent Transactions",
                fontWeight = FontWeight.Bold, fontSize = 18.sp)
            Spacer(Modifier.height(8.dp))

            // Transaction list
            transactions.forEach { tx ->
                Card(
                    Modifier.fillMaxWidth().padding(vertical = 4.dp)
                ) {
                    Row(
                        Modifier.padding(16.dp),
                        verticalAlignment = Alignment.CenterVertically
                    ) {
                        Box(
                            Modifier.size(40.dp).clip(CircleShape)
                                .background(
                                    if (tx.isIncome)
                                        Color(0xFF4CAF50).copy(alpha = 0.1f)
                                    else
                                        Color(0xFFF44336).copy(alpha = 0.1f)
                                ),
                            contentAlignment = Alignment.Center
                        ) {
                            Icon(tx.icon, null,
                                tint = if (tx.isIncome) Color(0xFF4CAF50)
                                       else Color(0xFFF44336))
                        }
                        Spacer(Modifier.width(12.dp))
                        Text(tx.name, modifier = Modifier.weight(1f))
                        Text(
                            "${'$'}{if (tx.isIncome) "+" else ""}...",
                            fontWeight = FontWeight.Bold,
                            color = if (tx.isIncome) Color(0xFF4CAF50)
                                    else Color(0xFFF44336)
                        )
                    }
                }
            }
        }
    }
}

Tips and Pitfalls

  • Primary color card: Using MaterialTheme.colorScheme.primary as containerColor creates a branded hero card that stands out.
  • Currency formatting: Use String.format("%,.2f", amount) for locale-aware thousand separators and two decimal places.
  • Color coding convention: Green (0xFF4CAF50) for income, red (0xFFF44336) for expenses follows universal financial UI conventions.
  • LazyColumn migration: For longer transaction lists, replace forEach with LazyColumn + items() for better performance.

Minimum SDK: API 21+ with Compose BOM

Get New Tutorials by Email

No spam. Just clear, practical breakdowns you can apply right away.

Enjoy this tutorial?

Get new practical tech tutorials in your inbox.