sdui_flutter_sanity 0.3.0 copy "sdui_flutter_sanity: ^0.3.0" to clipboard
sdui_flutter_sanity: ^0.3.0 copied to clipboard

A Server-Driven UI Flutter package powered by Sanity.io headless CMS with realtime config updates.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:sdui_flutter_sanity/sdui_flutter_sanity.dart';

// ---------------------------------------------------------------------------
// This example demonstrates the full SDUI Flutter Sanity workflow:
//
//  Step 1. Run `dart run sdui_flutter_sanity:generate` to generate:
//            - lib/sdui_components/product_card_component.dart
//            - lib/sdui_actions/add_to_cart_action_handler.dart
//          (The generated files are stubbed inline below for reference.)
//
//  Step 2. Implement the generated TODO stubs with your real widget code.
//
//  Step 3. Register them in SduiEngine.initialize() below.
//
//  Step 4. Drop <SduiView slug: 'home'> anywhere — done!
// ---------------------------------------------------------------------------

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await SduiEngine.initialize(
    config: const SduiConfig(
      projectId:
          'your_project_id_here', // <-- Replace with your Sanity Project ID
      dataset: 'production',
      // token: 'YOUR_READ_TOKEN_IF_DATASET_IS_PRIVATE',
    ),

    // Register the components generated (or hand-written) for this app.
    // The package already includes: text, image, button, column, row, card, spacer.
    components: [
      ProductCardComponent(),
      PromoBannerComponent(),
    ],

    // Register action handlers for interactive CMS-driven events.
    actionHandlers: [
      AddToCartActionHandler(),
    ],
  );

  runApp(const SduiApp());
}

// ---------------------------------------------------------------------------
// APP SHELL
// ---------------------------------------------------------------------------

class SduiApp extends StatelessWidget {
  const SduiApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SDUI + Sanity Demo',
      theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.indigo),
      // Integrate with your router (e.g. go_router) to pass dynamic slugs.
      home: const SduiPage(slug: 'home'),
    );
  }
}

/// Renders whatever component tree Sanity has published for [slug].
/// Replace [slug] with any Page Config document slug from your Sanity Studio.
class SduiPage extends StatelessWidget {
  final String slug;
  const SduiPage({super.key, required this.slug});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Page: $slug')),
      body: SafeArea(
        child: SduiView(
          slug: slug,
          scrollable: true,
          padding: const EdgeInsets.all(16),
          // Optional: show a branded loading state
          loadingWidget: const Center(child: CircularProgressIndicator()),
          // Optional: custom error UI with a retry button
          errorBuilder: (ctx, err, stack) => Center(
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                const Icon(Icons.cloud_off_outlined,
                    size: 48, color: Colors.grey),
                const SizedBox(height: 12),
                Text('Could not load "$slug":\n$err',
                    textAlign: TextAlign.center),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

// ===========================================================================
// GENERATED COMPONENT — product_card
// ===========================================================================
// Generated by: dart run sdui_flutter_sanity:generate
//   Component name: product_card
//   Fields:         title:string, price:number, imageUrl:image, inStock:boolean
//   Has children?   no
//
// After generation, replace `return const Placeholder()` with your real widget.
// ===========================================================================

class ProductCardComponent extends SduiComponent {
  @override
  String get type =>
      'productCard'; // Must match the Sanity schema `initialValue`

  @override
  Widget build(
      BuildContext context, UiNode node, Widget Function(UiNode) buildChild) {
    // Typed prop extraction — generated automatically by the CLI.
    final title = node.props['title'] as String? ?? '';
    final price = (node.props['price'] as num?)?.toDouble() ?? 0.0;
    final imageUrl = node.props['imageUrl'] as String? ?? '';
    final inStock = node.props['inStock'] as bool? ?? false;

    // Actions can be attached to any component in Sanity Studio.
    // e.g. the editor sets action = "cart_add:product-abc123"
    final action = SduiAction.tryParse(node.props['action']);

    // --- Real widget implementation (replacing the generated Placeholder) ---
    return Card(
      margin: const EdgeInsets.symmetric(vertical: 8),
      elevation: 2,
      child: InkWell(
        onTap: action != null
            ? () => SduiEngine.handleAction(context, action)
            : null,
        child: Padding(
          padding: const EdgeInsets.all(12),
          child: Row(
            children: [
              if (imageUrl.isNotEmpty)
                ClipRRect(
                  borderRadius: BorderRadius.circular(8),
                  child: Image.network(imageUrl,
                      width: 72, height: 72, fit: BoxFit.cover),
                ),
              const SizedBox(width: 12),
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(title, style: Theme.of(context).textTheme.titleMedium),
                    const SizedBox(height: 4),
                    Text(
                      '\$${price.toStringAsFixed(2)}',
                      style: Theme.of(context).textTheme.bodyLarge?.copyWith(
                          color: Colors.green, fontWeight: FontWeight.bold),
                    ),
                  ],
                ),
              ),
              if (!inStock)
                const Chip(
                    label: Text('Out of Stock'), backgroundColor: Colors.red),
            ],
          ),
        ),
      ),
    );
  }
}

// ===========================================================================
// HAND-WRITTEN COMPONENT — promo_banner
// (Demonstrates a component with children + actions, written without the CLI)
// ===========================================================================

class PromoBannerComponent extends SduiComponent {
  @override
  String get type => 'promoBanner';

  @override
  Widget build(
      BuildContext context, UiNode node, Widget Function(UiNode) buildChild) {
    final headline = node.props['headline'] as String? ?? '';
    final subtitle = node.props['subtitle'] as String? ?? '';

    // Dispatch CMS-defined navigation actions
    final action = SduiAction.tryParse(node.props['action']);

    return GestureDetector(
      onTap: action != null
          ? () => SduiEngine.handleAction(context, action)
          : null,
      child: Container(
        margin: const EdgeInsets.only(bottom: 16),
        padding: const EdgeInsets.all(20),
        decoration: BoxDecoration(
          gradient: const LinearGradient(
            colors: [Color(0xFF6200EA), Color(0xFF03DAC6)],
          ),
          borderRadius: BorderRadius.circular(12),
        ),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(headline,
                style: Theme.of(context).textTheme.headlineSmall?.copyWith(
                    color: Colors.white, fontWeight: FontWeight.bold)),
            if (subtitle.isNotEmpty) ...[
              const SizedBox(height: 4),
              Text(subtitle,
                  style: Theme.of(context)
                      .textTheme
                      .bodyMedium
                      ?.copyWith(color: Colors.white70)),
            ],
            // Recursively render any children the editor added in the CMS
            ...node.children.map(buildChild),
          ],
        ),
      ),
    );
  }
}

// ===========================================================================
// GENERATED ACTION HANDLER — add_to_cart
// ===========================================================================
// Generated by: dart run sdui_flutter_sanity:generate
//   Action name:  add_to_cart
//
// CMS action string format:  "cart_add:product-abc123"
//   action.type    => "cart_add"
//   action.payload => "product-abc123"
//   action.params  => {} (or structured params from Map format)
//
// After generation, replace the TODO body with your business logic.
// Works with any state management — Riverpod, Bloc, Provider, etc.
// ===========================================================================

class AddToCartActionHandler implements SduiActionHandler {
  @override
  bool canHandle(SduiAction action) => action.type == 'cart_add';

  @override
  void handle(BuildContext context, SduiAction action) {
    final productId = action.payload;
    final params = action.params;

    // --- Real implementation (replacing the generated debugPrint stub) ---
    // Plug in any state management:
    //   context.read<CartNotifier>().add(productId, params);
    //   context.read<CartBloc>().add(CartAddEvent(productId));

    debugPrint(
        'AddToCartActionHandler: Adding "$productId" with params $params');

    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text('Added "$productId" to cart!'),
        duration: const Duration(seconds: 2),
      ),
    );
  }
}
0
likes
140
points
73
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A Server-Driven UI Flutter package powered by Sanity.io headless CMS with realtime config updates.

Homepage
Repository (GitHub)
View/report issues

Topics

#ui #cms #sanity #server-driven-ui

License

MIT (license)

Dependencies

cached_network_image, flutter, hive, hive_flutter, http, url_launcher

More

Packages that depend on sdui_flutter_sanity