# 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
