Closed twfungi closed 2 months ago
Hi @twfungi, Thanks for filing the issue. Can you please share a minimal reproducible code sample for me to investigate.
Thanks
Performing hot restart... Waiting for connection from debug service on Chrome... Restarted application in 184ms. [log] [onSuggestionTap] selected Queen
======== Exception caught by widgets library ======================================================= The following IndexError was thrown building RawGestureDetector(state: RawGestureDetectorState#4f3ec(gestures: [tap])): RangeError (index): Index out of range: index must not be negative: -1
The relevant error-causing widget was:
GestureDetector GestureDetector:file:///Users/.../AndroidStudioProjects/searchfield_issue1/lib/main.dart:98:15
When the exception was thrown, this was the stack:
dart-sdk/lib/_internal/js_dev_runtime/private/ddcruntime/errors.dart 296:3 throw
dart-sdk/lib/_internal/js_dev_runtime/private/js_array.dart 592:7 _get]
packages/searchfield/src/searchfield.dart 669:46
import 'package:flutter/material.dart';
import 'package:searchfield/searchfield.dart';
import 'dart:developer' as d;
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with AutomaticKeepAliveClientMixin {
@override
bool wantKeepAlive = true;
final focus = FocusNode();
final _controller = TextEditingController();
bool submitted = false;
var suggestions = <String>["Alpha", "Beta", "Charlie", "Delta", "Echo", "Fox", "Gamma", "Hello", "Indigo"];
var defaultSuggestions = <String>[
"Alpha",
"Beta",
"Charlie",
"Delta",
"Echo",
"Fox",
"Gamma",
"Hello",
"Indigo",
"Jimmy",
"Kitty",
"Lemon",
"Monkey",
"Nimo",
"Oxford",
"Peter",
"Queen",
"Rabbit",
"Snake",
"Timothy",
"Uncle",
"Victor",
"Whiskey",
"Xray",
"Yellow",
"Zullu"
];
@override
void initState() {
super.initState();
suggestions = getSuggestions();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
super.build(context);
Widget searchChild(x) => Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 12),
child: Text(x, style: Theme.of(context).textTheme.bodyMedium!),
);
SuggestionDecoration suggestionDecoration = SuggestionDecoration(
elevation: 16.0,
color: Colors.white54,
borderRadius: BorderRadius.circular(24),
);
return Scaffold(
appBar: AppBar(centerTitle: true, automaticallyImplyLeading: true, title: const Text('test')),
body: GestureDetector(
onTap: () {
FocusScope.of(context).unfocus();
},
child: SizedBox(
height: double.infinity,
width: double.infinity,
child: Column(
children: [
SizedBox(
width: 400,
child: SearchField(
controller: _controller,
onSearchTextChanged: (query) {
List<String> filter;
query = query.trim();
if (query.isEmpty) {
filter = suggestions = getSuggestions();
} else {
filter = defaultSuggestions
.where((element) => element.toLowerCase().contains(query.toLowerCase()))
.toList();
}
return filter.map((e) => SearchFieldListItem<String>(e, child: searchChild(e))).toList();
},
onSubmit: (str) async {
submitted = true;
d.log('[onSubmit] $str');
saveSuggestion(str.trim());
if (mounted) {
await Navigator.push(
context, MaterialPageRoute(builder: (context) => const Page2(title: 'onSubmit')));
}
},
initialValue: null,
onTap: () async {
suggestions = getSuggestions();
setState(() {});
},
showEmpty: false,
emptyWidget: Container(
decoration: suggestionDecoration, height: 200, child: const Center(child: Text('Empty'))),
key: const Key('searchfield'),
dynamicHeight: true,
maxSuggestionBoxHeight: 300,
scrollbarDecoration: ScrollbarDecoration(minThumbLength: 30, thickness: 10),
onTapOutside: null,
suggestionDirection: SuggestionDirection.down,
suggestionStyle: Theme.of(context).textTheme.bodyMedium!,
searchInputDecoration: SearchInputDecoration(
searchStyle: Theme.of(context).textTheme.bodyMedium!,
prefixIcon: const Icon(Icons.search),
fillColor: Colors.white38,
suffixIcon: IconButton(
onPressed: _controller.clear,
icon: const Icon(Icons.clear),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(24),
borderSide: const BorderSide(
width: 1,
color: Colors.grey,
style: BorderStyle.solid,
),
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(24),
borderSide: const BorderSide(
width: 1,
color: Colors.black,
style: BorderStyle.solid,
),
),
filled: true,
contentPadding: const EdgeInsets.symmetric(
horizontal: 20,
),
),
suggestionsDecoration: suggestionDecoration,
suggestions:
suggestions.map((e) => SearchFieldListItem<String>(e, child: searchChild(e))).toList(),
focusNode: focus,
suggestionState: Suggestion.expand,
onSuggestionTap: (SearchFieldListItem<String> x) {
submitted = false;
focus.unfocus();
d.log('[onSuggestionTap] selected ${x.searchKey}');
saveSuggestion(x.searchKey);
Future.delayed(const Duration(milliseconds: 200), () async {
if (!submitted) {
if (mounted) {
await Navigator.push(context,
MaterialPageRoute(builder: (context) => const Page2(title: 'onSuggestionTap')));
}
}
});
},
)),
],
))));
}
List<String> getSuggestions() {
return suggestions;
}
void saveSuggestion(String item) {
if (item.isNotEmpty) {
suggestions.removeWhere((e) => e == item);
suggestions.insert(0, item);
if (suggestions.length > 10) {
suggestions.removeLast();
}
}
}
}
class Page2 extends StatefulWidget {
const Page2({super.key, required this.title});
final String title;
@override
State<Page2> createState() => _Page2State();
}
class _Page2State extends State<Page2> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(centerTitle: true, automaticallyImplyLeading: true, title: Text(widget.title)),
body: Center(child: Text('This is the Page2 from [${widget.title}]')));
}
}
I'm getting same issue here. Please check it
Fixed and released in searchfield: ^1.1.2
Describe the bug Exception: RangeError (index): Index out of range: index must not be negative: -1 This issue exists for a while.
To Reproduce Steps to reproduce the behavior:
[ X] By clicking this checkbox, I confirm I am using the latest version of the package found on pub.dev/searchfield 1.1.1
Expected behavior No assertion failure
Actual behavior Assertion failure
Screenshots ======== Exception caught by widgets library ======================================================= The following IndexError was thrown building RawGestureDetector(state: RawGestureDetectorState#b6d79(gestures: [tap])): RangeError (index): Index out of range: index must not be negative: -1
The relevant error-causing widget was: ... When the exception was thrown, this was the stack: dart-sdk/lib/_internal/js_dev_runtime/private/ddcruntime/errors.dart 296:3 throw dart-sdk/lib/_internal/js_dev_runtime/private/js_array.dart 592:7 _get] packages/searchfield/src/searchfield.dart 668:46
dart-sdk/lib/_internal/js_dev_runtime/private/js_array.dart 624:15 indexWhere]
packages/searchfield/src/searchfield.dart 667:39 didUpdateWidget
packages/flutter/src/widgets/framework.dart 5789:55 update
packages/flutter/src/widgets/framework.dart 3941:14 updateChild
packages/flutter/src/widgets/framework.dart 6907:14 update
packages/flutter/src/widgets/framework.dart 3941:14 updateChild
packages/flutter/src/widgets/framework.dart 4090:32 updateChildren
Without knowing the root case, my temporary workaround in searchfield.dart :666 Adding a few checks can avoid the exception, and I haven't observed any side effect.