Advanced: Custom Widgets
While Devity provides a library of standard Widgets, you will often need UI elements specific to your application's design language or functionality. Devity's extensibility allows you to create custom native Flutter widgets and render them based on your server-driven Specs.
Why Use Custom Widgets?
- Unique UI/UX: Implement components that are not part of the standard library (e.g., custom charts, branded cards, complex interactive elements).
- Platform Integration: Create widgets that deeply integrate with platform-specific features or native SDKs.
- Performance Optimization: Build highly optimized widgets for specific use cases where standard compositions might be insufficient.
- Encapsulation: Encapsulate complex UI logic and state within a reusable Flutter widget, controlled via attributes from the Spec.
Process Overview
- Create: Build your custom widget in your Flutter application codebase like any other Flutter widget.
- Define Interface: Determine which attributes of your widget need to be configurable from the server Spec.
- Build Builder: Create a builder function that takes the Spec definition (attributes, context) and returns an instance of your custom Flutter widget.
- Register: Register your builder function with the Devity SDK, associating it with a unique
widgetType
string. - Use in Spec: Use the registered
widgetType
within your Specs in the Devity Console, configuring its attributes as needed.
1. Creating the Flutter Widget
Build your custom widget using standard Flutter practices. It can be stateful or stateless.
// Example: A simple custom widget
import 'package:flutter/material.dart';
class MyCustomRatingWidget extends StatelessWidget {
final int maxRating;
final double currentRating;
final Color starColor;
const MyCustomRatingWidget({
Key? key,
this.maxRating = 5,
required this.currentRating,
this.starColor = Colors.orange,
}) : super(key: key);
@override
Widget build(BuildContext context) {
// Your widget build logic here (e.g., displaying stars)
return Row(
mainAxisSize: MainAxisSize.min,
children: List.generate(maxRating, (index) {
return Icon(
index < currentRating ? Icons.star : Icons.star_border,
color: starColor,
);
}),
);
}
}
2. Defining the Builder Function
The Devity SDK needs a way to create an instance of your widget when it encounters the corresponding widgetType
in the Spec. You provide a builder function for this.
(Note: The exact signature of the builder function will depend on the final SDK API design. It will likely receive the widget's definition from the Spec and potentially other context.)
// Hypothetical Builder Function Signature
// typedef CustomWidgetBuilder = Widget Function(BuildContext context, Map<String, dynamic> attributes, Map<String, dynamic>? style);
Widget myCustomRatingWidgetBuilder(BuildContext context, Map<String, dynamic> attributes, Map<String, dynamic>? style) {
// Extract attributes from the spec definition, providing defaults
final int maxRating = attributes['maxRating'] as int? ?? 5;
// IMPORTANT: Handle potential data types and nulls carefully!
final double currentRating = (attributes['currentRating'] as num?)?.toDouble() ?? 0.0;
final String colorString = attributes['starColor'] as String? ?? '#FFA500'; // Default orange
// TODO: Convert colorString to Color object
final Color starColor = Color(int.parse(colorString.substring(1, 7), radix: 16) + 0xFF000000);
// TODO: Apply common styling properties from the 'style' map if needed
return MyCustomRatingWidget(
maxRating: maxRating,
currentRating: currentRating,
starColor: starColor,
);
}
Key Considerations for Builders:
- Attribute Parsing: Carefully parse attributes from the map, handle potential type mismatches (data from JSON might not be the exact Dart type needed), and provide sensible defaults.
- Error Handling: Implement robust error handling if required attributes are missing or invalid.
- Styling: Decide how to handle common
style
properties passed from the Spec (padding, margin, etc.). You might wrap your custom widget in aContainer
or use the properties directly if applicable. - Context: Utilize the
BuildContext
if needed.
3. Registering the Widget
Register your builder function with the SDK during initialization, typically in main.dart
, associating it with the unique widgetType
string you will use in your Specs.
// In main.dart, after DevitySDK.initialize
DevitySDK.registerCustomWidget(
'MyRatingWidget', // The unique widgetType used in the Spec
myCustomRatingWidgetBuilder, // The builder function
);
(See SDK API Reference for the exact registration method)
4. Using in the Spec
Now, in the Devity Console, you can add a widget and set its widgetType
to "MyRatingWidget"
. The attributes you defined (e.g., maxRating
, currentRating
, starColor
) will be available in the Attribute Inspector for configuration.
// Example usage within a Spec's children array
{
"id": "product_rating",
"type": "Widget",
"widgetType": "MyRatingWidget", // Registered custom type
"attributes": {
"currentRating": "{{api.productDetails.response.body.averageRating}}", // Dynamic binding
"maxRating": 5,
"starColor": "#FFC107" // Amber color
}
}
The SDK will now use your registered myCustomRatingWidgetBuilder
function to render MyCustomRatingWidget
whenever it encounters "widgetType": "MyRatingWidget"
in the JSON Spec.