I am very new to Flutter and Dart so I am trying to build a simple search app with queries based on a word typed in Flutters showSearch search bar. I understand the listtile that is built to show suggestions to the user, and tapping that suggestion will activate the buildresults widget. However, I want to enable the search button on the keyboard to simply search the inputted word, rather than tapping on the limited suggestion list.
Simple android keyboard
When the button is clicked normally, the keyboard is closed the buildresults is not activated. So far you have to actually click a suggested listtile option. Is there a way to enable the keyboard's search button to search the inputted text? or Is the user limited to the suggested listtile options? I will list my showSearch delegate below:
class StockDelegate extends SearchDelegate<String> {
final stocks = [
"IBM",
"NKLAW",
"DKNGZ",
"DRD",
"PRTS",
"TSLA",
"KIRK",
"VBIV"
];
final suggested = ["IBM", "TSLA", "BNTX"];
#override
// TODO: implement textInputAction
// TODO: implement textInputAction
TextInputAction get textInputAction => super.textInputAction;
#override
List<Widget> buildActions(BuildContext context) {
//actions for app bar
StockProvider _stockProvider =
Provider.of<StockProvider>(context, listen: true);
return [
IconButton(
icon: Icon(Icons.clear),
onPressed: () {
query = '';
}),
];
}
#override
Widget buildLeading(BuildContext context) {
//Leading icon on the left of the app bar
return IconButton(
icon: AnimatedIcon(
icon: AnimatedIcons.menu_arrow,
progress: transitionAnimation,
),
onPressed: () {
close(context, null);
});
}
#override
Widget buildResults(BuildContext context) {
//build results code
}
#override
Widget buildSuggestions(BuildContext context) {
// show when someone search for something
stocks.insert(0, query.toString());
final suggestionList = query.isEmpty
? suggested
: stocks.where((p) => p.startsWith(query)).toList();
return ListView.builder(
itemCount: suggestionList.length,
itemBuilder: (context, index) => ListTile(
onTap: () async {
Provider.of<StockProvider>(context, listen: false)
.searchBySymbol(suggestionList[index]);
if (!stocks.contains(suggestionList[index]))
suggestionList.add(suggestionList[index]);
showResults(context);
},
leading: Icon(Icons.attach_money),
title: Text(suggestionList[index]),
),
);
}
I don't know whether you know about onSubmitted property or not, which is used in the TextField/Text/TextFormField. Since, I cannot see your TexFormField/Text/TextField anywhere, but I think you must be using it somewhere.
So, this is how onFieldSubmitted used, which does the job which you want, that is, when the user hit on the magnifying lens on the keypad, it runs your function
TextFormField(
controller: _yourTextEditingController,
textInputAction: TextInputAction.search,
onFieldSubmitted: (){
// here you do your operation when you hit the
// keypad magnifying lens
// check with print()
print('Pressed via keypad');
}
)
Don't get confused with onSubmitted name, onFieldSubmitted is used in TextFormField/Text/TextField, which uses onSubmitted property. I hope that answers your question :) Let me know, is that was the thing you were looking for.
Override showResults() method similar to buildSuggestions().
override showResults method as follow:
#override
void showResults(BuildContext context) {
super.showResults(context);
showSuggestions(context);
FocusScope.of(context).unfocus();
}
Related
I am getting data of documents through API. I have tried many ways but couldn't resolved that how to filter the list.
I didn't properly understand your question, but you can use the stream builder.
You get the pages from the api and add them to the stream. Check this link.
https://api.flutter.dev/flutter/widgets/StreamBuilder-class.html
you have to show some of the code you already have so as to know where to start from helping you, but anyways, this is the basic logic to do it,
first, you should have some sort of searchDelegate set up, then in your CustomSearchDelegate you should
class MyDelegate extends SearchDelegate<returnType> {
#override
Widget? buildLeading(BuildContext context) {
return IconButton(
icon: const Icon(Icons.arrow_back), // could be anything
onPressed: () {
Navigator.pop(context); /* could be anything you want your leading icon to do */
},
);
}
#override
List<Widget>? buildActions(BuildContext context) {
return [
IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
query.isEmpty ? Navigator.pop(context) : query = ''; // again, could be anything at all
},
),
];
}
#override
Widget buildResults(BuildContext context) {
return const Center();
}
#override
Widget buildSuggestions(BuildContext context) {
List<dataType> suggestions;
query.isEmpty
? suggestions = [] /* if you want the list to be empty first time user opens searchpage or if user clears search field else, remove the check */
: suggestions = ListfromAPI.where((data) {
final result = data.toLowerCase();
final input = query.toLowerCase();
return result.contains(input);
}).toList();
return ListView.builder(
itemCount: suggestions.length,
itemBuilder: (context, suggestIndex) {
final suggestion = suggestions[suggestIndex];
return ListTile(
title: Text(suggestion),
onTap: () {
close(context, suggestion); /* passing back the data user selected and where you call showSearch(), you await this data and when it arrives, you use it to do anything you want to */
},
);
});
}
}
I have a list view and want to edit the Tile's title. When user click the edit icon, text widget change to TextField. Once user tap the textfield, keyboard show and immediately disappeared.
May I know what is the issue?
class EditableListTile extends StatefulWidget {
final Favourite favourite;
final Function onChanged;
final Function onTap;
const EditableListTile(
{Key? key,
required this.favourite,
required this.onChanged,
required this.onTap})
: super(key: key);
#override
_EditableListTileState createState() => _EditableListTileState();
}
class _EditableListTileState extends State<EditableListTile> {
Favourite? favourite;
late bool _isEditingMode;
late TextEditingController _titleEditingController;
#override
void initState() {
super.initState();
favourite = widget.favourite;
_isEditingMode = false;
}
#override
Widget build(BuildContext context) {
return ListTile(
onTap: () {
widget.onTap(favourite);
},
leading: leadingWidget,
title: titleWidget,
trailing: tralingButton,
);
}
Widget get leadingWidget {
return SizedBox(
width: 32,
child: FolderIcon(
color: Theme.of(context).iconTheme.color!,
),
);
}
Widget get titleWidget {
if (_isEditingMode) {
_titleEditingController = TextEditingController(text: favourite?.name);
return TextField(
controller: _titleEditingController,
);
} else {
return Text(favourite!.name);
}
}
Widget get tralingButton {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
(favourite?.isDefault == false)
? (_isEditingMode
? IconButton(
icon: const Icon(Icons.check),
onPressed: saveChange,
)
: IconButton(
icon: const Icon(Icons.edit),
onPressed: _toggleMode,
))
: Container(),
_isEditingMode
? IconButton(
icon: const Icon(Icons.cancel_outlined),
onPressed: cancelChange,
)
: Container()
],
);
}
void _toggleMode() {
setState(() {
_isEditingMode = !_isEditingMode;
});
}
void cancelChange() {
setState(() {
_isEditingMode = !_isEditingMode;
});
}
void saveChange() {
favourite!.name = _titleEditingController.text;
_toggleMode();
widget.onChanged(favourite!);
}
}
you get this error because you initialized the TextEdittingController inside the titleWidget. every time the widget rebuild, create a new instance of TextEdittingController.
on top of your clas change it like this
// late TextEditingController _titleEditingController; <== Change This
TextEditingController _titleEditingController = TextEditingController();
in titleWidget change your code to this.
Widget get titleWidget {
if (_isEditingMode) {
_titleEditingController.text = favourite?.name;
Real culprit is key in ListView.seperate. I used key : UniqueKey(). If I change to ValueKey(state.favourites[index]), it is working now.
I used key : UniqueKey() because I have onDismissed but one of the item, want to trigger onDismissed but don't want to dismissed.
Let's say, item is folder name and if user delete the item, delete the folder and delete all the files under that folder. But one folder is system generated and don't want user to delete that folder but let them to delete files. So we call confirmDismiss and tell the user that it is system generated folder and only will delete files.
But list view don't allow. So I found out the UniqueKey is work
around. So edit is importance. So that I take out UniqueKey.
Like Alex Aung's answer above, they're right that the use of UniqueKey() is a problem .
In my case I had an action button that pushed a page route to the navigator. On the content view (GameView) I had a UniqueKey() set and it was responsible for series of issues with input fields downstream.
Any time I set this back to UniqueKey(), any clicking inside a downstream TextFormField causes the Keyboard open then immediately close.
Widget getActionButton() {
return FloatingActionButton(
onPressed: () {
final Account account = Account("test#test.com");
widget.viewModels.gamesModel.load(account);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => GamesView(
key: Key("GameView"), // UniqueKey() is a problem here.
account: account,
viewModels: widget.viewModels,
)
)
);
},
tooltip: 'Save Changes',
child: Icon(Icons.save),
);
}
I have this code in SearchDelegate
class CustomSearchDelegate extends SearchDelegate<String> {
#override
List<Widget> buildActions(BuildContext context) {
return [
IconButton(
icon: Icon(Icons.clear),
onPressed: (){
query = "";
},
)
];
}
#override
Widget buildLeading(BuildContext context) {
return IconButton(
icon: Icon(Icons.arrow_back),
onPressed: (){
close(context, "");
},
);
}
#override
Widget buildResults(BuildContext context) {
close(context, query );
return Container();
}
#override
Widget buildSuggestions(BuildContext context) {
return Container();
}
}
When I run it in debug mode, it works properly. But when I run in release mode, when confirming the search, it doesn't return to the page that called Search, it just returns container, even with the "close(context, query );" before in buildResults.
I couldn't solve this specific problem, I really searched for any possible solution, but it seems to have to do with the way flutter generates the apk in release. In my case I just gave up using showSearch with SearchDelegate, and created a custom Appbar like a SearchBar. I'll leave below some links that I based it on
This was the one I used the most as an example
https://github.com/ahmed-alzahrani/Flutter_Search_Example
Custom AppBar Flutter
This option I haven't used but I found it interesting as another option to optimize your SearchBar
https://pub.dev/packages/flutter_search_bar
There's a button on my home page which when clicked should redirect me to another page and click on textformfield on that page,I dont know how to do that
just use autofocus: true on another page
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: TextButton(
onPressed: () {
Navigator.push(context, MaterialPageRoute(
builder: (context) {
return NextPage();
},
));
},
child: Text("Click")),
),
);
}
}
class NextPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: TextField(
autofocus: true,
),
);
}
}
There is multiple ways to do this depending on your use case
1- if you want to focus on the text form field every time you enter the page you change autofocus property in the textField to true,
TextField(
autofocus:true,
)
2- if you want to trigger focus on a textfield by manually you can use FocusNode object, this focus node will be attached to your text field.
First you need to initialize the object.
FocusNode myFocusNode;
#override
void initState() {
super.initState();
myFocusNode = FocusNode();
}
#override
void dispose() {
// Clean up the focus node when the Form is disposed.
myFocusNode.dispose();
super.dispose();
}
then Attach the focus node object to your text field
TextField(
focusNode: myFocusNode,
);
Now you can use this focus node in a function to focus on this text
// focus on textfield (same as text field pressed)
myFocusNode.requestFocus()
// unfocus on textfield (same as pressing done on textfield or pressing the back button)
myFocusNode.unfocus()
you can pass a flag to the new page you are going to, which will trigger the focus function
I made a simple search bar with SearchDelegate.
Although there are many resources on the internet about searching, there is almost no source on how to get results when the search is over.
My list, detail page and search bar are in this view
After searching, how can I go to the detail page with the buildResults?
class SearchTeam extends SearchDelegate<String> {
#override
List<Widget> buildActions(BuildContext context) {
return [
IconButton(
icon: Icon(Icons.clear),
onPressed: () {
query = "";
})
];
}
#override
Widget buildLeading(BuildContext context) {
return IconButton(
icon: AnimatedIcon(
icon: AnimatedIcons.menu_arrow,
progress: transitionAnimation,
),
onPressed: () {
close(context, null);
});
}
#override
Widget buildResults(BuildContext context) {
// How can I go to the detail page of the found team name?
}
#override
Widget buildSuggestions(BuildContext context) {
final suggestionList = query.isEmpty
? [" "]
: MyData.TEAM_NAME.where((p) => p.toLowerCase().contains(query.toLowerCase()))
.toList();
return ListView.builder(
itemBuilder: (context, index) => ListTile(
onTap: (){},
title: Text(suggestionList[index]),
),
itemCount: suggestionList.length,
);
}
}
You can't take advantage of the keyboard's onSubmitted event when using a SearchDelegate as it won't expose the API to do that. Don't use SearchDelegate just for showing the TextField in that case. If you want to use your custom page for displaying search results, create your own search input TextField as well. It will be easier for you to do that with much better APIs that you can have control over.
If you are to use SearchDelegate you have to build your result widget directly in the buildResults method. In your case, show a widget using the query field which on pressing will take the user to the detail page. Or you can just navigate to the detail page upon pressing the item in the suggested list.
It would be something like this.
List<String> getResults(String query) {
// apply getting results logic here
return [];
}
#override
Widget buildResults(BuildContext context) {
final results = getResults(query);
return ListView.builder(
itemBuilder: (context, index) => ListTile(
onTap: () {
// assuming `DetailsPage` exists
Navigator.push(context, DetailsPage(results[index]));
},
title: Text(results[index]),
),
itemCount: results.length,
);
}
it works,
just ignore the returning value of buildResults()
thank you.