Skip to content

Commit

Permalink
Merge pull request #163 from maheshj01/fix-162
Browse files Browse the repository at this point in the history
Fix deactivate widget ancestor is unsafe during scroll to view
  • Loading branch information
maheshj01 authored Aug 5, 2024
2 parents efd99ef + c61869c commit 70b870d
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 62 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
#### [1.0.9] - August 05, 2024

- renamed `dynamicHeightItem` to `dynamicHeight`
- Fix exception on scroll (debug) [Issue #162](https://github.com/maheshj01/searchfield/issues/162)

#### [1.0.8] - July 23, 2024

- Fixed Regression: Broke basic functionality to search [hotfix #161](https://github.com/maheshj01/searchfield/pull/161)
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# [searchfield: ^1.0.8](https://pub.dev/packages/searchfield)
# [searchfield: ^1.0.9](https://pub.dev/packages/searchfield)

<a href="https://github.com/maheshj01/searchfield" rel="noopener" target="_blank"><img src="https://img.shields.io/badge/platform-flutter-ff69b4.svg" alt="Flutter Platform Badge"></a>
<a href="https://pub.dev/packages/searchfield"><img src="https://img.shields.io/pub/v/searchfield.svg" alt="Pub"></a>
Expand Down Expand Up @@ -273,7 +273,7 @@ The position of suggestions is dynamic based on the space available for the sugg
- `autoValidateMode`: Used to enable/disable this form field auto validation and update its error text.defaults to `AutoValidateMode.disabled`
- `controller`: TextEditing Controller to interact with the searchfield.
- `comparator` property to filter out the suggestions with a custom logic (Comparator is deprecated Use `onSearchTextChanged` instead).
- `dynamicHeightItem`: Set to true to opt-in to dynamic height, defaults to false (`itemHeight : 51`)
- `dynamicHeight`: Set to true to opt-in to dynamic height, defaults to false (`itemHeight : 51`)
- `emptyWidget`: Custom Widget to show when search returns empty Results or when `showEmpty` is true. (defaults to `SizedBox.shrink`)
- `enabled`: Defines whether to enable the searchfield defaults to `true`
- `focusNode` : FocusNode to interact with the searchfield.
Expand All @@ -284,7 +284,7 @@ The position of suggestions is dynamic based on the space available for the sugg
- `inputFormatters`: Input Formatter for SearchField
- `itemHeight` : height of each suggestion Item, (defaults to 51.0).
- `marginColor` : Color for the margin between the suggestions.
- `maxSuggestionBoxHeight`: Specifies a maximum height for the suggestion box when `dynamicHeightItem` is set to `true`.
- `maxSuggestionBoxHeight`: Specifies a maximum height for the suggestion box when `dynamicHeight` is set to `true`.
- `maxSuggestionsInViewPort` : The max number of suggestions that can be shown in a viewport.
- `offset` : suggestion List offset from the searchfield, The top left corner of the searchfield is the origin (0,0).
- `onOutSideTap` : callback when the user taps outside the searchfield.
Expand Down
2 changes: 1 addition & 1 deletion example/lib/dynamic_height.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class _DynamicHeightExampleState extends State<DynamicHeightExample> {
return Padding(
padding: const EdgeInsets.all(8.0),
child: SearchField<UserModel>(
dynamicHeightItem: true,
dynamicHeight: true,
// maxSuggestionBoxHeight: MediaQuery.of(context).size.height * 0.8,
hint: 'Search for users dynamic height',
suggestionsDecoration: SuggestionDecoration(
Expand Down
2 changes: 1 addition & 1 deletion example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ class _SearchFieldSampleState extends State<SearchFieldSample> {
),
SearchField(
hint: 'Basic SearchField',
dynamicHeightItem: true,
dynamicHeight: true,
maxSuggestionBoxHeight: 300,
initialValue: SearchFieldListItem<String>('ABC'),
suggestions: dynamicHeightSuggestion
Expand Down
132 changes: 85 additions & 47 deletions lib/src/listview.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:developer';

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:searchfield/searchfield.dart';
Expand Down Expand Up @@ -153,56 +155,92 @@ class _SFListviewState<T> extends State<SFListview<T>> {
child: LimitedBox(
maxHeight: widget.maxHeight ?? double.infinity,
child: ListView.builder(
shrinkWrap: widget.maxHeight != null,
reverse: widget.suggestionDirection == SuggestionDirection.up,
padding: EdgeInsets.zero,
controller: _scrollController,
itemCount: widget.list.length,
physics: widget.list.length == 1
? NeverScrollableScrollPhysics()
: ScrollPhysics(),
itemBuilder: (context, index) => Builder(builder: (context) {
if (widget.selected == index) {
SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
Scrollable.ensureVisible(context,
alignment: 0.1, duration: Duration(milliseconds: 300));
});
}
return TextFieldTapRegion(
onTapOutside: (x) {
widget.onTapOutside!(x);
shrinkWrap: widget.maxHeight != null,
reverse: widget.suggestionDirection == SuggestionDirection.up,
padding: EdgeInsets.zero,
controller: _scrollController,
itemCount: widget.list.length,
physics: widget.list.length == 1
? NeverScrollableScrollPhysics()
: ScrollPhysics(),
itemBuilder: (context, index) {
return KeepAliveListItem(child: Builder(
builder: (context) {
if (widget.selected == index) {
SchedulerBinding.instance
.addPostFrameCallback((Duration timeStamp) {
if (mounted) {
try {
Scrollable.ensureVisible(context,
alignment: 0.1,
duration: Duration(milliseconds: 300));
} catch (e) {
log('Error scrolling to selected item: $e');
}
}
});
}

final child = TextFieldTapRegion(
onTapOutside: (x) {
widget.onTapOutside!(x);
},
child: Material(
color: widget.suggestionsDecoration == null
? Theme.of(context).colorScheme.surface
: Colors.transparent,
child: InkWell(
hoverColor:
widget.suggestionsDecoration?.hoverColor ??
Theme.of(context).hoverColor,
onTap: () => widget.onSuggestionTapped(
widget.list[index], index),
child: Container(
height: widget.dynamicHeight
? null
: widget.itemHeight,
key: widget.list[index].key,
width: double.infinity,
decoration: _getDecoration(index),
child: Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: widget.list[index].child ??
Text(
widget.list[index].searchKey,
style: widget.suggestionStyle,
),
)),
),
)));
return child;
},
child: Material(
color: widget.suggestionsDecoration == null
? Theme.of(context).colorScheme.surface
: Colors.transparent,
child: InkWell(
hoverColor: widget.suggestionsDecoration?.hoverColor ??
Theme.of(context).hoverColor,
onTap: () => widget.onSuggestionTapped(
widget.list[index], index),
child: Container(
height:
widget.dynamicHeight ? null : widget.itemHeight,
key: widget.list[index].key,
width: double.infinity,
decoration: _getDecoration(index),
child: Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: widget.list[index].child ??
Text(
widget.list[index].searchKey,
style: widget.suggestionStyle,
),
)),
),
)));
}),
),
));
}),
),
),
);
}
}

class KeepAliveListItem extends StatefulWidget {
final Widget child;

KeepAliveListItem({required this.child});

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

class _KeepAliveListItemState extends State<KeepAliveListItem>
with AutomaticKeepAliveClientMixin {
@override
Widget build(BuildContext context) {
super.build(context);
return widget.child;
}

@override
bool get wantKeepAlive => true;
}
20 changes: 10 additions & 10 deletions lib/src/searchfield.dart
Original file line number Diff line number Diff line change
Expand Up @@ -218,20 +218,20 @@ class SearchField<T> extends StatefulWidget {
///
/// When not specified, the default value is `51.0`.
///
/// If you don't want to set a fixed item height, set **[dynamicHeightItem]** to true.
/// If you don't want to set a fixed item height, set **[dynamicHeight]** to true.
final double itemHeight;

/// (Optional) Specifies a maximum height for the suggestion box.
///
/// This will only take into account when setting **[dynamicHeightItem]** to true (aka. opt-in to dynamic height)
/// This will only take into account when setting **[dynamicHeight]** to true (aka. opt-in to dynamic height)
///
/// When not specified, the default value is half the screen height.
final double? maxSuggestionBoxHeight;

/// Set to true to opt-in to dynamic height. We don't calculate the total height for the whole box, instead, each item on the suggestion list will have their respective height.
///
/// (Optional) Use **[maxSuggestionBoxHeight]** to set a maximum height for the suggestion box.
final bool dynamicHeightItem;
final bool dynamicHeight;

/// Specifies the color of margin between items in suggestions list.
///
Expand Down Expand Up @@ -346,7 +346,7 @@ class SearchField<T> extends StatefulWidget {
this.initialValue,
this.inputFormatters,
this.inputType,
this.dynamicHeightItem = false,
this.dynamicHeight = false,
this.maxSuggestionBoxHeight,
this.itemHeight = 51.0,
this.marginColor,
Expand Down Expand Up @@ -461,7 +461,7 @@ class _SearchFieldState<T> extends State<SearchField<T>> {
SuggestionDirection getDirection() {
// Early return if not flex or dynamic height
if (_suggestionDirection != SuggestionDirection.flex &&
!widget.dynamicHeightItem) {
!widget.dynamicHeight) {
return _suggestionDirection;
}
final MediaQueryData mediaQuery = MediaQuery.of(context);
Expand Down Expand Up @@ -718,7 +718,7 @@ class _SearchFieldState<T> extends State<SearchField<T>> {
} else if (snapshot.data!.isEmpty || widget.showEmpty) {
isEmpty = true;
_totalHeight = 0;
} else if (widget.dynamicHeightItem) {
} else if (widget.dynamicHeight) {
_totalHeight = null;
} else {
final paddingHeight = widget.suggestionsDecoration != null
Expand Down Expand Up @@ -769,8 +769,8 @@ class _SearchFieldState<T> extends State<SearchField<T>> {
behavior:
ScrollConfiguration.of(context).copyWith(scrollbars: false),
child: SFListview<T>(
dynamicHeight: widget.dynamicHeightItem,
maxHeight: widget.dynamicHeightItem
dynamicHeight: widget.dynamicHeight,
maxHeight: widget.dynamicHeight
? _totalHeight ??
widget.maxSuggestionBoxHeight ??
remainingHeight
Expand Down Expand Up @@ -824,7 +824,7 @@ class _SearchFieldState<T> extends State<SearchField<T>> {
if (direction == SuggestionDirection.down) {
return Offset(0, textFieldSize.height);
} else if (direction == SuggestionDirection.up) {
if (widget.dynamicHeightItem) {
if (widget.dynamicHeight) {
return Offset(
0,
widget.maxSuggestionBoxHeight != null
Expand Down Expand Up @@ -909,7 +909,7 @@ class _SearchFieldState<T> extends State<SearchField<T>> {
final lastSearchResult = <SearchFieldListItem<T>>[];
@override
Widget build(BuildContext context) {
if (!widget.dynamicHeightItem) {
if (!widget.dynamicHeight) {
if (widget.suggestions.length > widget.maxSuggestionsInViewPort) {
_totalHeight = widget.itemHeight * widget.maxSuggestionsInViewPort;
} else {
Expand Down

0 comments on commit 70b870d

Please sign in to comment.