Flutter UI doesn't update - search in gridView - flutter

I have a GridView.builder widget displaying all of the users of the app from the list searchedUsers and I am trying to implement searching. The TextFormField widget's onChanged property is defined to trigger the searchName(String searchQuery) function. I made the searchName function print out the searchedUsers list and I can see from the log that it is updated everytime I type something in the TextFormField but the GridView is always empty. Please see the code below. Any help would be greatly appreciated.
I have added comments to the code to explain everything.
class UserGrid extends StatefulWidget {
#override
_UserGridState createState() => _UserGridState();
}
class _UserGridState extends State<UserGrid> {
#override
Widget build(BuildContext context) {
//The list of userModels from FireStore
final userModels = Provider.of<List<UserModel>>(context) ?? [];
//The list that will contain users whose names match the value in TextFormField
List<UserModel> searchedUsers = [];
//The function that updates the searchedUsers list. I wrote setState whenever I changed the
//list because I thought it would update the UI but it doesn't
void searchName (String searchQuery){
for (var i in userModels) {
if (i.name.toLowerCase().contains(searchQuery.toLowerCase())) {
setState(() {
searchedUsers.add(i);
print(searchedUsers);
});
}
}
if (searchedUsers == null) {
setState(() {
searchedUsers = userModels;
});
}
}
return Column(
children: [
SizedBox(height: 20,),
Padding(
padding: EdgeInsets.all(10),
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: new BorderRadius.circular(1000.0),
),
child: Padding(
padding: EdgeInsets.only(left: 15, right: 15, top: 5),
child: TextFormField(
onChanged: (val) {
searchName(val);
},
decoration: InputDecoration(
focusColor: Colors.white,
icon: Icon(Icons.search),
border: InputBorder.none,
labelText: 'For- eller efternavn',
))))),
SizedBox(height: 40,),
GridView.builder(
itemCount: searchedUsers.length,
shrinkWrap: true,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
),
itemBuilder: (context, index) {
return userModelTile(userModel: searchedUsers[index]);
},
),
],
);
}
}

I figured out the problem. The list searchedUsers need to be defined before the build function.

Related

Dynamic List View Builder error - Black Screen List View Builder

I'm currently working on a part of a project in which in I will be typing in an entry to the provided Text Form Field and the typed entry will be displayed in a list view builder dynamically (meaning the list grows the more entry the user gives).
However, I came across this error where right after I entered an entry, the screen changes straight to black [PLEASE SEE VIDEO or PICTURES (attached)]. If you slow down the video, you can catch a glimpse that the entered input was indeed displayed in the box, but the screen just switches to black right away. Any ideas why this happens? What can I fix? What's my error?
The following is a time stamp screenshot of what the error looks like (in case the video does not load):
part1
part2
part3
part4
The following is my code:
import 'package:english_words/english_words.dart';
import 'package:globesisters_internship_project/screens/invite_list.dart';
import 'package:flutter/material.dart';
/*GLOBAL VARIABLE*/
//TextFormField for Friends Name
var _friendsName;
final _friendsNameController = TextEditingController();
class InviteFriendsPage extends StatefulWidget {
#override
State<InviteFriendsPage> createState() => _InviteFriendsPageState();
}
class _InviteFriendsPageState extends State<InviteFriendsPage> {
#override
void dispose1() {
_friendsNameController.dispose();
super.dispose();
}
List<String> list_of_friends = []; **//THIS IS THE ENTRY LIST!!**
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return Column(children: <Widget>[
SizedBox(
height: 10.0,
),
Container(
decoration: BoxDecoration(border: Border.all(color: Colors.black)),
width: 300,
height: 260,
child: ListView.builder( **//THIS IS THE LISTVIEW BUILDER**
shrinkWrap: true,
itemCount: list_of_friends.length,
itemBuilder: (context, index) {
return Card(
//margin: EdgeInsets.all(5),
child: ListTile(
title: Text(list_of_friends[index]),
));
},
)),
SizedBox(
height: 10.0,
), //spacing
Container(
margin: EdgeInsets.only(
left: 20,
right: 20,
),
child: Column(
children: <Widget>[
Form(
key: _formKey,
child: Column(
children: [
TextFormField(
validator: (value) {
if (value!.isEmpty)
return 'Please enter a valid Name';
else
return null;
},
controller: _friendsNameController,
decoration: InputDecoration(
labelText: "Invite your friend!",
prefixIcon: Icon(Icons.person),
border: OutlineInputBorder(),
),
),
SizedBox(height: 10.0),
SendInviteButton(context),
],
),
),
],
))
]);
}
//
/* BUTTON */
OutlinedButton SendInviteButton(BuildContext context) {
int count = 0;
**//THIS IS MY ATTEMPT TO SET-STATE FOR DYNAMIC LIST ENTRY**
void addFriends(String friends) {
setState(() {
list_of_friends.add(friends);
});
}
return OutlinedButton(
style: OutlinedButton.styleFrom(minimumSize: const Size(400, 50)),
onPressed: () {
if (_formKey.currentState!.validate()) {
count++;
final addedFriend = _friendsNameController.text;
//widget.add_friend(addedFriend);
addFriends(addedFriend);
Navigator.of(context).pop();
}
},
child: Text(
"Send Invite".toUpperCase(),
style: const TextStyle(color: Colors.cyan, fontWeight: FontWeight.bold),
),
);
}
}

Deleting specific item out of ListView with Bloc

I have a page that consists of a ListView, which contains TextFormFields. The user can add or remove items from that ListView.
I use the bloc pattern, and bind the number of Items and their content inside the ListView to a list saved in the bloc state. When I want to remove the items, I remove the corresponding text from this list and yield the new state. However, this will always remove the last item, instead of the item that's supposed to be removed. While debugging, I can clearly see that the Item I want removed is in fact removed from the state's list. Still, the ListView removes the last item instead.
I've read that using keys solves this problem and it does. However, if I use keys there is a new problem.
Now, the TextFormField will go out of focus every time a character is written. I guess this is to do with the fact that the ListView is redrawing its items everytime a character is typed, and somehow having a key makes the focus behave differently.
Any ideas how to solve this?
The page code (The ListView is at the bottom):
class GiveBeneftis extends StatelessWidget {
#override
Widget build(BuildContext context) {
var bloc = BlocProvider.of<CreateChallengeBloc>(context);
return BlocBuilder<CreateChallengeBloc, CreateChallengeState>(
builder: (context, state) {
return CreatePageTemplate(
progress: state.progressOfCreation,
buttonBar: NavigationButtons(
onPressPrevious: () {
bloc.add(ProgressOfCreationChanged(nav_direction: -1));
Navigator.of(context).pop();
},
onPressNext: () {
bloc.add(ProgressOfCreationChanged(nav_direction: 1));
Navigator.of(context).pushNamed("create_challenge/add_pictures");
},
previous: 'Details',
next: 'Picture',
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text(
'List the benefits of you Challenge',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
),
SizedBox(height: 30),
Text(
'Optionally: Make a list of physical and mental benefits the participants can expect. ',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.grey,
fontSize: 14,
fontWeight: FontWeight.w400),
),
SizedBox(height: 50),
Container(
margin: EdgeInsets.all(8.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Colors.yellow[600]),
child: FlatButton(
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
onPressed: () => bloc.add(ChallengeBenefitAdded()),
child: Text('Add a benefit',
style: TextStyle(
color: Colors.white, fontWeight: FontWeight.bold)),
),
),
Expanded(
child: new ListView.builder(
itemCount: state.benefits.length,
itemBuilder: (BuildContext context, int i) {
final item = state.benefits[i];
return Padding(
padding: EdgeInsets.symmetric(horizontal: 25),
child: TextFieldTile(
//key: UniqueKey(),
labelText: 'Benefit ${i + 1}',
validator: null,
initialText: state.benefits[i],
onTextChanged: (value) => bloc.add(
ChallengeBenefitChanged(
number: i, text: value)),
onCancelIconClicked: () {
bloc.add(ChallengeBenefitRemoved(number: i));
},
));
})),
],
),
);
});
}
}
The Code of the TextfieldTile:
class TextFieldTile extends StatelessWidget {
final Function onTextChanged;
final Function onCancelIconClicked;
final Function validator;
final String labelText;
final String initialText;
const TextFieldTile(
{Key key,
this.onTextChanged,
this.onCancelIconClicked,
this.labelText,
this.initialText,
this.validator})
: super(key: key);
#override
Widget build(BuildContext context) {
return Stack(children: <Widget>[
TextFormField(
textCapitalization: TextCapitalization.sentences,
initialValue: initialText,
validator: validator,
onChanged: onTextChanged,
maxLines: null,
decoration: InputDecoration(
labelText: labelText,
)),
Align(
alignment: Alignment.topRight,
child: IconButton(
icon: Icon(Icons.cancel), onPressed: onCancelIconClicked),
),
]);
}
}
The relevant portion of the Bloc:
if (event is ChallengeBenefitAdded) {
var newBenefitsList = List<String>.from(state.benefits);
newBenefitsList.add("");
yield state.copyWith(benefits: newBenefitsList);
}
else if (event is ChallengeBenefitChanged) {
var newBenefitsList = List<String>.from(state.benefits);
newBenefitsList[event.number] = event.text;
yield state.copyWith(benefits: newBenefitsList);
}
else if (event is ChallengeBenefitRemoved) {
var newBenefitsList = List<String>.from(state.benefits);
newBenefitsList.removeAt(event.number);
yield state.copyWith(benefits: newBenefitsList);
}
I can think of two things you can do here.
Create a different bloc for processing the changes in the text field, that will avoid having to actually update the state of the entire list if no needed.
Have a conditional to avoid rebuilding the list when your bloc change to a state that is relevant only to the keyboard actions.
Example:
BlocBuilder<CreateChallengeBloc, CreateChallengeState>(
buildWhen: (previousState, currentState) {
return (currentState is YourNonKeyboardStates);
}
...
);

'This expression has a type of 'void' so its value can't be used.' setState() error in Flutter

I am getting the error quoted above when I try to rebuild a class. The class I am calling, ListOfIngs(), is a class that basically creates a textField, but my goal is to create a large amount of TextFields, the variable countIngs holds the value for the exact number, in a listView. Here is the code:
body: Container(
padding: EdgeInsets.fromLTRB(10.0, 20.0, 10.0, 30.0),
child: Column(
children: <Widget>[
Row(
children: <Widget>[
Text('Ingredients',
style: GoogleFonts.biryani(fontSize: 15.0)),
IconButton(
icon: new Icon(Icons.add),
onPressed: () {
setState(() {
countings++;
});
debugPrint('$countings');
},
)
],
),
setState(() {
ListOfIngsWidget(countings);
}),
],
),
)
Try this:
class ListOfIngs extends State<ListOfIngsWidget> {
final int countIngs;
final myController = new TextEditingController();
ListOfIngs(
this.countIngs,
);
Widget build(BuildContext contex) {
return new Container(
child: ListView.builder(
itemCount: countIngs,
itemBuilder: (context,index){
return Container(
child: TextField(
controller: myController,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Ingredient $index',
),
),
);
},
),
);
}
}
A few important things first:
Every TextField needs its own TextEditingController, so in your case you need a list of TextEditingControllers.
With TextField() you create an object of type TextField. In the brackets you can't just write a for loop, because parameters to create the object are expected here.
Ingredient $countIngs' always gives you only the length. What you want to have is your variable i, which increases in every loop pass.
import 'package:flutter/material.dart';
class ListOfIngsWidget extends StatefulWidget {
final int countIngs;
const ListOfIngsWidget(this.countIngs, {Key key}) : super(key: key);
#override
_ListOfIngsState createState() => _ListOfIngsState();
}
class _ListOfIngsState extends State<ListOfIngsWidget> {
List<TextEditingController> _controllerList = [];
List<TextField> _textFieldList = [];
#override
void initState() {
for (int i=0; i<widget.countIngs; i++) {
TextEditingController controller = TextEditingController();
TextField textField = TextField(
controller: controller,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Ingredient $i',
),
);
_textFieldList.add(textField);
_controllerList.add(controller);
}
super.initState();
}
#override
Widget build(BuildContext context) {
return new Container(
child: Flexible(
child: ListView(children: _textFieldList),
),
);
}
}
You can now call the widget for example with ListOfIngsWidget(5).
The problem is with the ListView. It does not have a children: [] atribute. You need to build the widget list before
List<Widget> list = [];
for(int i = 0; i < countIngs; i ++) {
list.add(TextField(
controller: myController,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Ingredient $i',
),
));
}
Then build the ListView
...
ListView(
children: list
)
...
Note: If you are building multiple TextFields, make sure you create a different controller for each of them.
You can also use ListView.builder(), which is better for many items. You can make use of functions inside of it.

Not able to change a value in one page with respect to the value from another page in flutter

i want to change the indexvalue (pictogramindex) of one page when we click nextbutton on another screen.I will explain briefly , I have 2 screens in my scenario the first screen contains an image and it's name , a textfield and nextbutton (i have provided a dummy data contains a list of image and it's names) the logic behind this is , when we complete the textfield box and click next button(after validate) the textfield value checks with the correctvalue which i was given in the dummy data and show it's synonym which also provided. when we click the next button we will go to another page which contains the correct answer(passed from first page) and a textfield in this the user can write about the correct answer ( validated) when click next button in this page (till this my applicationworks perfectly) i want to load the first page with it's index updated (+1) which i initialised as 0 (var pictogramindex=0). But in my case when coming back to first page the index is not updating it will automatically stores the initialised value. what i want is i want to update index on the first page when i click next button in the Second page .
my source code of first screen is shown here
class Pictogramscreen extends StatefulWidget {
final int length;
const Pictogramscreen({Key key, this.length}) : super(key: key);
#override
_PictogramscreenState createState() => _PictogramscreenState();
}
class _PictogramscreenState extends State<Pictogramscreen> {
#override
final _Key = GlobalKey<FormState>();
Color defaultcolor = Colors.blue[50];
Color trueColor = Colors.green;
Color falseColor = Colors.red;
Widget defcorrect = Text('');
var pictogramindex = 0;
TextEditingController usertitleInput = TextEditingController();
nextPictogram() {
setState(() {
pictogramindex++;
});
}
fillColor() {
setState(() {
usertitleInput.text == pictdata[pictogramindex]['pictcorrectword']
? defaultcolor = trueColor
: defaultcolor = falseColor;
});
}
correctText() {
setState(() {
usertitleInput.text == pictdata[pictogramindex]['pictcorrectword']
? defcorrect = Text(pictdata[pictogramindex]['pictsynonym'])
: defcorrect = Text(pictdata[pictogramindex]['pictcorrectword']);
});
}
reset() {
setState(() {
defaultcolor = Colors.blue[50];
defcorrect = Text('');
usertitleInput.clear();
});
}
void description(BuildContext ctx) {
Navigator.of(context).pushNamed('/user-description', arguments: {
'id': pictdata[pictogramindex]['pictid'],
'word': pictdata[pictogramindex]['pictcorrectword']
});
}
Widget build(BuildContext context) {
int length = pictdata.length;
return Scaffold(
body: pictogramindex < pictdata.length
? ListView(
children: <Widget>[
Container(
margin: EdgeInsets.only(top: 20),
padding: EdgeInsets.all(15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Card(
margin: EdgeInsets.only(top: 20),
child: Image.network(
pictdata[pictogramindex]['pictimg']),
),
SizedBox(
height: 10,
),
Text(
pictdata[pictogramindex]['pictword'],
style: TextStyle(
fontSize: 25,
),
),
SizedBox(
height: 10,
),
//Card(
//color: Colors.blue,
// child: TextField(
// decoration: InputDecoration.collapsed(
// hintText: 'type here'),
//textAlign: TextAlign.center,
// onSubmitted: (value) {
// usertitleInput = value;
// print(usertitleInput);
// },
// ),
//),
Form(
key: _Key,
child: TextFormField(
controller: usertitleInput,
validator: (usertitleInput) {
if (usertitleInput.isEmpty) {
return 'Answer cannot be empty';
} else {
return null;
}
},
textAlign: TextAlign.center,
decoration: InputDecoration(
enabledBorder: OutlineInputBorder(
borderSide:
BorderSide(color: Colors.blueAccent),
borderRadius: BorderRadius.all(
Radius.circular(15),
)),
labelText: 'Type your Answer',
filled: true,
fillColor: defaultcolor,
),
onFieldSubmitted: (value) {
usertitleInput.text = value;
fillColor();
correctText();
print(usertitleInput.text);
}),
),
SizedBox(
height: 10,
),
defcorrect,
SizedBox(
height: 10,
),
RaisedButton(
onPressed: () {
if (_Key.currentState.validate()) {
description(context);
// nextPictogram();
reset();
}
//
//if (_Key.currentState.validate() == correctText()) {
// nextPictogram;
// }
},
child: Text('Next'),
)
],
),
),
],
)
: Center(
child: Text('completed'),
));
}
}
my source code of the second screen is show here
class Userinputscreen extends StatefulWidget {
final String id;
final String word;
const Userinputscreen({Key key, this.id, this.word}) : super(key: key);
#override
_UserinputscreenState createState() => _UserinputscreenState();
}
class _UserinputscreenState extends State<Userinputscreen> {
final _Keey = GlobalKey<FormState>();
TextEditingController userdescription = TextEditingController();
var pictogramindex;
void nextpict(BuildContext context) {
Navigator.of(context).pushNamed('/main-screen');
}
// void nextpict(BuildContext context, int index) {
// Navigator.push(
// context,
// MaterialPageRoute(
// builder: (ctx) => Pictogramscreen(
// index: i = 0,
// )));
// }
#override
Widget build(BuildContext context) {
final routeArgs =
ModalRoute.of(context).settings.arguments as Map<String, String>;
final correctWord = routeArgs['word'];
return MaterialApp(
home: Scaffold(
body: ListView(children: <Widget>[
Padding(
padding: EdgeInsets.only(top: 50),
child: Center(
child: Container(
padding: EdgeInsets.all(20),
margin: EdgeInsets.only(top: 100),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
correctWord,
style: TextStyle(fontSize: 26),
),
SizedBox(
height: 10,
),
Form(
key: _Keey,
child: TextFormField(
controller: userdescription,
validator: (userdescription) {
if (userdescription.isEmpty) {
return 'Answer cannot be empty';
} else {
return null;
}
},
textAlign: TextAlign.center,
decoration: InputDecoration(
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.blueAccent),
borderRadius: BorderRadius.all(
Radius.circular(15),
)),
labelText: 'Type your Answer',
filled: true,
),
onFieldSubmitted: (value) {
userdescription.text = value;
print(userdescription.text);
}),
),
SizedBox(
height: 10,
),
RaisedButton(
onPressed: () {
if (_Keey.currentState.validate()) {
nextpict(context);
}
},
child: Text('Next'),
)
],
),
),
),
),
])),
);
}
}
If I get it right, you basically want to tell the initial page that it's state is updated(the index) elsewhere. You basically need to make your app "reactive".
As is said in Google Developers Tutorial:
One of the advantages of Flutter is that it uses reactive views, which you can take to the next level by also applying reactive principles to your app’s data model.
Use some sort of state management. You need to choose from and use either Bloc, InheritedWidget and InheritedModel, Provider(ScopedModel), or the like.
Check this article on flutter about state management, or this for a complete list of approaches

Flutter - allow user enter hashtags

Hello Flutter newbie here! I want to let my users enter some hashtags linked to the entry, which will go into Firestore.
For the hashtag, I set it as a List but I'm not sure how to let user create the hashtags?
Basically something like the tags field in SO's ask a question.
On Firestore I have set the field to be an array to receive the data.
I can't find much documentation on creating hashtag on flutter.
Any help would be appreciated!! :) Thanks in advance!
Being I used dartpad to create this, I used ListView for suggestions.
You may replace with your own suggestions view like AutoCompleteTextView or something else...
List<String> list = ['Java', 'Flutter', 'Kotlin', 'Swift', 'Objective-C'],
selected = [];
TextEditingController tc;
#override
void initState() {
super.initState();
tc = TextEditingController();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text('Search Tags'),
backgroundColor: Colors.green[800],
),
body: Column(
// mainAxisSize:MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
controller: tc,
decoration: InputDecoration(
border: OutlineInputBorder(),
contentPadding: EdgeInsets.fromLTRB(20, 0, 20, 0),
prefixIcon: selected.length < 1
? null
: Padding(
padding: const EdgeInsets.only(left:10, right:10),
child: Wrap(
spacing: 5,
runSpacing: 5,
children: selected.map((s) {
return Chip(
backgroundColor: Colors.blue[100],
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(7),
),
label: Text(s,
style:
TextStyle(color: Colors.blue[900])),
onDeleted: () {
setState(() {
selected.remove(s);
});
});
}).toList()),
))),
),
SizedBox(height: 20),
ListView.builder(
shrinkWrap: true,
itemCount: list.length,
itemBuilder: (c, i) {
return list[i].toLowerCase().contains(tc.text.toLowerCase())
? ListTile(
title: Text(list[i],
style: TextStyle(color: Colors.blue[900])),
onTap: () {
setState(() {
if (!selected.contains(list[i]))
selected.add(list[i]);
});
})
: null;
})
]),
);
}