Usage Dashboard
Loading preview
Starting the Flutter app
A Flutter SaaS usage dashboard with plan limits, metered usage bars, and an upgrade prompt.
import 'package:flutter/material.dart';
class _Meter {
final String label;
final int used;
final int limit;
final String unit;
const _Meter(this.label, this.used, this.limit, this.unit);
}
class UsageDashboard extends StatelessWidget {
const UsageDashboard({super.key});
static const _meters = [
_Meter('API requests', 82000, 100000, ''),
_Meter('Storage', 38, 50, 'GB'),
_Meter('Team seats', 9, 10, ''),
_Meter('Automations', 1200, 2000, '/mo'),
];
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final scheme = theme.colorScheme;
return Scaffold(
appBar: AppBar(title: const Text('Usage')),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
Card(
color: scheme.primaryContainer,
child: Padding(
padding: const EdgeInsets.all(20),
child: Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Pro plan',
style: theme.textTheme.titleLarge?.copyWith(
color: scheme.onPrimaryContainer,
fontWeight: FontWeight.bold)),
Text('Renews Aug 1 · \$49/mo',
style: TextStyle(color: scheme.onPrimaryContainer)),
],
),
const Spacer(),
FilledButton(
onPressed: () => _showDemo(context, 'Upgrade (demo)'), child: const Text('Upgrade')),
],
),
),
),
const SizedBox(height: 8),
for (final m in _meters) _MeterTile(meter: m),
],
),
);
}
}
class _MeterTile extends StatelessWidget {
final _Meter meter;
const _MeterTile({required this.meter});
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
final ratio = meter.used / meter.limit;
final near = ratio > 0.8;
return Card(
margin: const EdgeInsets.symmetric(vertical: 6),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(meter.label,
style: const TextStyle(fontWeight: FontWeight.w600)),
const Spacer(),
Text('${_fmt(meter.used)} / ${_fmt(meter.limit)}${meter.unit}',
style: TextStyle(
color: near ? scheme.error : scheme.onSurfaceVariant)),
],
),
const SizedBox(height: 10),
ClipRRect(
borderRadius: BorderRadius.circular(6),
child: LinearProgressIndicator(
value: ratio,
minHeight: 8,
color: near ? scheme.error : scheme.primary,
backgroundColor: scheme.surfaceContainerHighest,
),
),
],
),
),
);
}
String _fmt(int n) =>
n >= 1000 ? '${(n / 1000).toStringAsFixed(n % 1000 == 0 ? 0 : 1)}k' : '$n';
}
void _showDemo(BuildContext context, String message) {
ScaffoldMessenger.of(context)
..hideCurrentSnackBar()
..showSnackBar(SnackBar(content: Text(message)));
}
Add it to your project
Copies the Dart source into your app, so you own it. Flutter SDK only, no extra packages.
First time? Run dart pub global activate fluttertemplates_dev, or read the CLI guide.
How it works
_meters drives one _MeterTile each. The tile computes ratio, and near flips text and bar to scheme.error when usage passes 80%, the only real logic here. _fmt abbreviates thousands, so 82000 prints as 82k and 1200 as 1.2k. Plan name and renewal date are literals, and Upgrade shows a demo SnackBar.
Adapting it
- Fetch
_metersfrom your billing API, moving the page to aFutureBuilderor state object. - Pull the
0.8threshold into a named constant, or set it per meter. - Add a space before
unitwhere needed, as'GB'currently renders flush against the number.