Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question about issue during dialog close #131

Open
kvo87 opened this issue Jan 5, 2025 · 3 comments
Open

Question about issue during dialog close #131

kvo87 opened this issue Jan 5, 2025 · 3 comments

Comments

@kvo87
Copy link

kvo87 commented Jan 5, 2025

Hi! I'm getting error "This widget has been unmounted, so the State no longer has a context (and should be considered defunct)."
It appears that state is updated earlier then dialog is closed. The error disappears if I set 200ms delay for screenCtrl.setBaseCurrency(value); .

Question: Is it possible to await until pop() function will be completed? or how o avoid this issue?

Here is my code:

final screenState = ref.watch(tripFormScreenCtrlProvider);
final screenCtrl = ref.read(tripFormScreenCtrlProvider.notifier);

SearchChoices.single(
                key: const Key('currencyField'),
                displayClearIcon: false,
                padding: EdgeInsets.symmetric(vertical: 7.h),
                value: screenState.baseCurrency,
                isExpanded: true,
                hint: loc.onboarding_currency_hint,
                searchHint: loc.onboarding_currency_hint,
                closeButton: loc.close,
                searchDelay: 150,
                items: screenState.isLoading
                    ? [
                        DropdownMenuItem(
                          value: screenState.baseCurrency,
                          child: Row(
                            children: [
                              Expanded(
                                child: Text(
                                  screenState.baseCurrency!.toLocalizedString(loc.localeName),
                                  maxLines: 2,
                                  style: const TextStyle(fontSize: 16),
                                ),
                              ),
                            ],
                          ),
                        )
                      ]
                    : currencyList,
                searchFn: (String keyword, items) {
                  List<DropdownMenuItem<Currency>> currencyList = items;
                  List<int> result = [];
                  if (items != null && keyword.isNotEmpty) {
                    keyword.split(" ").forEach((k) {
                      int i = 0;
                      for (final item in currencyList) {
                        if (k.isNotEmpty &&
                            (item.value!
                                .toLocalizedString(loc.localeName)
                                .toLowerCase()
                                .contains(k.toLowerCase()))) {
                          result.add(i);
                        }
                        i++;
                      }
                    });
                  }
                  if (keyword.isEmpty) {
                    result = Iterable<int>.generate(items.length).toList();
                  }
                  return (result);
                },
                displayItem: (item, selected) {
                  return Row(
                    children: [
                      Expanded(
                        key: const Key('currencyListItem'),
                        child: item,
                      ),
                    ],
                  );
                },
                underline: Container(
                  height: 1.h,
                  decoration: BoxDecoration(
                    border: Border(
                      bottom: theme.inputDecorationTheme.enabledBorder!.borderSide,
                    ),
                  ),
                ),
                autovalidateMode: AutovalidateMode.disabled,
                onChanged: (value, onChangeContext, pop) async {                
                  screenCtrl.setBaseCurrency(value);
                },
                onTap: () {
                  SystemChannels.textInput.invokeMethod("TextInput.show");
                },
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.start,
                children: [
                  Text(
                    loc.base_currency,
                    style: TextStyle(fontSize: 12, color: theme.hintColor),
                  ),
                ],
              ),
              SizedBox(height: 15.h),
            ],
          );

// function that updates screenState
  setBaseCurrency(Currency? value) {
    state = state.copyWith(baseCurrency: value);
  }
@lcuis
Copy link
Owner

lcuis commented Jan 5, 2025

Hi @kvo87 , thanks for reaching out with this question.

My first attempt would be with a customization of the searchResultDisplayFn attribute:

searchResultDisplayFn: ({ required List<Tuple3<int, DropdownMenuItem, bool>> itemsToDisplay, required ScrollController scrollController, required bool thumbVisibility, required Widget emptyListWidget, required void Function(int index, dynamic value, bool itemSelected) itemTapped, required Widget Function(DropdownMenuItem item, bool isItemSelected) displayItem, }) { return Expanded( child: Scrollbar( controller: scrollController, thumbVisibility: thumbVisibility, child: itemsToDisplay.length == 0 ? emptyListWidget : ListView.builder( controller: scrollController, itemBuilder: (context, index) { int itemIndex = itemsToDisplay[index].item1; DropdownMenuItem item = itemsToDisplay[index].item2; bool isItemSelected = itemsToDisplay[index].item3; return InkWell( onTap: () { itemTapped( itemIndex, item.value, isItemSelected, ); }, child: displayItem( item, isItemSelected, ), ); }, itemCount: itemsToDisplay.length, ), ),

I would add the delay before the call to itemTapped.

@kvo87
Copy link
Author

kvo87 commented Jan 5, 2025

Hi @lcuis, thank you for the suggestion. As far as I understand Pop() function is called inside itemTapped so delay before this function doesn't affect the issue.

ListView.builder(
    controller: scrollController,
    itemCount: itemsToDisplay.length,
    itemBuilder: (context, index) {
      int itemIndex = itemsToDisplay[index].item1;
      DropdownMenuItem item = itemsToDisplay[index].item2;
      bool isItemSelected = itemsToDisplay[index].item3;
      return InkWell(
        onTap: () {
          Future.delayed(const Duration(milliseconds: 200), () {
            itemTapped( itemIndex, item.value, isItemSelected,
            );
          });
        },
        child: displayItem( item, isItemSelected ),
      );
    },
  ),

Following code works for me but I feel like it is a hack and would like to find better solution.

onChanged: (value, onChangeContext, pop) {
  Future.delayed(const Duration(milliseconds: 200), () {
    screenCtrl.setBaseCurrency(value);
  });
  pop(onChangeContext);
},

@lcuis
Copy link
Owner

lcuis commented Jan 5, 2025

Hi @kvo87 ,

Thanks for your explanations and examples.
Sorry for my misleading answer.

I don't think there is any better option than the one you found at the moment and I agree it would be better to have a better way. Maybe some kind of optional delay before the pop call? I hope this can be implemented one day.

Cheers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants