fassad_ui 0.2.0 copy "fassad_ui: ^0.2.0" to clipboard
fassad_ui: ^0.2.0 copied to clipboard

Flutter widgets and HTTP client for querying dblm fassad templates — render database results as charts and tables.

fassad_ui #

Flutter widgets and HTTP client for querying dblm fassad templates — render your database results as charts and tables with a few lines of code.

What is a fassad? #

A fassad is a reusable, parameterized query template stored in dblm. You define the query once (dblm fassad create), give it named parameters, and then run it from anywhere — including this Flutter package — without writing SQL or touching a DSN.

Architecture #

Flutter app  ──►  fassad_ui  ──►  @dblm/middleware (HTTP)  ──►  dblm  ──►  database

This package talks to @dblm/middleware, which must be running before your app starts.

Setup #

1. Start the middleware

npx @dblm/middleware
# set API_KEY=your-secret in .env or environment

2. Add the package

dependencies:
  fassad_ui: ^0.2.0

3. Run flutter pub get

Usage #

Query and display data #

import 'package:fassad_ui/fassad_ui.dart';

final client = FassadClient(
  baseUrl: 'http://localhost:3000',
  apiKey: 'your-api-key',
);

// Execute a fassad template
final result = await client.run('top-users', params: {'limit': '10'});

// Render as a bar chart (responsive, theme-aware)
FassadChart(
  result: result,
  xColumn: 'name',
  yColumn: 'count',
  type: FassadChartType.bar,
)

// Render as a scrollable table
FassadTable(result: result)

Offline-capable dashboard (stale-while-revalidate) #

Use runWithCache() instead of run() to get instant rendering from local cache while fresh data loads in the background. Works offline automatically.

class SalesDashboard extends StatefulWidget {
  const SalesDashboard({super.key});

  @override
  State<SalesDashboard> createState() => _SalesDashboardState();
}

class _SalesDashboardState extends State<SalesDashboard> {
  final _client = FassadClient(
    baseUrl: 'http://localhost:3000',
    apiKey: 'your-api-key',
  );
  final _cache = FassadCache();   // shared instance; persists across app restarts

  late final Stream<FassadCachedResult> _stream;

  @override
  void initState() {
    super.initState();
    _stream = _client.runWithCache(
      'monthly-sales',
      params: {'year': '2024'},
      cache: _cache,
    );
  }

  @override
  void dispose() {
    _client.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<FassadCachedResult>(
      stream: _stream,
      builder: (context, snapshot) {
        if (snapshot.hasError) {
          return Text('Error: ${snapshot.error}');
        }
        if (!snapshot.hasData) {
          return const CircularProgressIndicator();
        }

        final r = snapshot.data!;
        return Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // Show a badge when rendering cached (offline) data
            if (r.isStale)
              Text('Showing cached data from ${r.cachedAt}'),

            FassadChart(
              result: r.data,
              xColumn: 'month',
              yColumn: 'revenue',
              type: FassadChartType.line,
            ),
            FassadTable(result: r.data),
          ],
        );
      },
    );
  }
}

How runWithCache behaves:

Scenario What happens
First launch, online Shows loading indicator briefly → emits fresh result → saves to cache
Subsequent launch, online Emits cached result immediately (no blank screen) → silently updates with fresh data
Offline, cache exists Emits cached result immediately → network error swallowed silently
Offline, no cache Throws FassadException so you can surface the error

Dark / light mode #

Charts automatically respect the app theme — no extra configuration needed. Just supply both theme and darkTheme in your MaterialApp:

MaterialApp(
  theme: ThemeData(colorSchemeSeed: Colors.indigo, brightness: Brightness.light),
  darkTheme: ThemeData(colorSchemeSeed: Colors.indigo, brightness: Brightness.dark),
  themeMode: ThemeMode.system,
  home: MyDashboard(),
)

Bar/line colours come from colorScheme.primary, grid lines from colorScheme.outlineVariant, and pie sections cycle through [primary, secondary, tertiary, primaryContainer, secondaryContainer, tertiaryContainer] — all adapting correctly in both modes.

API reference #

FassadClient #

FassadClient({
  required String baseUrl,  // middleware base URL, e.g. 'http://localhost:3000'
  required String apiKey,   // must match API_KEY set on the middleware
  http.Client? httpClient,  // optional — inject for testing
})
Method Returns Description
list() Future<List<FassadSummary>> List all available fassad templates
show(name) Future<FassadDetail> Get template details and parameter definitions
run(name, {params}) Future<FassadResult> Execute a template and return rows
runWithCache(name, {params, cache}) Stream<FassadCachedResult> Stale-while-revalidate fetch with local persistence
dispose() void Close the underlying HTTP client

Throws FassadException(statusCode, body) on any non-2xx response.

FassadCache #

Persists FassadResult data to SharedPreferences keyed by template name + params.

final cache = FassadCache();

await cache.save(name, params, result);       // write
await cache.load(name, params);               // FassadCachedResult?
await cache.remove(name, params);             // delete one entry
await cache.clearAll();                       // wipe all fassad cache entries

Create a single FassadCache instance per app and pass it to every runWithCache call so all templates share the same store.

FassadCachedResult #

Returned by runWithCache() stream emissions.

Property Type Description
data FassadResult The query result (columns + rows)
isStale bool true when data came from local cache, false when fresh from network
cachedAt DateTime When the data was last successfully fetched from the network

FassadChart #

Property Type Default Description
result FassadResult required Data from client.run() or cached.data
xColumn String required Column name for X axis / pie labels
yColumn String required Column name for Y axis / pie values
type FassadChartType bar Chart type: bar, line, or pie
height double? auto Fixed height in logical pixels; omit to auto-size

The chart is fully responsive — it uses LayoutBuilder to fill available width and scales bar widths, font sizes, and pie geometry accordingly. When height is omitted the chart takes 55 % of available width, clamped between 200 and 420 px.

Shows "No data" when result.rows is empty.

Chart types:

Value Description
FassadChartType.bar Vertical bar chart with adaptive bar widths
FassadChartType.line Curved line chart with shaded area fill
FassadChartType.pie Pie chart with labels, scaled center radius

FassadTable #

Property Type Default Description
result FassadResult required Data from client.run() or cached.data
columnWidth double? null Optional fixed column width

Renders a scrollable DataTable (horizontal + vertical scroll). Shows "No rows returned" when empty.

Models #

FassadResult

class FassadResult {
  final List<String> columns;
  final List<List<dynamic>> rows;
  final String source;         // database driver, e.g. "postgres"

  // Convenience accessor — returns null if column not found
  dynamic cell(int row, String column);
}

FassadSummary — name, mode, paramCount, createdAt

FassadDetail — name, mode, connection, module, body, params, createdAt, updatedAt

FassadParam — name, kind, required, defaultValue

Requirements #

Dependency Version
Dart SDK ≥ 3.0.0
Flutter ≥ 3.10.0
http ^1.2.0
fl_chart ^0.68.0
shared_preferences ^2.2.0

License #

MIT

0
likes
140
points
64
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Flutter widgets and HTTP client for querying dblm fassad templates — render database results as charts and tables.

Repository (GitHub)

License

MIT (license)

Dependencies

fl_chart, flutter, http, shared_preferences

More

Packages that depend on fassad_ui