# Custom Widgets

Custom widgets allow you to extend Digia UI with native Flutter components. This is essential for accessing native platform features, integrating third-party packages, and creating specialized UI components that Digia Studio doesn't provide.

## Overview

Custom widgets bridge the gap between Digia's visual development environment and Flutter's native capabilities. They allow you to:

* Access native platform APIs (camera, GPS, sensors)
* Integrate third-party Flutter packages
* Create performance-critical custom rendering
* Implement complex animations and interactions
* Handle platform-specific behaviors

## When to Use Custom Widgets

### Use Custom Widgets When You Need:

* ✅ **Native Platform Features**: Camera, GPS, device sensors, file system access
* ✅ **Third-party Flutter Packages**: Complex animations, charts, maps, payment SDKs
* ✅ **Performance-Critical Logic**: Custom rendering or complex state management
* ✅ **Missing Digia UI Components**: Specialized widgets not available in Digia Studio
* ✅ **Custom Styling**: Specific design requirements that Digia UI can't achieve
* ✅ **Platform-Specific Behavior**: Different behavior on iOS vs Android

### Use Digia UI Directly For:

* ❌ **Basic UI Elements**: Buttons, text, images, lists (already available in Digia)
* ❌ **Standard Layouts**: Containers, columns, rows, cards
* ❌ **Simple Forms**: Text inputs, checkboxes, dropdowns
* ❌ **Navigation**: Standard app bars, bottom tabs, drawers

## Decision Matrix

| Component Type      | Use Digia UI | Use Custom Widget | Reason                              |
| ------------------- | ------------ | ----------------- | ----------------------------------- |
| Product Card        | ✅            | ❌                 | Standard layout, available in Digia |
| Shopping Cart       | ✅            | ❌                 | List + standard components          |
| Camera Button       | ❌            | ✅                 | Native camera API needed            |
| Payment Form        | ❌            | ✅                 | Third-party SDK integration         |
| Custom Animation    | ❌            | ✅                 | Complex Flutter animations          |
| GPS Location        | ❌            | ✅                 | Native location services            |
| Charts/Graphs       | ❌            | ✅                 | Third-party charting library        |
| File Picker         | ❌            | ✅                 | Native file system access           |
| Status Badges       | ❌            | ✅                 | Custom styling requirements         |
| Progress Indicators | ✅            | ❌                 | Available in Digia UI               |
| Modal Dialogs       | ✅            | ❌                 | Standard modal components           |

## Implementation Pattern

### 1. Props Class

Define a class to handle data passed from Digia Studio:

```dart
class CustomButtonProps {
  final String text;
  final Color backgroundColor;
  final double borderRadius;

  CustomButtonProps({
    required this.text,
    required this.backgroundColor,
    required this.borderRadius,
  });

  static CustomButtonProps fromJson(Map<String, dynamic> json) {
    return CustomButtonProps(
      text: json['text'] as String? ?? 'Button',
      backgroundColor: Color(
        int.parse((json['backgroundColor'] as String? ?? '#FF6B35').replaceFirst('#', '0xff'))
      ),
      borderRadius: (json['borderRadius'] as num?)?.toDouble() ?? 8.0,
    );
  }
}
```

### 2. Widget Class

Create the widget that extends `VirtualLeafStatelessWidget`:

```dart
class CustomButton extends VirtualLeafStatelessWidget<CustomButtonProps> {
  CustomButton({
    required super.props,
    required super.commonProps,
    required super.parent,
    required super.refName,
  });

  @override
  Widget render(RenderPayload payload) {
    return CustomButtonWidget(
      text: props.text,
      backgroundColor: props.backgroundColor,
      borderRadius: props.borderRadius,
      onPressed: () => _handlePress(),
    );
  }

  void _handlePress() {
    // Send message to Digia UI
    DUIAppState().postMessage(
      'button_pressed',
      {'buttonId': refName, 'timestamp': DateTime.now().toIso8601String()},
    );
  }
}
```

### 3. Widget Implementation

The actual Flutter widget implementation:

```dart
class CustomButtonWidget extends StatelessWidget {
  final String text;
  final Color backgroundColor;
  final double borderRadius;
  final VoidCallback onPressed;

  const CustomButtonWidget({
    super.key,
    required this.text,
    required this.backgroundColor,
    required this.borderRadius,
    required this.onPressed,
  });

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: onPressed,
      style: ElevatedButton.styleFrom(
        backgroundColor: backgroundColor,
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(borderRadius),
        ),
        padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
      ),
      child: Text(
        text,
        style: const TextStyle(
          color: Colors.white,
          fontSize: 16,
          fontWeight: FontWeight.w600,
        ),
      ),
    );
  }
}
```

### 4. Registration

Register the widget with Digia UI:

```dart
void registerCustomButton() {
  DUIFactory().registerWidget<CustomButtonProps>(
    'custom/button-v1', // Unique ID used in Digia Studio
    CustomButtonProps.fromJson,
    (props, childGroups) => CustomButton(
      props: props,
      commonProps: null,
      parent: null,
      refName: 'custom_button',
    ),
  );
}
```

### 5. App Initialization

#### Method 1: Using DigiaUIAppBuilder (Recommended)

Register widgets after DigiaUIAppBuilder initialization:

```dart
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  runApp(DigiaUIAppBuilder(
    options: DigiaUIOptions(
      accessKey: 'YOUR_ACCESS_KEY',
      flavor: Flavor.debug(),
    ),
    builder: (context, status) {
      // Register custom widgets AFTER DigiaUIAppBuilder
      if (status.isReady) {
        registerCustomButton();
        registerOtherWidgets();
      }
      
      if (status.hasError) {
        return ErrorScreen(error: status.error);
      }
      
      if (status.isLoading) {
        return LoadingScreen();
      }
      
      return MyApp();
    },
  ));
}
```

#### Method 2: Using DigiaUIApp with Manual Registration

For more control over initialization timing:

```dart
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // Pre-register custom widgets before Digia UI initialization
  registerCustomButton();
  registerOtherWidgets();
  
  // Initialize Digia UI manually
  final digiaUI = await DigiaUI.initialize(
    DigiaUIOptions(
      accessKey: 'YOUR_ACCESS_KEY',
      flavor: Flavor.debug(),
    ),
  );
  
  runApp(DigiaUIApp(
    digiaUI: digiaUI,
    home: MyApp(),
  ));
}
```

## Usage in Digia Studio

Once registered, widgets appear in Digia Studio's component palette:

```json
{
  "widget": "custom/button-v1",
  "props": {
    "text": "Click Me",
    "backgroundColor": "#FF6B35",
    "borderRadius": 12
  },
  "events": {
    "onPressed": {
      "action": "postMessage",
      "channel": "custom_button_clicked",
      "params": {
        "buttonText": "{{widget.text}}",
        "page": "{{page.id}}"
      }
    }
  }
}
```

## Advanced Patterns

### Stateful Widgets

For widgets that need internal state:

```dart
class CounterWidget extends VirtualLeafStatefulWidget<CounterProps> {
  CounterWidget({
    required super.props,
    required super.commonProps,
    required super.parent,
    required super.refName,
  });

  @override
  State<CounterWidget> createState() => _CounterWidgetState();
}

class _CounterWidgetState extends State<CounterWidget> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return CounterWidgetImpl(
      count: _count,
      onIncrement: () => setState(() => _count++),
    );
  }
}
```

### Integration with Analytics

Send analytics events from custom widgets:

```dart
void _handleInteraction() {
  // Log to analytics
  DUIAppState().postMessage(
    'analytics_event',
    {
      'name': 'custom_widget_interaction',
      'params': {
        'widget_type': 'button',
        'widget_id': refName,
        'timestamp': DateTime.now().toIso8601String(),
      }
    },
  );

  // Handle the interaction
  // ... widget-specific logic
}
```

### Accessing Native Services

Use custom widgets to access device features:

```dart
class CameraButton extends VirtualLeafStatelessWidget<CameraButtonProps> {
  // ... constructor

  @override
  Widget render(RenderPayload payload) {
    return ElevatedButton(
      onPressed: () async {
        // Access native camera
        final image = await ImagePicker().pickImage(source: ImageSource.camera);
        if (image != null) {
          // Send image data back to Digia UI
          DUIAppState().postMessage(
            'camera_image_captured',
            {'imagePath': image.path},
          );
        }
      },
      child: const Text('Take Photo'),
    );
  }
}
```

## Best Practices

### 1. Keep Widgets Focused

* Each widget should have a single responsibility
* Avoid complex state management within widgets
* Use props for configuration, not behavior

### 2. Handle Props Safely

* Always validate props in `fromJson`
* Provide sensible defaults
* Handle null/undefined values gracefully

### 3. Use Proper Naming

* Widget IDs should be unique and descriptive
* Use kebab-case for IDs: `custom/my-widget-name`
* Follow Flutter naming conventions for classes

### 4. Test Thoroughly

* Test in both native and Digia contexts
* Verify props parsing works correctly
* Test event handling through message bus

### 5. Document Your Widgets

* Include clear prop descriptions
* Provide usage examples
* Document event behaviors

## Common Patterns

### Payment Integration

```dart
class PaymentButton extends VirtualLeafStatelessWidget<PaymentButtonProps> {
  @override
  Widget render(RenderPayload payload) {
    return PaymentSDKButton(
      amount: props.amount,
      onSuccess: () => DUIAppState().postMessage('payment_success', {...}),
      onFailure: () => DUIAppState().postMessage('payment_failed', {...}),
    );
  }
}
```

### Map Integration

```dart
class MapWidget extends VirtualLeafStatelessWidget<MapProps> {
  @override
  Widget render(RenderPayload payload) {
    return GoogleMap(
      initialCameraPosition: CameraPosition(
        target: LatLng(props.latitude, props.longitude),
        zoom: props.zoom,
      ),
      markers: _createMarkers(),
    );
  }
}
```

### File Picker

```dart
class FilePickerWidget extends VirtualLeafStatelessWidget<FilePickerProps> {
  @override
  Widget render(RenderPayload payload) {
    return ElevatedButton(
      onPressed: () async {
        FilePickerResult? result = await FilePicker.platform.pickFiles();
        if (result != null) {
          DUIAppState().postMessage('file_selected', {
            'filePath': result.files.single.path,
            'fileName': result.files.single.name,
          });
        }
      },
      child: Text(props.buttonText),
    );
  }
}
```

## Troubleshooting

### Widget Not Appearing in Digia Studio

* Check that registration is called before Digia UI initialization
* Verify the widget ID matches exactly in Digia Studio
* Ensure props parsing doesn't throw exceptions

### Props Not Working

* Check `fromJson` method for correct parsing
* Verify prop names match Digia Studio configuration
* Add debug prints to see what props are received

### Events Not Firing

* Ensure message bus is properly initialized
* Check channel names match between widget and handler
* Verify Digia Studio event configuration

## Example: Complete Custom Widget

See the [ProductHub demo](https://github.com/Digia-Technology-Private-Limited/product_hub) for a complete working example of custom widget implementation, including:

* `DeliveryTypeStatus` widget with custom styling
* Analytics integration
* Message bus communication
* Registration and usage patterns


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.digia.tech/extend-and-ship/custom-widgets.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
