maheshj01 / searchfield

A highly customizable simple and easy to use flutter Widget to add a searchfield to your Flutter Application.This Widget allows you to search and select from list of suggestions.
MIT License
84 stars 63 forks source link

Exception: Looking up a deactivated widget's ancestor is unsafe when using searchfield #162

Closed pavkoccino closed 3 months ago

pavkoccino commented 3 months ago

Describe the bug There seems to be an issue in your ListView widget (see specifically this line in stack _SFListviewState.build.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:searchfield/src/listview.dart:167:30)) when I am scrolling through the list and then typing more letters. I can reliably reproduce it.

Stack:

======== Exception caught by scheduler library =====================================================
The following assertion was thrown during a scheduler callback:
Looking up a deactivated widget's ancestor is unsafe.

At this point the state of the widget's element tree is no longer stable.

To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method.

When the exception was thrown, this was the stack: 
#0      Element._debugCheckStateIsActiveForAncestorLookup.<anonymous closure> (package:flutter/src/widgets/framework.dart:4738:9)
#1      Element._debugCheckStateIsActiveForAncestorLookup (package:flutter/src/widgets/framework.dart:4752:6)
#2      Element.getElementForInheritedWidgetOfExactType (package:flutter/src/widgets/framework.dart:4787:12)
#3      Scrollable.maybeOf (package:flutter/src/widgets/scrollable.dart:347:41)
#4      Scrollable.ensureVisible (package:flutter/src/widgets/scrollable.dart:472:46)
#5      _SFListviewState.build.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:searchfield/src/listview.dart:167:30)
#6      SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1386:15)
#7      SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1322:11)
#8      SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:1169:5)
#9      _invoke (dart:ui/hooks.dart:312:13)
#10     PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:399:5)
#11     _drawFrame (dart:ui/hooks.dart:283:31)

To Reproduce Steps to reproduce the behavior:

  1. Use attached custom widget in some of your project.
  2. Click on the search field to view suggestions and prepare for typing.
  3. Scroll all the way down through the suggestion list.
  4. Start typing for example "as"
  5. See the exception.

[✅] By clicking this checkbox, I confirm I am using the latest version of the package found on pub.dev/searchfield - YES, I am using version 1.0.8

Expected behavior No exception when scrolling and typing more letters into the search.

Actual behavior Mentioned exception occurs.

Use the following widget in some of your project. I tried to cut out everything unimportant to be still able to reproduce it. Code sample

Show code sample ```dart import 'package:flutter/material.dart'; import 'package:searchfield/searchfield.dart'; /// Handles allergies and medicaments therefore is a bit more complex class CustomSearchField extends StatefulWidget { const CustomSearchField(); @override State createState() => _CustomSearchFieldState(); } class _CustomSearchFieldState extends State { late final List searchableItemsList; late final List selectedItemsList; late final TextEditingController textController; @override void initState() { super.initState(); searchableItemsList = [ 'Aspirin', 'Ibuprofen', 'Paracetamol', 'Cetirizine', 'Loratadine', 'Amoxicillin', 'Azithromycin', 'Ciprofloxacin', 'Metformin', 'Lisinopril', 'Amlodipine', 'Simvastatin', 'Omeprazole', 'Levothyroxine', 'Metoprolol', 'Pantoprazole', 'Prednisone', 'Gabapentin', 'Montelukast', 'Albuterol', 'Furosemide', 'Hydrochlorothiazide', 'Losartan', 'Zolpidem', 'Cyclobenzaprine', 'Tramadol', 'Clopidogrel', 'Sertraline', 'Escitalopram', 'Duloxetine', 'Venlafaxine', 'Trazodone', 'Bupropion', 'Quetiapine', 'Risperidone', 'Olanzapine', 'Aripiprazole', 'Lamotrigine', 'Levetiracetam', 'Topiramate', 'Carbamazepine', 'Valproate', 'Lithium', 'Haloperidol', 'Chlorpromazine', 'Perphenazine', 'Fluphenazine', 'Thiothixene', 'Ziprasidone', 'Paliperidone', 'Lurasidone', 'Asenapine', 'Iloperidone', 'Loxapine', 'Pimozide', 'Tetrabenazine', 'Clonazepam', 'Lorazepam', 'Diazepam', 'Alprazolam', 'Midazolam', 'Chlordiazepoxide', 'Temazepam', 'Oxazepam', 'Buspirone', 'Propranolol', 'Atenolol', 'Bisoprolol', 'Nadolol', 'Pindolol', 'Timolol', 'Acebutolol', 'Betaxolol', 'Esmolol', 'Metoprolol succinate', 'Metoprolol tartrate', 'Carvedilol', 'Nebivolol', 'Sotalol', 'Amiodarone', 'Dronedarone', 'Flecainide', 'Propafenone', 'Quinidine', 'Disopyramide', 'Procainamide', 'Lidocaine', 'Mexiletine', 'Phenytoin', 'Diltiazem', 'Verapamil', 'Nifedipine', 'Amlodipine', 'Felodipine', 'Nicardipine', 'Isradipine', 'Nisoldipine', 'Cilnidipine', 'Nimodipine', 'Bepridil', 'Ranolazine', ]; textController = TextEditingController(); selectedItemsList = []; } @override void dispose() { textController.dispose(); super.dispose(); } Widget _buildTrailingLabel({required dynamic item}) { return const Text('custom'); // TODO localize } Widget _getSuggestionTextWidget({required String text, required dynamic item}) { return Padding( padding: const EdgeInsets.all(8.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Flexible( child: Tooltip( message: text, child: Text( text, overflow: TextOverflow.ellipsis, ), ), ), Container( padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1), decoration: BoxDecoration( color: Colors.green.withOpacity(0.1), // TODO differentiate for custom borderRadius: const BorderRadius.all(Radius.circular(4)), ), child: _buildTrailingLabel(item: item), ), ], ), ); } SearchFieldListItem _createSearchFieldListItem(dynamic item) { final String itemName = item as String; Widget suggestionText; suggestionText = _getSuggestionTextWidget(text: itemName, item: item); return SearchFieldListItem(itemName, item: item, child: suggestionText); } void removeItem({required dynamic item}) { setState(() { selectedItemsList.remove(item); }); } @override Widget build(BuildContext context) { return SearchField( suggestionAction: SuggestionAction.unfocus, controller: textController, searchInputDecoration: const InputDecoration( hintText: 'Search', // TODO localize ), suggestionsDecoration: SuggestionDecoration( selectionColor: Colors.transparent, // Important so there is no grey shadow ), onSuggestionTap: (tappedItem) { setState(() { textController.clear(); selectedItemsList.add(tappedItem.item ?? tappedItem.searchKey); }); }, suggestions: searchableItemsList.map(_createSearchFieldListItem).toList(), ); } } ```

Additional context I checked other issues and haven't found anything similar. I am using version 1.0.8 See the attached video

https://github.com/user-attachments/assets/2466d4a8-710a-4236-ada2-a43430f4748a

maheshj01 commented 3 months ago

Hi @pavkoccino, Thanks for filing the detailed issue , it is really helpful, I will investigate and get back on this.

pavkoccino commented 3 months ago

Thank you, luckily this exception is not bothering me in release as it is "invisible", but during development it is annoying and should not happen.

maheshj01 commented 3 months ago

Self Notes:

This issue is when trying to scroll to a widget that is no longer in the widget tree, Since the listview lazily loads the widgets

if (widget.selected == index) {
      SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
        if (mounted) {
          Scrollable.ensureVisible(context,
              alignment: 0.1, duration: Duration(milliseconds: 300));
          }
        });
      }
maheshj01 commented 3 months ago

@pavkoccino fixed in latest release v1.0.9

pavkoccino commented 3 months ago

Thanks for fast fix! Today I updated to 1.0.9 and can confirm that the issue does not happen anymore.