Backend-Driven UI
Flutter UIs That Update Themselves. Seriously.
Server-driven UI framework with ApiWidget - build data-driven interfaces without FutureBuilder boilerplate.
✨ Features
- 🎯 33 Built-in Widgets - Fully interactive apps from JSON
- ⚡ Zero App Releases - Update UI from backend JSON instantly
- 🔓 100% Open Source - MIT licensed, yours forever
- 📦 ApiWidget - FutureBuilder's smarter, faster cousin
- 🚀 Lightweight & Fast - Optimized parsing, lazy loading
- 💎 Production Ready - Buttons, lists, gestures, caching
📸 Screenshots
A WhatsApp-style UI built entirely from backend JSON — zero hardcoded screens.
| Chats | Status | Calls | Settings |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
🚀 Quick Start
Installation
dependencies:
backend_driven_ui: ^0.3.0
Global Base URL (optional)
Set once at app startup to avoid repeating the full URL on every widget:
void main() {
BduiConfig.baseUrl = 'https://api.myapp.com';
runApp(MyApp());
}
All relative endpoints are then resolved automatically:
BackendDrivenScreen(endpoint: '/screens/home') // → https://api.myapp.com/screens/home
ApiWidget(endpoint: '/products') // → https://api.myapp.com/products
Full URLs (https://...) are always used as-is regardless of baseUrl.
📖 Documentation
Backend-Driven UI (the wow factor)
Render an entire screen from a JSON schema your backend returns. No app update needed — change the JSON, the UI changes instantly.
BackendDrivenScreen(
endpoint: '/api/screens/home',
cacheDuration: Duration(minutes: 5),
onNavigate: (route, args) => Navigator.pushNamed(context, route),
)
Backend returns JSON → Flutter renders the UI:
{
"type": "Column",
"props": { "mainAxisAlignment": "center" },
"children": [
{
"type": "Text",
"props": { "text": "Hello from Backend!", "fontSize": 24, "color": "#1976D2" }
},
{ "type": "SizedBox", "props": { "height": 16 } },
{
"type": "ElevatedButton",
"props": { "text": "Shop Now" },
"action": { "type": "navigate", "route": "/shop" }
}
]
}
Colors in JSON
Three formats are supported — use whichever fits your backend:
| Format | Example | Notes |
|---|---|---|
| Named | "color": "blue" |
All Flutter Colors.* names |
Colors.x |
"color": "Colors.deepPurple" |
Flutter notation directly |
Hex #RRGGBB |
"color": "#1976D2" |
Standard CSS hex |
Hex #AARRGGBB |
"color": "#FF1976D2" |
With alpha channel |
| ARGB int | "color": 4278190080 |
Raw Flutter int |
Local Schema (no API call needed)
SchemaWidget.fromJson({
"type": "Column",
"children": [
{ "type": "Text", "props": { "text": "Hello from JSON!", "fontSize": 24 } }
]
})
ApiWidget
The declarative way to fetch and display API data.
ApiWidget(
endpoint: '/api/products/featured',
method: HttpMethod.get,
// Optional: Headers
headers: {'Authorization': 'Bearer $token'},
// Optional: Request body (for POST/PUT)
body: {'category': 'electronics'},
// Optional: Cache duration
cacheDuration: Duration(minutes: 5),
// Optional: Auto-refresh (polling)
pollInterval: Duration(seconds: 30),
// State widgets
loadingWidget: CircularProgressIndicator(),
successWidget: (data) {
final products = data['products'] as List;
return ProductGrid(products: products);
},
errorWidget: (error) {
return ErrorCard(
message: error,
onRetry: () => setState(() {}),
);
},
// Optional: Empty state
emptyWidget: EmptyState(message: 'No products found'),
// Optional: Callbacks
onSuccess: (data) => print('Loaded ${data.length} products'),
onError: (error) => print('Error: $error'),
)
Using ApiRequest (reusable request config)
Bundle all request parameters into one object — compose it outside the widget tree and reuse across screens:
final productsRequest = ApiRequest(
endpoint: '/api/products',
method: HttpMethod.get,
headers: {'Authorization': 'Bearer $token'},
cacheDuration: Duration(minutes: 5),
);
// Use it directly
ApiWidget(
request: productsRequest,
successWidget: (data) => ProductList(data),
)
// Derive a variant with copyWith
final filteredRequest = productsRequest.copyWith(
endpoint: '/api/products?category=electronics',
);
Injecting a custom HTTP client
Implement BduiHttpClient to swap the network layer — useful for testing or custom HTTP libraries:
class MockHttpClient implements BduiHttpClient {
@override
Future<ApiResponse> get(String url, { ... }) async {
return ApiResponse(statusCode: 200, data: {'products': []});
}
// implement remaining methods...
}
ApiWidget(
endpoint: '/api/products',
httpClient: MockHttpClient(),
successWidget: (data) => ProductList(data),
)
Custom Widget Registration
Extend with your own widgets using SchemaParser.register:
final parser = SchemaParser();
parser.register('ProductCard', (schema, context) {
final props = schema.props ?? {};
return ProductCard(
title: props['title'],
price: props['price'],
imageUrl: props['imageUrl'],
);
});
Use it from backend:
{
"type": "ProductCard",
"props": {
"title": "iPhone 15",
"price": 79999,
"imageUrl": "https://cdn.app.com/iphone15.jpg"
}
}
🎯 Advanced Features
Action Handling
Execute actions from your backend schemas:
{
"type": "navigate",
"route": "/products"
}
Supported actions:
navigate- Navigate to a routepop- Go backreplace- Replace current routepopUntil- Pop to a named route (or root)api- Make API callsshowDialog- Show alert dialogs (supportsonConfirm,onCancel,onDismiss)showSnackBar- Show snackbarsshowBottomSheet- Show modal bottom sheetslaunchUrl- Open a URL (requiresonLaunchUrlcallback)copy- Copy text to clipboardshare- Share text contentsequence- Execute multiple actions in orderconditional- Conditional executioncustom- App-defined custom actions
Caching & Performance
// Enable widget caching
final parser = SchemaParser(enableCache: true);
// Clear cache when needed
parser.clearCache();
// API caching
ApiWidget(
endpoint: '/api/products',
cacheDuration: Duration(minutes: 5), // Cache for 5 minutes
)
Server-Controlled Caching
Let your backend control cache behavior per response:
{
"cachePolicy": "cache",
"cacheTTL": 300,
"ui": {
"type": "Column",
"children": [...]
}
}
Cache policies:
cache- Cache response (default)noCache- Never cache, always fetch freshrefresh- Return cached data, refresh in background (stale-while-revalidate)
Handle background refresh:
ApiWidget(
endpoint: '/api/live-data',
onBackgroundRefresh: (newData) {
// UI automatically updates with fresh data
print('Data refreshed in background!');
},
)
Auto-Retry & Error Handling
ApiWidget(
endpoint: '/api/products',
maxRetries: 3, // Retry up to 3 times
showRetryButton: true, // Show retry button on error
onError: (error) => logError(error),
)
📚 Schema Reference
See the Schema Reference for complete documentation of all 33 widgets, props, actions, and conditions — also available as SCHEMA_REFERENCE.md.
📱 Examples
Check out the example directory for complete samples:
- ApiWidget Examples - Basic & list API calls with caching
- Backend-Driven UI - JSON schema rendering
- Local Schema - Use JSON without API calls
- Conditional Rendering - Platform & theme-based UI
🤝 Contributing
Contributions are welcome! Feel free to open issues or submit pull requests on GitHub.
📄 License
MIT License - see LICENSE file for details.
🌟 Show Your Support
If you like this package, please give it a ⭐ on GitHub!
Built with ❤️ for the Flutter community
Libraries
- backend_driven_ui
- Backend-Driven UI Framework for Flutter



