Skip to main content
Stac provides multiple ways to render widgets from JSON, each suitable for different scenarios. This guide covers all available rendering methods and when to use them.

Prerequisites

Before rendering any Stac widgets, you must initialize Stac in your application:
import 'package:stac/stac.dart';
import 'package:your_app/default_stac_options.dart';

void main() async {
  await Stac.initialize(options: defaultStacOptions);
  runApp(const MyApp());
}

Rendering Methods

1. From Stac Cloud (Stac Widget)

The most common approach for server-driven UI is fetching screens from Stac Cloud using the Stac widget.

Usage

Stac(routeName: 'home_screen')
This widget automatically fetches the screen JSON from Stac Cloud based on the routeName and renders it.

Dart Source Code

The home_screen is defined in your /stac folder as a Dart file. Here’s an example: stac/home_screen.dart:
import 'package:stac_core/stac_core.dart';

@StacScreen(screenName: 'home_screen')
StacWidget homeScreen() {
  return StacScaffold(
    appBar: StacAppBar(title: StacText(data: 'Home')),
    body: StacColumn(
      children: [
        StacText(data: 'Welcome to Stac!'),
        StacElevatedButton(
          onPressed: {
            'actionType': 'navigate',
            'routeName': 'details'
          },
          child: StacText(data: 'Go to Details'),
        ),
      ],
    ),
  );
}
After running stac deploy, this Dart code is converted to JSON and uploaded to Stac Cloud, making it available via Stac(routeName: 'home_screen').

Properties

PropertyTypeDescription
routeNameStringThe screen name registered in Stac Cloud
loadingWidgetWidget?Custom widget shown while fetching (optional)
errorWidgetWidget?Custom widget shown on error (optional)

Example

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Stac Demo',
      home: Stac(
        routeName: 'hello_world',
        loadingWidget: const Center(child: CircularProgressIndicator()),
        errorWidget: const Center(child: Text('Failed to load screen')),
      ),
    );
  }
}

When to Use

  • ✅ Production apps using Stac Cloud
  • ✅ Dynamic content that changes server-side
  • ✅ A/B testing and experimentation
  • ✅ Apps that need instant updates without app store approval

2. From JSON (Stac.fromJson)

Render a widget directly from a JSON map. Useful for testing, prototyping, or when you have JSON in memory.

Usage

Stac.fromJson(jsonMap, context)

Properties

ParameterTypeDescription
jsonMap<String, dynamic>?The JSON object representing the widget
contextBuildContextThe build context

Example

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

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

  @override
  Widget build(BuildContext context) {
    final json = {
      'type': 'scaffold',
      'body': {
        'type': 'center',
        'child': {
          'type': 'text',
          'data': 'Hello from JSON!'
        }
      }
    };

    return Stac.fromJson(json, context) ?? const SizedBox();
  }
}

When to Use

  • ✅ Testing and development
  • ✅ Prototyping with hardcoded JSON
  • ✅ Rendering widgets from local variables
  • ✅ Converting existing JSON data to widgets

3. From Assets (Stac.fromAssets)

Load and render widgets from JSON files bundled with your app. Perfect for static content or offline-first scenarios.

Usage

Stac.fromAssets(
  'assets/screens/home.json',
  loadingWidget: (context) => const CircularProgressIndicator(),
  errorWidget: (context, error) => Text('Error: $error'),
)

Properties

ParameterTypeDescription
assetPathStringPath to the JSON file in your assets folder
loadingWidgetLoadingWidgetBuilder?Widget shown while loading (optional)
errorWidgetErrorWidgetBuilder?Widget shown on error (optional)

Setup

First, add your JSON file to pubspec.yaml:
flutter:
  assets:
    - assets/screens/home.json

Example

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

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

  @override
  Widget build(BuildContext context) {
    return Stac.fromAssets(
      'assets/screens/home.json',
      loadingWidget: (context) => const Scaffold(
        body: Center(child: CircularProgressIndicator()),
      ),
      errorWidget: (context, error) => Scaffold(
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Icon(Icons.error_outline, size: 48),
              const SizedBox(height: 16),
              Text('Failed to load: $error'),
            ],
          ),
        ),
      ),
    );
  }
}

When to Use

  • ✅ Static content that doesn’t change
  • ✅ Offline-first applications
  • ✅ Fallback screens when network fails
  • ✅ Demo apps and prototypes

4. From Network (Stac.fromNetwork)

Fetch and render widgets from any HTTP endpoint. Provides more control than Stac Cloud and works with your own API.

Usage

Stac.fromNetwork(
  context: context,
  request: StacNetworkRequest(
    url: 'https://api.example.com/ui/screen',
    method: Method.get,
  ),
  loadingWidget: (context) => const CircularProgressIndicator(),
  errorWidget: (context, error) => Text('Error: $error'),
)

Properties

ParameterTypeDescription
contextBuildContextThe build context
requestStacNetworkRequestNetwork request configuration
loadingWidgetLoadingWidgetBuilder?Widget shown while loading (optional)
errorWidgetErrorWidgetBuilder?Widget shown on error (optional)

StacNetworkRequest Properties

PropertyTypeDescription
urlStringThe URL to fetch JSON from
methodMethodHTTP method (get, post, put, delete)
headersMap<String, dynamic>?HTTP headers (e.g., Authorization)
queryParametersMap<String, dynamic>?URL query parameters
bodydynamicRequest body for POST/PUT
contentTypeString?Content-Type header (e.g., application/json)

Example

import 'package:flutter/material.dart';
import 'package:stac/stac.dart';
import 'package:stac_core/actions/network_request/stac_network_request.dart';

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

  @override
  Widget build(BuildContext context) {
    return Stac.fromNetwork(
      context: context,
      request: StacNetworkRequest(
        url: 'https://api.example.com/ui/home',
        method: Method.get,
        headers: {
          'Authorization': 'Bearer your-token-here',
          'Accept': 'application/json',
        },
      ),
      loadingWidget: (context) => const Scaffold(
        body: Center(child: CircularProgressIndicator()),
      ),
      errorWidget: (context, error) => Scaffold(
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Icon(Icons.cloud_off, size: 48),
              const SizedBox(height: 16),
              Text('Network error: $error'),
              const SizedBox(height: 16),
              ElevatedButton(
                onPressed: () {
                  // Retry logic
                },
                child: const Text('Retry'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

POST Request Example

Stac.fromNetwork(
  context: context,
  request: StacNetworkRequest(
    url: 'https://api.example.com/ui/dynamic',
    method: Method.post,
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer token',
    },
    body: {
      'userId': '123',
      'featureFlags': ['new-ui', 'experiments'],
    },
  ),
)

When to Use

  • ✅ Custom API endpoints
  • ✅ Server-side rendering
  • ✅ Dynamic content based on user data
  • ✅ Multi-tenant applications

Comparison

MethodSourceBest ForNetwork Required
Stac(routeName:)Stac CloudProduction SDUI apps✅ Yes
Stac.fromJson()In-memory JSONTesting, prototyping❌ No
Stac.fromAssets()Bundled JSON fileOffline, static content❌ No
Stac.fromNetwork()Custom API endpointCustom backends, advanced use cases✅ Yes

Best Practices

  1. Always provide loading states: Users should know content is loading.
  2. Handle errors gracefully: Show meaningful error messages and retry options.
  3. Use appropriate method: Choose the rendering method that fits your use case.
  4. Validate JSON: Ensure your JSON follows Stac schema before rendering.

Hybrid Approach

class HybridScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Try cloud first, fallback to assets
    return FutureBuilder<bool>(
      future: checkNetworkConnection(),
      builder: (context, snapshot) {
        if (snapshot.data == true) {
          return Stac(routeName: 'home_screen');
        } else {
          return Stac.fromAssets('assets/screens/home.json');
        }
      },
    );
  }
}