Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
metaltigerfish opened this issue Jan 23, 2019 · 18 comments

Comments

@metaltigerfish
Copy link

metaltigerfish commented Jan 23, 2019

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
Copy link
Collaborator

danvick commented Jan 24, 2019

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
Copy link
Author

Would it be possible to implement this from the flutter_typeahead

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

@danvick
Copy link
Collaborator

danvick commented Jan 26, 2019

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
Copy link
Collaborator

danvick commented Jan 26, 2019

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
Copy link
Author

Awesome thank you, I will try that .

@danvick
Copy link
Collaborator

danvick commented Jan 28, 2019

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

@aytunch
Copy link

aytunch commented Jun 24, 2019

@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
Copy link
Collaborator

danvick commented Jun 25, 2019

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.

@danvick danvick reopened this Jun 25, 2019
@aytunch
Copy link

aytunch commented Jun 25, 2019

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
Copy link
Collaborator

danvick commented Jun 25, 2019

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
Copy link

aytunch commented Jun 25, 2019

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
Copy link
Collaborator

danvick commented Jun 25, 2019

Within findSuggestions the line that reads:

else {
      return [];
}

change it to:

else {
      return places;
}

or whatever your equivalent of your code is.

@aytunch
Copy link

aytunch commented Jun 25, 2019

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
Copy link

aytunch commented Jun 25, 2019

@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
Copy link
Collaborator

danvick commented Jun 26, 2019

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
Copy link

aytunch commented Jun 26, 2019

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
Copy link
Contributor

@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
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants