searchfield icon indicating copy to clipboard operation
searchfield copied to clipboard

onSuggestionTap onsubmit are invoked both

Open hpp0hpp opened this issue 1 year ago • 19 comments

Describe the bug when I input something in the searchfield, and I use key to select one suggestion, when I press Enter, onSuggestionTap and onsubmit are invoked the same time, while only onSuggestionTap is expect to be invoked

To Reproduce Steps to reproduce the behavior: when I input something in the searchfield, and I use key to select one suggestion, when I press Enter, onSuggestionTap and onsubmit are invoked the same time, while only onSuggestionTap is expect to be invoked

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

Expected behavior A clear and concise description of what you expected to happen.

Actual behavior What you actually saw instead of the expected behavior.

Screenshots If applicable, add screenshots to help explain your problem.

Code sample

Show code sample
Paste minimal and complete code sample here to investigate the issue. 

Additional context Add any other context about the problem here.

hpp0hpp avatar Aug 29 '24 02:08 hpp0hpp

maybe add else here works image

hpp0hpp avatar Aug 29 '24 02:08 hpp0hpp

Hi @hpp0hpp Thanks for filing the issue. The API is designed in a way that when you select a suggestion by pressing enter on a physical keyboard or submit using virtual keyboard onSubmit should be invoked and when you select a suggestion with a pointer device like mouse or with a touch onSuggestionTapped Should be invoked. If you are seeing both then please share me a minimal code sample to reproduce the issue.

Thanks

maheshj01 avatar Aug 30 '24 13:08 maheshj01

I'm encountering the same issue. Here is the code (for flutter web) that can reproduce the issue.

Steps to reproduce the issue:

  1. Select "Option1"
  2. Enter "Option1" to fire submit, and you see both events.

Console log: [log] [onSuggestionTap] selected OPtion1 <--- Step 1 [log] [onSuggestionTap] selected OPtion1 <--- Step 2 [log] [onSubmit] OPtion1 <--- Step 2

Source code:

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});

  // This widget is the root of your application.
  @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> {
  final focus = FocusNode();
  final _controller = TextEditingController();

  @override
  void initState() {
    super.initState();
    suggestions = getSuggestions();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  var suggestions = <String>[];

  @override
  Widget build(BuildContext 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 = suggestions
                                  .where((element) => element.toLowerCase().contains(query.toLowerCase()))
                                  .toList();
                            }
                            return filter.map((e) => SearchFieldListItem<String>(e, child: searchChild(e))).toList();
                          },
                          onSubmit: (str) async {
                            d.log('[onSubmit] $str');
                          },
                          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'),
                          searchStyle: Theme.of(context).textTheme.bodyMedium!,
                          dynamicHeight: true,
                          maxSuggestionBoxHeight: 300,
                          scrollbarDecoration: ScrollbarDecoration(minThumbLength: 30, thickness: 10),
                          onTapOutside: null,
                          suggestionDirection: SuggestionDirection.down,
                          suggestionStyle: Theme.of(context).textTheme.bodyMedium!,
                          searchInputDecoration: InputDecoration(
                            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) {
                            focus.unfocus();
                            d.log('[onSuggestionTap] selected ${x.searchKey}');
                          },
                        )),
                  ],
                ))));
  }

  List<String> getSuggestions() {
    return ["OPtion1", "OPtion2", "OPtion3", "OPtion4", "OPtion5"];
  }
}

twfungi avatar Sep 05 '24 12:09 twfungi

I tried further with the above code by deleting one character at a time and pressing the enter key. It seems "Option1" stays selected even when I deleted characters. See the log below:

[log] [onSuggestionTap] selected OPtion1 [log] [onSubmit] OPtion

[log] [onSuggestionTap] selected OPtion1 [log] [onSubmit] OPtio

[log] [onSuggestionTap] selected OPtion1 [log] [onSubmit] OPti

[log] [onSuggestionTap] selected OPtion1 [log] [onSubmit] OPti

twfungi avatar Sep 05 '24 12:09 twfungi

@twfungi Thanks for the code sample I will take a look at it, I have published a new release with few fixes v1.1.1.

However this issue has not been taken care of yet, I am not really sure if this is an issue. I need to rethink about the expected behavior or perhaps a race condition I believe?

Your suggestions are welcome! I would appreciate your feedback based on the latest version of the package.

Thanks

maheshj01 avatar Sep 14 '24 18:09 maheshj01

I'd suggest we fire either one, but not both. It depends on how you define these two events.

Through my observation onSuggestionTap is always fired before onSubmit. What I did to workaround this "issue" is to delay 200ms in onSuggestionTap before we make sure onSubmit is not fired. Something like:

onSubmit: (str) async {
                                          d.log('[onSubmit] $str');
                                          submitted = true;
                                          // perform the onSubmit action
                                         },                                        },
onSuggestionTap: (SearchFieldListItem<String> x) {
                                          submitted = false;
                                          focus.unfocus();
                                          d.log('[onSuggestionTap] selected ${x.searchKey}');
                                          Future.delayed(const Duration(milliseconds: 200), () async {
                                            if (!submitted) {
                                             // perform onSuggestionTap action
                                            }
                                          });
                                        },

twfungi avatar Sep 15 '24 11:09 twfungi

onSubmit is now fired only when an option is not selected and user hits enter, Let me know if this seems like a reasonable fix? Beacause while the option is selected onSuggestionTap will anyways fire

This is released in searchfield: ^1.1.2

cc: @twfungi @hpp0hpp

maheshj01 avatar Sep 20 '24 16:09 maheshj01

verified the fix in 1.1.3, thanks!

twfungi avatar Sep 22 '24 01:09 twfungi

With 1.1.3, there's a side effect that, if the user inputs a string that partially matches something in the suggestion list and enters to fire submit, it always fires the matched item in the suggestion list instead of the string the user entered in the textedit.

I think we should allow the user to choose which text to fire, either the string the user enters in the textedit or the partially matched text. What do you think, @maheshj01 ?

twfungi avatar Sep 22 '24 12:09 twfungi

That seems reasonable, We should make this configurable to allow Submitting custom input with an enum

  • submitType.custom / submitType.match

  • With submitType.custom: User can submit with the text entered in the textfield, and onSubmit will return the entered text

  • With submitType.match: User can Submit only the selected suggestion (matched based on the logic), if no suggestion matches the entered text onSubmit won't be fired and onSuggestionTap will be fired on selecting one of the suggestions either via keyboard or with a tap.

Let me know if this seems like a valid approach.

maheshj01 avatar Sep 22 '24 14:09 maheshj01

sgtm

twfungi avatar Sep 23 '24 10:09 twfungi

This is possible in latest release v1.1.4, we don't need a enum

That seems reasonable, We should make this configurable to allow Submitting custom input with an enum

  • submitType.custom / submitType.match
  • With submitType.custom: User can submit with the text entered in the textfield, and onSubmit will return the entered text
  • With submitType.match: User can Submit only the selected suggestion (matched based on the logic), if no suggestion matches the entered text onSubmit won't be fired and onSuggestionTap will be fired on selecting one of the suggestions either via keyboard or with a tap.

Let me know if this seems like a valid approach.

maheshj01 avatar Sep 24 '24 18:09 maheshj01

Bumping down the priority and keeping this issue open for discussion and feedback.

maheshj01 avatar Sep 24 '24 18:09 maheshj01

This is possible in latest release v1.1.4, we don't need a enum

May I know how we can achieve this with v1.1.5? I just tried it and it still fires the matched suggestion instead of the entered text.

twfungi avatar Sep 26 '24 08:09 twfungi

@twfungi Looks like I misunderstood, It is only partially possible as of now OnSubmit fires the entered takes when the searchresult is empty,

Note for self OnSubmit should be fired regardless of whether the input matches or not on pressing enter as long as no suggestion is selected, if a suggestion is already selected enter will trigger onSuggestion tap replacing the input with selected suggestion.

maheshj01 avatar Sep 27 '24 02:09 maheshj01

The problem here is, searchfield will just try to match one in the suggestion list and select it automatically and replace the user entered text with the auto-selected one when firing the onSubmit. This is not always helpful when the use case is to get the exact user input string and perform something else.

One solution without adding the enum is, we still fire both onSuggestion and onSubmit. On the onSuggestion event, the searchkey is the selected one, while on the other, the onSubmit string is the user entered string. Leave the decision to the user which one to use. If this is not the one you prefer, I'm also fine with the enum proposal you mentioned earlier.

twfungi avatar Sep 27 '24 07:09 twfungi

Thanks for clarifying that @twfungi, I think would probably work on this over a weekend, if you are interested please feel free to make a PR.

maheshj01 avatar Sep 27 '24 22:09 maheshj01

@maheshj01 sir when i close the keyboard all the suggestion showing from scratch not showing hat i have entered char in the textfield https://drive.google.com/file/d/132iZYWgLzDNMllLGyOlkA15SayzkrcZe/view check this

sabari7320 avatar Nov 07 '24 10:11 sabari7320

Update: OnSubmit is not being fired as of now I don't see a usecase of onSubmit given that when the input is focused, a suggestion Item will be selected by default meaning the suggestions will have the keyboard focus.

So I plan to remove onSubmit in upcoming releases. I am open to thoughts.

maheshj01 avatar May 16 '25 22:05 maheshj01