flutter-form-builder-ecosystem / flutter_form_builder

Simple form maker for Flutter Framework
https://pub.dev/packages/flutter_form_builder
MIT License
1.49k stars 537 forks source link

I am getting an error on the Type Ahead when I return a List<Entity> to the suggestionsCallback #21

Closed metaltigerfish closed 5 years ago

metaltigerfish commented 5 years ago

controls: [ FormBuilderInput.typeAhead( label: 'Team 1', attribute: 'team1', require: true, //value: team1name, itemBuilder: (context, team1) { return ListTile( title: Text(team1.name), ); }, suggestionsCallback: (query) async { BlocProvider.of(context).dataBloc.typeAheadQuery.add(query); return await BlocProvider.of(context) .dataBloc .typeAheadValues .last; }, ), ],

The suggestions appear fine in the type ahead field suggestion box, but when I click on one of the items in the list I get the following error and the value does not get set to the selected value.

flutter: ══╡ EXCEPTION CAUGHT BY GESTURE ╞═══════════════════════════════════════════════════════════════════ flutter: The following assertion was thrown while handling a gesture: flutter: type '_$Entity' is not a subtype of type 'String'

danvick commented 5 years ago

I think this shows that the suggestionsCallback is expecting a list of Strings. Try using a class with a toString() implementation.

Kindly note that flutter_typeahead is a separate package maintained by another developer. Try raising the issue on this repo

metaltigerfish commented 5 years ago

Would it be possible to implement this from the flutter_typeahead

onSuggestionSelected: (suggestion) { this._typeAheadController.text = suggestion; },

danvick commented 5 years ago

I get your point @metaltigerfish. However, that would mean giving passing the TextEditingController for the TypeAhead to the user then it becomes:

onSuggestionSelected: (TextEditingController typeAheadController, dynamic suggestion) {
    typeAheadController.text = suggestion.name;
},

Which again seems to make sense. I'll explore the option, though the plan was not to significantly alter the underlying package

danvick commented 5 years ago

Meanwhile might I suggest that you map your list of object to a list of strings, I've tried that and it seems to work. Your suggestionsCallback would end up looking similar to:

suggestionsCallback: (query) {
                var contacts = [
                  AppProfile('Andrew', 'stock@man.com'),
                  AppProfile('Brian', 'brian@flutter.io'),
                  AppProfile('Fred', 'fred@google.com'),
                  AppProfile('John', 'john@flutter.io'),
                  AppProfile('Paul', 'paul@google.com'),
                  AppProfile('Thomas', 'thomas@flutter.io')
                ];
                var contactsNames = contacts
                    .map((contact) => "${contact.name} <${contact.email}>")
                    .toList(growable: false);
                if (query.length != 0) {
                  var lowercaseQuery = query.toLowerCase();
                  return contactsNames.where((contact) {
                    return contact.toLowerCase().contains(lowercaseQuery);
                  }).toList(growable: false)
                    ..sort((a, b) => a
                        .toLowerCase()
                        .indexOf(lowercaseQuery)
                        .compareTo(b.toLowerCase().indexOf(lowercaseQuery)));
                } else {
                  return contactsNames;
                }
},
metaltigerfish commented 5 years ago

Awesome thank you, I will try that .

danvick commented 5 years ago

I'm gonna consider this elegant enough of a solution and close this issue since it doesn't directly relate to this package.

aytunch commented 5 years ago

@danvick I think your solution helps get rid of the error but inside of the code, we are still not be able to get the whole class object whose name is selected. I opened up an issue as you suggested in flutter_typeahead. Most of the time user will be getting the suggestions from a backend as objects and we would like to process the object(its id for instance) instead of just its .toString() version. Any help would be greatly appreciated. And thanks for this great package.

danvick commented 5 years ago

I will consider working on the issue. The problem is that flutter_typeahead only accepts String values.

I've had this issue myself in other projects.

My workaround for this issue was to use FormBuilderChipsInput then set maxChips to 1 then get the first element using a valueTransformer. I know that sounds complex, I'll share sample code later on when I have time.

aytunch commented 5 years ago

Thats a very interesting workaround @danvick i would love to see the code if possible. Because i have every other module in my form working other than this type ahead and i cant go forward without it. I use google places api to get suggestions. Everything in the form Looks great. Thanks for this package. Meanwhile I hope author of typeahead spends sometime on this issue as well since its that packages main responsibility to have support for this issue. But again i am ok with a workaround atm since i need to get the suggested object instead of the string to go forward.

danvick commented 5 years ago

Try this out. Let me know if you have any questions.

FormBuilderChipsInput(
  decoration:
      InputDecoration(labelText: "Contact Name"),
  attribute: 'contact',
  maxChips: 1, //Allow only one chip
  valueTransformer: (val) => val?[0], //Pick the first element from the array
  findSuggestions: (String query) async {
    List<Contact> contacts = await _fetchContacts();
    if (query.length != 0) {
      var lowercaseQuery = query.toLowerCase();
      return contacts.where((contact) {
        return contact.name
            .toLowerCase()
            .contains(query.toLowerCase());
      }).toList(growable: false)
        ..sort((a, b) => a.name
            .toLowerCase()
            .indexOf(lowercaseQuery)
            .compareTo(b.name
                .toLowerCase()
                .indexOf(lowercaseQuery)));
    } else {
      return [];
    }
  },
  chipBuilder: (context, state, contact) {
    return InputChip(
      key: ObjectKey(contact),
      label: Text('${contact.name}'),
      onDeleted: () => state.deleteChip(contact),
      materialTapTargetSize:
          MaterialTapTargetSize.shrinkWrap,
    );
  },
  suggestionBuilder: (context, state, contact) {
    return ListTile(
      key: ObjectKey(contact),
      title: Text('${contact.name}'),
      subtitle: Text('${contact.email}'),
      onTap: () => state.selectSuggestion(contact),
    );
  },
),
aytunch commented 5 years ago

This look very well and even better for my use case. Thanks @danvick :) However, one thing I could't get to work was constructing the initial suggestion list when query string is empty and the text box is pressed.

I have a list of places which are around the users location and when the user presses the FormBuilderChipsInput and before types anything I want to show the suggestions. And when he/she types a letter I want to change suggestions. The second part works as of now but the initial suggestions don't work. Do you know how to trigger it?

danvick commented 5 years ago

Within findSuggestions the line that reads:

else {
      return [];
}

change it to:

else {
      return places;
}

or whatever your equivalent of your code is.

aytunch commented 5 years ago

Thats how I have it at the moment but it doesn't show the initial suggestions when I touch and gain focus on the textinputview:/

It works only after I type a letter and then backspace. But it doesn't trigger in the beginning.

Is there a way to fire findSuggestions when the controller gains focus?

aytunch commented 5 years ago

@danvick Also some part of my suggestion list stays under the keyboard. Is there a way to limit it? Sorry for bothering you this much:/

EDIT: just realized this issue is not present with TypeAhead but with ChipInput widget. EDIT2: ChipInput widget has a problem with its focus mechanics. When ChipInput is in focus and for example I select a FormBuilderRadio item, ChipInputs keyboard is still open and I can type inside of it.

danvick commented 5 years ago

Since I'm the maintainer of ChipsInput, I'll try to work on the issues you've raised. I may not be able to do it immediately but can I request you to close this issue then open the issues related to ChipsInput here

aytunch commented 5 years ago

I will do what you asked for in detail in a couple of hours when i am In front of my computer. Take care

JohnKuan commented 5 years ago

@danvick I have opened a pull request to allow control of the text to update onto TextEditingController. It has an assert statement to check if T is not of type String. Let me know if this works. https://github.com/danvick/flutter_form_builder/pull/115

Marcosmaliki commented 2 years ago

Is it possible to use TypeAhead as an autocomplete textfield? It works great when an item from the list is selected. I need a way to use the list only as suggestions but also accept new text from the user

yeswa-test commented 3 months ago

suggestionCallback isn't a type gives on my migrating project