digia_ui 1.1.0
digia_ui: ^1.1.0 copied to clipboard
Flutter SDK for rendering server-driven UIs using JSON from Digia Studio. Supports dynamic layouts, state, actions, and custom widgets.
Digia UI SDK is the Flutter-based rendering engine for Digia Studio, a low-code mobile application platform. Built on the Server-Driven UI (SDUI) architecture, this SDK dynamically renders native Flutter widgets based on configurations received from the server, enabling real-time UI updates without requiring app releases or store approvals.
๐ Overview #
What is Server-Driven UI? #
Server-Driven UI (SDUI) is an architectural pattern where the server controls the presentation layer of your application by sending UI configurations that the client interprets and renders. This approach offers several key advantages:
- ๐ Instant Updates - Deploy UI changes immediately without app store review cycles
- ๐งช A/B Testing - Run experiments and personalize experiences without client-side release cycles
- ๐ง Bug Fixes - Fix UI issues in production without releasing a new app version
- ๐ฑ Cross-Platform Consistency - Ensure uniform experiences across Android, iOS, and Mobile Web from a single configuration
The Digia Ecosystem #
Digia UI SDK is part of the Digia Studio ecosystem, where:
- Digia Studio - A visual low-code tool where users drag and drop widgets to create mobile applications
- Server Configurations - The studio generates structured configurations describing UI layout, data bindings, and business logic
- Digia UI SDK - This Flutter SDK interprets the server configurations to render fully functional native mobile apps across Android, iOS, and Mobile Web platforms
Key Features #
- ๐จ Server-Driven UI - Render Flutter widgets from server-side configurations
- ๐ Instant Updates - Push UI and logic changes instantly without app store approvals
- ๐ Expression Binding - Powerful data binding system for dynamic content
- ๐ฏ Pre-built Actions - Navigation, state management, API calls, and more
- ๐ฑ Native Performance - Rendering handled directly by Flutter widgets for optimal performance
- ๐งฉ Custom Widgets - Register your own widgets to extend functionality
- ๐ Multi-Platform - Single codebase for Android, iOS, and Mobile Web
๐ฆ Installation #
Add Digia UI SDK to your pubspec.yaml
:
dependencies:
digia_ui: ^latest_version
Or use the Flutter CLI:
flutter pub add digia_ui
Run:
flutter pub get
๐ Getting Started #
Note: Digia UI SDK requires configurations generated by Digia Studio. Please refer to the Digia Studio documentation to create your first project.
Initialization of DigiaUI SDK #
DigiaUI SDK offers two initialization strategies to suit different application needs:
NetworkFirst Strategy
- Prioritizes fresh content - Always fetches the latest DSL configuration from the network first
- Fast performance - DSL is hosted on CDN with average load times under 100ms for large projects
- Recommended for production - Ensures users always see the most up-to-date UI
- Best for - Apps where having the latest content is critical
- Timeout fallback - Optionally set a timeout; if exceeded, falls back to cache or burned DSL config
CacheFirst Strategy
- Instant startup - Uses cached DSL configuration for immediate rendering
- Fallback to network - Fetches updates in the background for next session
- Offline capable - Works even without network connectivity
- Best for - Apps prioritizing fast startup times or offline functionality
Implementation Options #
DigiaUI SDK offers two implementation options for different needs.
Option 1: Using DigiaUIApp
Use this approach when DigiaUI needs to be initialized before rendering the first frame.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final digiaUI = await DigiaUI.initialize(
DigiaUIOptions(
accessKey: 'YOUR_PROJECT_ACCESS_KEY',
flavor: Flavor.release(),
// Use a Strategy of your choice.
// NetworkFirstStrategy() or CacheFirstStrategy()
strategy: NetworkFirstStrategy(timeoutInMs: 2000),
),
);
runApp(
DigiaUIApp(
digiaUI: digiaUI,
builder: (context) => MaterialApp(
home: DUIFactory().createInitialPage(),
),
),
);
}
Option 2: Using DigiaUIAppBuilder
For advanced use cases where you need more granular control over the initialization process. You can choose whether or not to wait for DigiaUI to be ready. This is especially useful when your app starts with a few native Flutter pages before transitioning to DigiaUI-powered screens.
import 'package:digia_ui/digia_ui.dart';
void main() {
runApp(
DigiaUIAppBuilder(
options: DigiaUIOptions(
accessKey: 'YOUR_PROJECT_ACCESS_KEY', // Your project access key
flavor: Flavor.release(), // Use release or debug flavor
strategy: NetworkFirstStrategy(timeoutInMs: 2000), // Choose your init strategy
),
builder: (context, status) {
// Make sure to access DUIFactory only when SDK is ready
if (status.isReady) {
return MaterialApp(
home: DUIFactory().createInitialPage(),
);
}
// Show loading indicator while initializing
if (status.isLoading) {
return MaterialApp(
home: Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
SizedBox(height: 16),
Text('Loading latest content...'),
if (status.hasCache)
TextButton(
onPressed: () => status.useCachedVersion(),
child: Text('Use Offline Version'),
),
],
),
),
);
}
// Show error UI if initialization fails
// In practice, this scenario should never occur, but it's a good habit to provide a user-friendly fallback just in case.
return MaterialApp(
home: Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error_outline, size: 48),
SizedBox(height: 16),
Text('Failed to load content'),
Text('Error: ${status.error}'),
if (status.hasCache)
ElevatedButton(
onPressed: () => status.useCachedVersion(),
child: Text('Use Cached Version'),
),
],
),
),
),
);
},
),
);
}
๐ ๏ธ Usage Patterns #
Digia UI SDK supports two integration patterns:
1. Full App Mode #
Build your whole application in Digia Studio and use the SDK to render it. If you're on any paid plan, you can also download the APK directly from Digia Studio.
MaterialApp(
home: DUIFactory().createInitialPage(),
onGenerateRoute: (settings) {
// Let Digia UI handle all routing
return DUIFactory().createPageRoute(
settings.name!,
settings.arguments as Map<String, dynamic>?,
);
},
)
2. Hybrid Mode #
Migrate pages incrementally by mixing native Flutter screens with Digia UI pages:
// Navigate to a Digia UI page from native Flutter
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => DUIFactory().createPage('checkout_page', {
'cartId': cartId,
'userId': userId,
}),
),
);
๐ Creating Pages #
Pages are complete, full-screen UI definitions that include lifecycle hooks and built-in state management.
Learn more about Pages in the official documentation.
// Create a page with arguments
Widget checkoutPage = DUIFactory().createPage(
'checkout_page',
{
'cartId': '12345',
'totalAmount': 99.99,
},
);
// Navigate to a page
Navigator.push(
context,
DUIFactory().createPageRoute('product_details', {
'productId': product.id,
}),
);
๐งฉ Creating Components #
Components are modular UI elements that you can reuse throughout your app. They come with built-in lifecycle hooks and support for state management. Find more details about Components in the official documentation.
class ProductListPage extends StatelessWidget {
final List<Product> products;
const ProductListPage({Key? key, required this.products}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Products')),
body: ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) {
final product = products[index];
return DUIFactory().createComponent(
'product_list_item',
{
'id': product.id,
'title': product.title,
'price': product.price,
'imageUrl': product.imageUrl,
'rating': product.rating,
'isOnSale': product.isOnSale,
'discount': product.discount,
'onTap': () => _navigateToProduct(context, product),
'onAddToCart': () => _addToCart(product),
},
);
},
),
);
}
void _navigateToProduct(BuildContext context, Product product) {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => ProductDetailsPage(product: product),
),
);
}
void _addToCart(Product product) {
CartManager.instance.addToCart(product);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('${product.title} added to cart')),
);
}
}
๐๏ธ State Management #
Digia UI provides a comprehensive state management system with four levels:
1. Global State (App State) #
Shared across the entire app and can optionally persist between sessions. Global state provides bidirectional communication between your native Flutter code and Digia UI pages, enabling seamless integration and real-time updates.
Key Features:
- Bidirectional Access - Read and write from both native Flutter code and Digia UI
- Stream-Based - All state changes are broadcast through streams for reactive programming
- Real-time Updates - UI automatically updates when state changes from any source
- Persistence - Can optionally persist between app sessions
Setting State from Native Code:
// In your native Flutter code
class CartManager {
void addToCart(Product product) {
// Update your business logic
cart.add(product);
// Sync with Digia UI - these changes will be immediately reflected in all Digia UI pages
DUIAppState().setValue('cartCount', cart.length);
DUIAppState().setValue('cartTotal', cart.totalAmount);
DUIAppState().setValue('cartItems', cart.items.map((item) => item.toJson()).toList());
}
void updateUserProfile(User user) {
// Update user data that Digia UI pages can access
DUIAppState().setValue('user', {
'id': user.id,
'name': user.name,
'email': user.email,
'avatar': user.avatarUrl,
'preferences': user.preferences.toJson(),
});
}
}
Accessing State in Digia Studio:
For instructions on accessing App State within Digia Studio, please refer to the documentation.
Listening to State Changes in Native Code:
Global state changes can be monitored through streams, enabling reactive programming patterns:
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
late StreamSubscription _stateSubscription;
@override
void initState() {
super.initState();
// Listen to all global state changes
_stateSubscription = DUIAppState().listen('cartCount', (value) {
_updateCartBadge(value);
}
);
}
@override
void dispose() {
// Remember to cancel the subscription
_stateSubscription.cancel();
super.dispose();
}
void _updateCartBadge(int count) {
// Update native Flutter UI elements
setState(() {
// Update cart badge in app bar
});
}
}
StreamBuilder Integration in Digia Studio:
Since global state is stream-based, you can wrap any widget with StreamBuilder in Digia Studio to create reactive UI components that automatically update when state changes:
In Digia Studio Visual Editor:
- Drag a StreamBuilder widget from the widget palette
- Configure the stream source to listen to specific global state keys
- Design the UI that should update when the state changes
- Use expressions to access the stream data
Best Practices for Global State:
- Use meaningful keys - Choose descriptive names like
user.profile
instead ofdata1
- Structure complex data - Use nested objects for related data
- Minimize state size - Only store what's necessary for UI updates
- Handle null values - Always provide fallbacks in expressions
- Clean up on logout - Clear sensitive data when user logs out
- Use streams wisely - Cancel subscriptions to prevent memory leaks
2. Page State #
Scoped to individual pages and cleared when page is disposed. Every page needs its own state management for handling user interactions, form data, loading states, and temporary data that shouldn't persist beyond the page lifecycle.
Key Features:
- Automatically initialized when page is created
- Cleared when page is disposed or navigated away
- Isolated from other pages
- Perfect for form data, loading states, and temporary UI state
Common Use Cases:
- Form validation and data
- Loading and error states
- Selected items in lists
- Temporary filters and search queries
- Modal and dialog states
3. Component State #
Local state for reusable components. Components are modular UI elements that can maintain their own state independently, making them truly reusable across different pages and contexts.
Key Features:
- Each component instance has its own isolated state
- State persists as long as the component is mounted
- Enables building complex, stateful reusable components
- Perfect for interactive widgets like toggles, accordions, and custom inputs
Common Use Cases:
- Expandable/collapsible sections
- Custom input components with validation
- Interactive cards with hover/selection states
- Reusable form fields with their own validation
- Toggle switches and checkboxes
4. State Container (Local State) #
Widget-level state for UI interactions within a specific widget tree. While individual UI widgets in Digia Studio don't have built-in state, the Local State (State Container) provides a way to manage state in a localized manner within a widget subtree.
Key Features:
- Scoped to a specific widget subtree
- Managed by State Container widgets
- Enables stateful behavior for otherwise stateless widgets
- Perfect for localized UI interactions and temporary state
Common Use Cases:
- Counter widgets and numeric inputs
- Show/hide toggles for UI elements
- Tab selection within a widget group
- Temporary UI state that doesn't need to persist
- Interactive animations and transitions
State Hierarchy and Scope:
- Global State - Available everywhere in the app
- Page State - Available within the current page and its components
- Component State - Available within the specific component instance
- Local State - Available within the State Container widget subtree
Best Practices:
- Use Global State for user authentication, app settings, and data that needs to persist
- Use Page State for form data, loading states, and page-specific temporary data
- Use Component State for reusable component behavior and isolated interactions
- Use Local State for simple UI interactions and temporary widget-level state
๐จ Custom Widget Registration #
Extend Digia UI with your own custom widgets:
// Define your custom widget
class CustomMapWidget extends VirtualWidget<MapProps> {
final MapProps props;
@override
Widget render(RenderPayload payload) {
return GoogleMap(
initialCameraPosition: CameraPosition(
target: LatLng(props.latitude, props.longitude),
zoom: props.zoom,
),
);
}
}
// Register during initialization
DUIFactory().registerWidget(
'custom/map',
MapProps.fromJson,
(props, childGroups) => CustomMapWidget(props),
);
For detailed custom widget documentation, visit docs.digia.tech.
๐ License #
This project is licensed under the Business Source License 1.1 (BSL 1.1) - see the LICENSE file for details. The BSL 1.1 allows personal and commercial use with certain restrictions around competing platforms. On August 5, 2029, the license will automatically convert to Apache License 2.0.
For commercial licensing inquiries or exceptions, please contact admin@digia.tech.
๐ Support #
- ๐ Documentation
- ๐ฌ Community
- ๐ Issue Tracker
- ๐ง Contact Support
Built with โค๏ธ by the Digia team