Loading...
Loading...
Report UI rule for Android: any report with potential for more than 25 rows must render as a table, not cards. Includes decision rules and Compose patterns.
npx skill4agent add peterbamuhigire/skills-web-dev android-report-tablesReportTable<T>core/ui/components/ReportTable.ktReportTable(
columns = listOf(
TableColumn(header = "#", weight = 0.4f) { "#${it.rank}" },
TableColumn(header = "Name", weight = 1.5f) { it.fullName ?: "-" },
TableColumn(header = "Inv", weight = 0.4f) { it.totalInvoices.toString() },
TableColumn(header = "Amount", weight = 1.2f) { "$currency ${fmt.format(it.totalAmount)}" }
),
rows = report.rows,
onRowClick = { /* optional */ },
pageSize = 25
)<T>TableColumn<T>surfaceVariantModifier.weight()2026-02-14d MMM yyyy14 Feb 2026val apiDateFmt = remember { SimpleDateFormat("yyyy-MM-dd", Locale.US) }
val displayDateFmt = remember { SimpleDateFormat("d MMM yyyy", Locale.US) }
val formatDate: (String) -> String = { raw ->
try { displayDateFmt.format(apiDateFmt.parse(raw)!!) } catch (_: Exception) { raw }
}
// Usage in TableColumn:
TableColumn("Date", minWidth = 100.dp) { formatDate(it.date) }
TableColumn("Oldest", minWidth = 100.dp) { it.oldestDate?.let { formatDate(it) } ?: "-" }yyyy-MM-ddd MMM yyyyMMM dFeb 14-weight| Column Type | Weight | Examples |
|---|---|---|
| Index/Rank | 0.3-0.5f | #, Rank |
| Short text | 0.4-0.6f | Code, Qty, Inv |
| Name/Description | 1.3-1.5f | Product, Distributor |
| Currency amount | 1.0-1.2f | Amount, Balance, Due |
| Date | 0.8-1.0f | Date |
Column(Modifier.horizontalScroll(rememberScrollState())) {
ReportTable(columns = ..., rows = ...)
}stringResource(R.string.report_col_*)| Criteria | Use Cards | Use Table |
|---|---|---|
| Max rows <= 25 guaranteed | Yes | Optional |
| Max rows > 25 possible | No | Required |
| DPCs (5-20 items) | Yes | Optional |
| Daily summary (7 days) | Yes | Optional |
| Distributor lists | No | Required |
| Product lists | No | Required |
| Invoice lists | No | Required |
| Debtors lists | No | Required |
| Top 100 rankings | No | Required |
ReportTable@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MyReportScreen(viewModel: MyViewModel = hiltViewModel()) {
val uiState by viewModel.uiState.collectAsState()
var isRefreshing by remember { mutableStateOf(false) }
LaunchedEffect(uiState.loading) {
if (!uiState.loading) isRefreshing = false
}
PullToRefreshBox(
isRefreshing = isRefreshing,
onRefresh = { isRefreshing = true; viewModel.reload() },
modifier = Modifier.fillMaxSize()
) {
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(16.dp)
) {
// Report content
}
}
}reload()refresh()PullToRefreshBoxPullToRefreshContainerColumnLazyColumnReportTablePullToRefreshBoxPullToRefreshBox(
isRefreshing = isRefreshing,
onRefresh = { isRefreshing = true; viewModel.reload() },
modifier = Modifier.fillMaxSize().padding(paddingValues)
) {
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
// Filters
// Summary cards
// ReportTable (handles its own pagination)
}
}Modifier.weight()stringResource()verticalScroll