Flutter DropdownButton Dynamic Default Value Error

50 Views Asked by At

I am working on an open source project called Eleutherios and keep getting this error message:

Another exception was thrown: There should be exactly one item with [DropdownButton]'s value: ad7c17409ab11f249329c5528be5c5f7.

I am populating the dropdownButton from firebase and using initState to prefetch the default registrantId of the selected registrant for the end user and storing it in dropdownValue.

class _ViewForumScreenState extends ConsumerState<ViewForumScreen> {
    final messageController = TextEditingController();
    Registrant? selectedRegistrant;
    String? dropdownValue;
    var tapPosition;

    getSelectedRegistrant() async {
      final user = ref.read(userProvider)!;

      selectedRegistrant = await ref
          .read(getUserSelectedRegistrantProvider2(
              Tuple2(widget.forumId, user.uid)))
          .first;

      setState(() {
        dropdownValue = selectedRegistrant!.registrantId;
      });
    }

    @override
    void initState() {
      super.initState();
        WidgetsBinding.instance.addPostFrameCallback((_) {
          getSelectedRegistrant();
        }
      );
    }

Then using dropdownValue as the selected value for the DropdownButton.

Expanded(
    flex: 41,
    child: ref
        .watch(getUserRegistrantsProvider(
            Tuple2(widget.forumId, user.uid)))
        .when(
          data: (registrants) {
            return DropdownButtonHideUnderline(
              child: ButtonTheme(
                alignedDropdown: true,
                child: DropdownButton(
                  isDense: true,
                  value: dropdownValue,
                  onChanged: (String? registrantId) {
                    if (registrantId is String) {
                      changeSelectedRegistrant(
                          registrantId);
                    }
                  },
                  // *********************************
                  // items
                  // *********************************
                  items: registrants
                      .map<DropdownMenuItem<String>>(
                          (Registrant registrant) {
                            return ref
                                .watch(getServiceByIdProvider(
                                    registrant.serviceId))
                                .when(data: (service) {
                              return DropdownMenuItem<String>(
                                value: registrant.registrantId,
                                child: Row(children: [
                                  service!.image ==
                                          Constants
                                              .avatarDefault
                                      ? CircleAvatar(
                                          backgroundImage: Image
                                                  .asset(service
                                                      .image)
                                              .image,
                                          radius: 11,
                                        )
                                      : CircleAvatar(
                                          backgroundImage:
                                              NetworkImage(
                                                  service
                                                      .image),
                                          radius: 11,
                                        ),
                                  const SizedBox(
                                    width: 8,
                                  ),
                                  service.title.length > 12
                                      ? Text(
                                          '${service.title.substring(0, 12)}...',
                                          style:
                                              const TextStyle(
                                            fontSize: 14,
                                          ),
                                        )
                                      : Text(
                                          service.title,
                                          style:
                                              const TextStyle(
                                            fontSize: 14,
                                          ),
                                        ),
                                ]),
                              );
                            },
                            // *********************************
                            // error
                            // *********************************
                            error: (error, stackTrace) {
                              return DropdownMenuItem<String>(
                                value: error.toString(),
                                child: Text(error.toString()),
                              );
                            },
                            // *********************************
                            // loading
                            // *********************************
                            loading: () {
                              return const DropdownMenuItem<
                                  String>(
                                value: 'Loader',
                                child: Loader(),
                              );
                    });
                  }).toList(),
                ),
              ),
            );
          },
          error: (error, stackTrace) =>
              ErrorText(error: error.toString()),
          loading: () => const Loader(),
        ),
  ),

It works, but not without the flutter page flashing red for a split second with the aforementioned error message.

What is bizarre is the error only occurs when I navigate to the View Forum page from a Drawer (side panel) page.

Navigate to View Forum page from Drawer (Side Panel) page

In the above image you can see me opening the side draw and click on the Physical Examination Forum link.

View Forum Error Result

In the above image you can see I get the error message.

If I navigate to the View Forum page by clicking on the view button from the Forum Details page the error doesn't occur???

Navigate to View Forum Page from Forum Details page

In the above image you can see the Forum Details page before I click on the View button.

View Forum Success Result

And here's the result with no error and everything is working fine.

Has anybody encountered this problem before and got a work around or is there something I am missing?

Cheers!

Code for the List Draw page:

https://github.com/aletheon/eleutherios-flutter/blob/main/lib/features/home/drawers/list_drawer.dart

Code for the Forum Details page:

https://github.com/aletheon/eleutherios-flutter/blob/main/lib/features/forum/screens/forum_screen.dart

Code for the View Forum page:

https://github.com/aletheon/eleutherios-flutter/blob/main/lib/features/forum/screens/view_forum_screen.dart

Entire source code:

https://github.com/aletheon/eleutherios-flutter

1

There are 1 best solutions below

0
Rob Kara On

The problem is with this block of code.

// *********************************
// items
// *********************************
items: registrants.map<DropdownMenuItem<String>>((Registrant registrant) {
  return ref
    .watch(getServiceByIdProvider(registrant.serviceId))
    .when(data: (service) {
      return DropdownMenuItem<String>(
        value: registrant.registrantId,
        child: Row(children: [...

I'm using the map function to iterate through the registrants list and then immediately calling another provider within that map function to fetch the associated service belonging to the registrant:

return ref.watch(getServiceByIdProvider(registrant.serviceId))

This means the DropdownButton items is not being populated before the comparison is made because getServiceByIdProvider hasn't returned yet and thus the error occurs.

So, the answer became glaringly obvious, force the items to be populated before doing the comparison and the best place to do that was the DropdownMenuItem's child property.

Like so:

// *********************************
// items
// *********************************
items: registrants.map<DropdownMenuItem<String>>((Registrant registrant) {
  return DropdownMenuItem<String>(
    value: registrant.registrantId,
    child: ref
        .watch(getServiceByIdProvider(registrant.serviceId))
        .when(data: (service) {...

Now the DropdownButton items has to wait until all of the getServiceByIdProvider calls have been made before it does the comparison.

And viola, no more error!

A rookie mistake I know, but hey, live and learn right.

:-)