Flutterexperts

Empowering Vision with FlutterExperts' Expertise
Tags In Flutter

What are tags?

Tags are small keywords that contain certain information, which helps to describe something like an item. You must have seen them being used in popular social media apps like instagram, twitter etc.

They further help us by classifying items, suppose we are searching for an image of a person which has certain aspects like the ‘person is smiling’ and ‘it is raining’. We can then use tags like ‘rain’ and ‘smile’ to categorize that picture and find the picture we were looking for, that’s basically how tags work.

Now that you are familiar with tags, lets jump into how we are gonna make them.


For creating them you will need following packages :

flutter_tags: "^0.4.9+1"
flutter_typeahead:

flutter_typeahead — helps us by suggesting the word that user is currently trying to type in.

flutter_tags — helps us by creating tags for us, when user selects the suggestion.

Now that we are done with getting the necessary packages, we can start working on the code itself.

So first we need a textfield where we can enter our text and have it show us different results based on the text that we entered.

So we will use TypeAheadField here, this widget is the one provided by flutter_typeahead package and will get us all the different results based on our queries,

It takes 3 main parameters :

  1. onSuggestionSelected — its a function which is executed when we select an item from the list that is being shown to us by our query, an example would be to search for apple in a list of fruits by typing ‘ap’ and then clicking on it.
  2. itemBuilder — its a builder function which takes context and suggestions in it, suggestions are simple guesses from our list shown below the textfield in the form of list based on what user is currently typing.
  3. suggestionsCallback — another function, which expects us to return a List of Strings and gives us a value with which we can sort different suggestions based on what user is typing.

Now that all the main functions are out of the way, lets get to coding part of it.

We are first gonna code the suggestionsCallback function so that we can easily get the items in a list format which contains the text that user is typing.

_sugestionList({@required List<String> tags , @required String suggestion}) {
List<String> modifiedList = [];
modifiedList.addAll(tags);
modifiedList.retainWhere((text) => text.toLowerCase().contains(suggestion.toLowerCase()));
if(suggestion.length >=2) {
return modifiedList;
}
else {
return null;
}
}

Here we have created a function which will take list of tags and a String suggestion and on basis of it returns the number of items that contains the suggestion, we have used retainsWhere function of List which basically sorts through the list and retains the ones which match our condition, the condition that I have put in here is that we need to convert each item to lowerCase then convert the suggestion to lowerCase then compare if the list item contains that suggestion, if it does then it will return the modified list.

However, to make it look better I have put in another condition that, only returns the modified list if user has entered 2 or more than 2 words in there so if the list of suggestions is too long it won’t look bad.

Alright now that our suggestionCallback is set lets code in our itemBuilder and onSuggestionSelected as well.

TypeAheadField(
hideOnLoading: true,
hideOnEmpty: true,
getImmediateSuggestions: false,
onSuggestionSelected: (val) {
_onSuggestionSelected(val);
},
itemBuilder: (context, suggestion) {
return ListTile(
title: Text(suggestion,),
);
},
suggestionsCallback: (val) {
return _sugestionList(tags: tagsList, suggestion: val,);
// return ;
},
),

This is the basic code for typeAheadField, you will notice that in the itemBuilder I have made a simple ListTile which has title of suggestion, and in the onSuggestionSelected, I have made a simple function that will add the selected item to my list of tags, for now we can comment this function out and notice how it returns our list on writing something in the field.

Of-course before we do so we need to create a list that this will use so the code for that is below

List<String> tagsList = ['apple', 'banana', 'orange', 'kiwi' , ''];
List<String> selectedTags = [];

Alright, now we are set so you can go ahead and try it out.

Flutter tags list

Now that we are done with the typeAheadField and we are getting the list of items, now to make a tag on selecting a particular item from the list .We are gonna make a function that will build tags for us and call it right below our typeAheadField widget,

_generateTags() {
return selectedTags.isEmpty ?
Container()
:
Container(
alignment: Alignment.topLeft,
child: Tags(
alignment: WrapAlignment.center,
itemCount: selectedTags.length,
itemBuilder: (index) {
return ItemTags(
index: index,
title: selectedTags[index],
color: Colors.blue,
activeColor: Colors.red,
onPressed: (Item item) {
print('pressed');
},
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
elevation: 0.0,
borderRadius: BorderRadius.all(Radius.circular(7.0)),
// textColor: ,
textColor: Colors.white,
textActiveColor: Colors.white,
removeButton: ItemTagsRemoveButton(
color: Colors.black,
backgroundColor: Colors.transparent,
size: 14,
onRemoved: () {
_onSuggestionRemoved(selectedTags[index]);
return true;
}),
textOverflow: TextOverflow.ellipsis,
);
},
),
);
}

This function will help us generate tags and will even help us remove them, ignore the remove button function for now we will come back to it later.

So this function will work with selectedTags list that we created earlier which should have no value right now but we will fill it upon selection of an item from the list, this function will return tags with titles from that list and an index which is provided by itemBuilder of Tags widget so we can easily determine which tag was clicked on as well.

Now we move onto coding for adding into the selectedTags list,

_onSuggestionSelected(String value) {
final String exists = tagsList.firstWhere((text) => text==value,orElse: (){return null;});
if(exists !=null) {
final String isAlreadyInSelectedList = selectedTags.firstWhere((text)=> text==value,orElse: () {return null;});

if(isAlreadyInSelectedList ==null) {
setState(() {
selectedTags.add(value);
tagsList.remove(value);
});
}
}
}

We have created this _onSuggestionSelected method that takes a String and based on it first checks if this value exists in the list of our tags then if it exists checks if the value is already present in selectedTags list, if it returns null then we add those items into selectedTags list and remove them from tagsList so that we don’t show an item which is already selected by the user in the list.

we are using firstWhere method here to give us the first matching result.

Alright now we will call this method inside the onSuggestionSelected parameter of TypeAheadField as shown above in the TypeAheadField code and pass it the value, after this, it will start showing our tags to us. As adding is done, now we can work on removing the tag and adding the removed tags value back to our list so let’s create a function for that too!

_onSuggestionRemoved(String value) {
final String exists = selectedTags.firstWhere((text) => text==value,orElse: () {return null;});
if(exists !=null) {
setState(() {
selectedTags.remove(value);
tagsList.add(value);
});
}
}

_onSuggestionRemovedfunction works just like the code from _onSuggestionSelected, it will first check if the value exists in the selectedTags list, if it does exists then we will remove that value from selectedTags list and add it back to our tagsList.

Now we can go ahead and call it inside our remove button parameter of Tag widget like shown before.

Alright so we are more or less done with tag system, but what if, the user wants to create their own tag when they submit ?

Now, we are going to create a function to do just that

_addSuggestion(String value) {
final String exists = tagsList.firstWhere((text) => text ==value,orElse: () {return null;});
if(exists !=null) {
final String isAlreadyInSelectedList = selectedTags.firstWhere((text) => text ==value,orElse: () {return null;});
if(isAlreadyInSelectedList ==null) {
setState(() {
selectedTags.add(value);
tagsList.remove(value);
});
}
}
else {
final String isAlreadyInSelectedList = selectedTags.firstWhere((text) => text==value,orElse: () {return null;});
if(isAlreadyInSelectedList ==null) {
setState(() {
selectedTags.add(value);
});
}
}
}

This _addSuggestion function will help us achieve user making their own tag, First we will check whether the tag that user is specifying already exists in our tagsList if it does, we then check if it exists inside of our selectedTagsList and only add it to our selectedTagsList if it does not exists in it already.

In the else part that is to say it is not present in our tagsList we then check if it is already present inside our selectedTagsList and only add it to the selectedTagsList if it is not already there.

Now you may be asking, where exactly do we call this function?

Well, there is another property of TypeAheadField which is textFieldConfiguration, it takes the value of TextFIeldConfiguration() inside it.

TextFieldConfiguration allows us to interact normally with the textfield that is to say it contains most of the properties of a textField, so we can easily call our function on, onSubmitted, and then we are done.

User entering a custom tag
the user submitting his custom tag

Here is the code

textFieldConfiguration: TextFieldConfiguration(
onSubmitted: (val) {
print('runtimetype of val is ${val.runtimeType}');
_addSuggestion(val);

}

and with that, we are completely done with letting the user create their own tags.

Here is the full code for working tags :

import 'package:flutter/material.dart';
import 'package:flutter_tags/flutter_tags.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: FlutterTagExample(),
);
}
}

class FlutterTagExample extends StatefulWidget {
// List<String> listOfTags = [];

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

class _FlutterTagExampleState extends State<FlutterTagExample> {
List<String> tagsList = ['apple', 'banana', 'orange', 'kiwi', ''];
List<String> selectedTags = [];

@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
child: Column(
children: <Widget>[
SizedBox(
height: 200,
),
TypeAheadField(
textFieldConfiguration: TextFieldConfiguration(
onSubmitted: (val) {
print('runtimetype of val is ${val.runtimeType}');
_addSuggestion(val);

}
),
hideOnLoading: true,
hideOnEmpty: true,
getImmediateSuggestions: false,
onSuggestionSelected: (val) {
_onSuggestionSelected(val);
},
itemBuilder: (context, suggestion) {
return ListTile(
title: Text(
suggestion,
),
);
},
suggestionsCallback: (val) {
return _sugestionList(
tags: tagsList,
suggestion: val,
);
// return ;
},
),
SizedBox(
height: 20,
),
_generateTags()
],
),
),
);
}

_onSuggestionRemoved(String value) {
final String exists =
selectedTags.firstWhere((text) => text == value, orElse: () {
return null;
});
if (exists != null) {
setState(() {
selectedTags.remove(value);
tagsList.add(value);
});
}
}
_addSuggestion(String value) {
final String exists = tagsList.firstWhere((text) => text ==value,orElse: () {return null;});
if(exists !=null) {
final String isAlreadyInSelectedList = selectedTags.firstWhere((text) => text ==value,orElse: () {return null;});
if(isAlreadyInSelectedList ==null) {
setState(() {
selectedTags.add(value);
tagsList.remove(value);
});
}
}
else {
final String isAlreadyInSelectedList = selectedTags.firstWhere((text) => text==value,orElse: () {return null;});
if(isAlreadyInSelectedList ==null) {
setState(() {
selectedTags.add(value);
// tagsList.add(value);
});
}
}
}

_onSuggestionSelected(String value) {
final String exists =
tagsList.firstWhere((text) => text == value, orElse: () {
return null;
});
if (exists != null) {
final String isAlreadyInSelectedList =
selectedTags.firstWhere((text) => text == value, orElse: () {
return null;
});

if (isAlreadyInSelectedList == null) {
setState(() {
selectedTags.add(value);
tagsList.remove(value);
});
}
}
}

_sugestionList({@required List<String> tags, @required String suggestion}) {
List<String> modifiedList = [];
modifiedList.addAll(tags);
modifiedList.retainWhere(
(text) => text.toLowerCase().contains(suggestion.toLowerCase()));
if (suggestion.length >= 2) {
return modifiedList;
} else {
return null;
}
}

_generateTags() {
return selectedTags.isEmpty
? Container()
: Container(
alignment: Alignment.topLeft,
child: Tags(
alignment: WrapAlignment.center,
itemCount: selectedTags.length,
itemBuilder: (index) {
return ItemTags(
index: index,
title: selectedTags[index],
color: Colors.blue,
activeColor: Colors.red,
onPressed: (Item item) {
print('pressed');
},
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
elevation: 0.0,
borderRadius: BorderRadius.all(Radius.circular(7.0)),
// textColor: ,
textColor: Colors.white,
textActiveColor: Colors.white,
removeButton: ItemTagsRemoveButton(
color: Colors.black,
backgroundColor: Colors.transparent,
size: 14,
onRemoved: () {
_onSuggestionRemoved(selectedTags[index]);
return true;
}),
textOverflow: TextOverflow.ellipsis,
);
},
),
);
}
}

If you face any problems while implementing this, let me know I will try my hardest to reply to you ASAP, let us meet again in my next blog, hope to see everyone there.

Have a good day!


Feel free to connect with us:
And read more articles from FlutterDevs.com.

FlutterDevs team of Flutter developers to build high-quality and functionally-rich apps. Hire a flutter developer for your cross-platform Flutter mobile app project on an hourly or full-time basis as per your requirement! You can connect with us on Facebook, GitHub, Twitter, and LinkedIn for any flutter related queries.

We welcome feedback and hope that you share what you’re working on using #FlutterDevs. We truly enjoy seeing how you use Flutter to build beautiful, interactive web experiences.

Leave comment

Your email address will not be published. Required fields are marked with *.