· Business Owner

Dynamic Drop Down Custom Widget With Add New Element

Widgets & Design

I want to present the user with a drop down list of values from pocketbase. If the user does not see the option, they can click on the "add new" where they can type in their choice and it will save it back to the DB. So far I have not found anything that can help me with this. I want the custom widget to be flexible so when designing my application, I can pass a tableName and some fields so nothing is hard coded. I am getting an error "cannot process parameter config". If anyone can help or have a similar solution that they could share, that would be amazing.

import 'package:pocketbase/pocketbase.dart';

typedef DropdownConfigCallback = void Function(DropdownConfig);

class DropdownConfig {
  final String tableName;
  final String displayFieldName;
  final String ownerIdFieldName;
  final String ownerId;

  DropdownConfig({
    required this.tableName,
    required this.displayFieldName,
    required this.ownerIdFieldName,
    required this.ownerId,
  });
}

class DynamicDropdown extends StatefulWidget {
  final DropdownConfig config;
  final DropdownConfigCallback? configChanged;

  const DynamicDropdown({
    Key? key,
    required this.config,
    this.configChanged,
  }) : super(key: key);

  @override
  _DynamicDropdownState createState() => _DynamicDropdownState();
}

class _DynamicDropdownState extends State<DynamicDropdown> {
  static final pb = PocketBase('https://luminartist.pockethost.io');
  List<dynamic> items = [];
  String? selectedValue;
  bool isLoading = true;

  @override
  void initState() {
    super.initState();
    _loadData();
  }

  @override
  void didUpdateWidget(covariant DynamicDropdown oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.config != widget.config) {
      _loadData();
      if (widget.configChanged != null) {
        widget.configChanged!(widget.config);
      }
    }
  }

  Future<void> _loadData() async {
    setState(() {
      isLoading = true;
    });

    try {
      final records = await pb.collection(widget.config.tableName).getList(
            filter:
                '${widget.config.ownerIdFieldName}="${widget.config.ownerId}"',
          );
      setState(() {
        items = records.items;
        items.add({'id': 'add_new', 'name': 'Add New...'});
        isLoading = false;
      });
    } catch (e) {
      print('Failed to load data: $e');
      setState(() => isLoading = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    return isLoading
        ? const CircularProgressIndicator()
        : DropdownButton<String>(
            value: selectedValue,
            items: items.map<DropdownMenuItem<String>>((dynamic value) {
              final displayText =
                  value[widget.config.displayFieldName]?.toString() ?? '';
              return DropdownMenuItem<String>(
                value: value['id']?.toString(),
                child: Text(
                  displayText,
                  overflow: TextOverflow.ellipsis,
                ),
              );
            }).toList(),
            onChanged: (String? newValue) async {
              if (newValue == 'add_new') {
                await _addNewElement();
              } else {
                setState(() {
                  selectedValue = newValue;
                });
              }
            },
          );
  }

  Future<void> _addNewElement() async {
    final TextEditingController controller = TextEditingController();
    final newItem = await showDialog<Map<String, dynamic>>(
      context: context,
      builder: (context) {
        return AlertDialog(
          title: const Text('Add New Element'),
          content: TextField(
            controller: controller,
            decoration:
                const InputDecoration(hintText: "Enter new element name"),
          ),
          actions: <Widget>[
            TextButton(
              child: const Text('Cancel'),
              onPressed: () {
                Navigator.of(context).pop();
              },
            ),
            TextButton(
              child: const Text('Save'),
              onPressed: () async {
                if (controller.text.isNotEmpty) {
                  try {
                    final newItem =
                        await pb.collection(widget.config.tableName).create(
                      body: {
                        widget.config.displayFieldName: controller.text,
                        widget.config.ownerIdFieldName: widget.config.ownerId,
                      },
                    );
                    Navigator.of(context).pop(newItem);
                  } catch (e) {
                    print('Error saving new item: $e');
                  }
                }
              },
            ),
          ],
        );
      },
    );

    if (newItem != null) {
      setState(() {
        items.insert(items.length - 1, newItem);
        selectedValue = newItem['id'].toString();
      });
    }
  }
}
What have you tried so far?

I have searched this forum for similar solutions. I have created this code but need help to get it working correctly.

Did you check FlutterFlow's Documentation for this topic?
No
4