I have a list of Dismissible widgets as follows:
Dismissible(
direction: DismissDirection.endToStart,
key: Key(widget.data[i]),
onDismissed: (direction) {
widget.onRemoveRequest(i, widget.data[i]);
},
background: Container(
color: Colors.red,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(right: 20.0),
child: Text(
"Delete",
textAlign: TextAlign.right,
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w500,
fontSize: 16.0,
),
),
),
],
),
),
child: CustomTextField(
padding: const EdgeInsets.only(left: 30.0, right: 30.0),
hintText: widget.newEntryHint,
text: widget.data[i],
keyboardType: TextInputType.multiline,
onChanged: (val) {
widget.onChanged(i, val);
},
),
)
It works as expected, except for when removing matching objects.
Note: widget.onRemoveRequest removes the object at specified index from the source data, widget.data.
widget.data is a List<String>. I provide these as the key, however whenever I have two matching strings and dismiss one, I get an error because the Dismissible isn't removed from the tree (understandable).
A dismissed Dismissible widget is still part of the tree.
So with a list of strings, how can I ensure each has a unique key, even if the actual strings are equal/match?
You need to assign each data to a unique identifier. Something unique enough for it to not contain any duplicates. Then you can associate that unique identifier to a Key.
This can't be done just with a primitive object such as String or Int. You'll need to map your data to a custom object.
The following class is a good example :
class Data {
final String id;
final String title;
Data({this.id, this.title});
}
This would allows you to then do the following :
Dismissible(
key: Key(widget.data[i].id),
...
)
You can generate a custom ID for your data using uuid package, or using a custom algorithm (such as an incremental index).
But be sure that your ID is unique for each item and stays the same for the whole lifetime of that item (even after updates).
Related
To whom it may concern,
I am working on a project in which I am required to work on functionality for chat rooms. I have the bulk of the code made and most of it runs just fine, but I am having issues rendering the user message tiles to the chats scroll list, or the tile the user clicks to enter a specific chat room. My problem is that I am trying to render only as many chat rooms as there are in the database for the user, and right now there is only 1 in the database, meaning only 1 message tile should be rendered. However, when the message tile is rendered, it is just rendered infinitely down the list, over and over. Even after adding a second user to the Added Users collection in Cloud Firestore, only the first user is ever rendered, and infinitely at that. How can I make it to where there are only as many message tiles as there are added users in the database and each user in the Added Users collection is only rendered once? I need this issue fixed in order to complete the functionality of chatting in my application, since the person the user chats with is dependent on which message tile they tap.
Please note that I am using Android Studio, Flutter/Dart, Firebase Authentication (though this is not very relevant in this case), and Firebase Cloud Firestore.
To be clear, I expect to see one message tile per Added Users in the Cloud Firestore database, and to have the ability to scroll through this list of users if there are more than the screen can hold. So far, I have tried debugging to see the issue. However, I believe that the infinite render issue is an issue with the logic of my code, which is why I am posting on Stack Overflow.
Below are pictures of the chats scroll page itself and the Cloud Firestore database for this project. Below these pictures is the code responsible for the chats scroll list page:
Pictures:
The chats scroll list. Message tiles can be seen here, being infinitely rendered
Picture of the Cloud Firestore database for this project. Note that Added Users is a subcollection of a user document within the collection Users
Code:
class ChatsPage extends StatelessWidget{
const ChatsPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context){
UserObject testReceiver = UserObject("test2#gmail.com", "MyTestUser2", "Test", "2", "456", "Test", "Test", "Test", "Test", 123456, "https://imgv3.fotor.com/images/blog-cover-image/10-profile-picture-ideas-to-make-you-stand-out.jpg");
// Create a custom scroll view to list user chats.
return CustomScrollView(
// Using slivers...
slivers: [
// Create a sliver app bar.
SliverAppBar(
// Set the color to whatever is necessary.
backgroundColor: Colors.black,
// Center the title text.
centerTitle: true,
// Set the title text.
title: Text("Chats Page"),
// Create an IconBackground object to display the back arrow icon.
leading: IconBackground(
// Set the icon itself to the back arrow icon.
icon: Icons.arrow_back,
// Upon pressing the icon...
onTap: (){
// Simply return to the previous screen.
Navigator.pop(context);
}),
actions: [
Padding(
padding: const EdgeInsets.only(right: 10.0),
// Add chat functionality should be implemented here.
child: IconBackground( icon: Icons.add, onTap: (){ print("Must complete add chat functionality"); })
),
Padding(
padding: const EdgeInsets.only(right: 15.0),
child: IconBackground(icon: Icons.search, onTap: (){ print("Must complete search functionality"); })
)
],
),
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance.collection("Users").doc(FirebaseAuth.instance.currentUser?.uid.toString()).collection("Added Users").snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot){
if(snapshot.hasError) {
return const Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text("Loading");
}
return SizedBox(
height: 75,
child: ListView(
physics: const NeverScrollableScrollPhysics(),
children: snapshot.data!.docs.map((DocumentSnapshot document){
UserObject docUser = UserObject(
document.get("email"),
document.get("userName"),
document.get("firstName"),
document.get("lastName"),
document.get("userID"),
document.get("birthday"),
document.get("university"),
document.get("city"),
document.get("state"),
document.get("zipCode"),
document.get("profilePictureURL")
);
return _MessageChatTile(user: docUser);
}).toList(),
),
);
},
);
}
),
)
],
);
}
}
class _MessageChatTile extends StatelessWidget{
const _MessageChatTile({
Key? key,
required this.user
}) : super(key: key);
final UserObject user;
// There are 19 children in this one function.
#override
Widget build(BuildContext context){
// log("Hello? Does this work?");
return Material(
color: Colors.black,
child: InkWell(
onTap: () {
Navigator.of(context).push(ChatRoom.routeMessage(user));
},
child: Row(
children: [
Padding(
padding: const EdgeInsets.all(11.0),
child: Avatar.medium(url: user.profilePictureURL),
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
DefaultTextStyle(
style: const TextStyle(
fontSize: 20,
color: Colors.white,
letterSpacing: 0.2,
wordSpacing: 1.5
// fontWeight: FontWeight.w900
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(user.userName, overflow: TextOverflow.ellipsis),
// TODO: Add functionality for seeing messages outside of chat room.
//Text(messageData.message, overflow: TextOverflow.ellipsis)
],
),
)
]
),
),
Padding(
padding: const EdgeInsets.only(right: 20.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
DefaultTextStyle(
style: const TextStyle(
fontSize: 11,
letterSpacing: -0.2,
fontWeight: FontWeight.w600
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
const SizedBox(
height: 4,
),
// TODO: Add functionality for seeing message dates outside of chat room.
// Text(
// messageData.dateMessage.toUpperCase(),
// style: const TextStyle(
// fontSize: 11,
// letterSpacing: -0.2,
// fontWeight: FontWeight.w600,
// color: Colors.blueGrey
// ),
// ),
const SizedBox(
height: 8,
),
Container(
width: 18,
height: 18,
decoration: const BoxDecoration(
color: Colors.blueAccent,
shape: BoxShape.circle
),
child: const Center(
child: Text(
'1',
style: TextStyle(
fontSize: 10,
color: Colors.white
),
),
),
)
],
)
)
],
),
)
],
),
)
);
}
}
I'm trying to create a listview widget with a floating action button on my Flutter app, but it's not working because Android Studio keeps on telling me that:
"the named parameter children isn't defined"
I basically can't put children in a body: Center widget, but I don't know why
I'm basically a beginner to Flutter and I'm still a bit confused about the basic syntax, and which widgets can hold which widgets, so any help is greatly appreciated! Thank you!
Here's my overall code that won't run due to the first error (in quotation marks above):
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(
home: Home(),
));
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.green[300],
title: Text(
'Welcome',
style: TextStyle(
fontSize: 25.0,
fontFamily: 'Raleway',
letterSpacing: 1.0,
),
),
centerTitle: true,
),
body: Center(
children: <Widget> [
ListView(
children: <Widget>[
Container(
height: 50,
color: Colors.green[100],
child: Text(
'Body Text',
style: TextStyle(
fontFamily: 'Raleway',
fontSize: 45.0,
letterSpacing: 1.0,
color: Colors.green[300],
),
),
),
Container(
height: 50,
color: Colors.green[100],
child: Text(
'Text'
),
),
],
),
FloatingActionButton(
onPressed: () {},
child: Text(
'+',
style: TextStyle(
fontFamily: 'Raleway',
fontSize: 35.0,
),
),
backgroundColor: Colors.green[300],
),
]
),
);
}
}
child parameter of Center Widget has a data type of Widget and so it can't take <Widget>[] as an input. It is similar to that an int won't accept String value. They are two different data types.
It seems that you want to have a list of data that is in center of the screen: For that you can use following code.
1.
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center, // (optional) will center horizontally.
children: <Widget>[
.....
]
)
2.
Center(
child: ListView(
shrinkWrap:true;
children: <Widget>[
.....
]
)
)
It's simple. Because Center is a widget that does not take more than on widget as input.
It can only align one widget provided as a child.
For mulitple children you have to use some widget that takes a list of widgets as input.
Like:
Column
Row
ListView
Wrap
etc.
Center accepts only one widget check laytout page at https://flutter.dev/docs/development/ui/layout
Column(mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: <Widget> [
ListView(
children: <Widget>[
Container(
height: 50,
color: Colors.green[100],
child: Text(
'Body Text',
style: TextStyle(
fontFamily: 'Raleway',
fontSize: 45.0,
letterSpacing: 1.0,
color: Colors.green[300],
),
),
),
Container(
height: 50,
color: Colors.green[100],
child: Text(
'Text'
),
),
],
),
FloatingActionButton(
onPressed: () {},
child: Text(
'+',
style: TextStyle(
fontFamily: 'Raleway',
fontSize: 35.0,
),
),
backgroundColor: Colors.green[300],
),
]
),
);
Center is a widget that centres its child within itself.
So you can have only 1 child inside Center Widget.
You can take structure like,
Center(
child: Column(
children: <Widget>[
All Children you have
]
)
There are two types of widgets, one of which accepts a widget as a child and the other one accepts [Widget] as children.
Accepts a Widget as Child: Container widget, Center widget, Padding widget, , etc.
Accepts [Widget] as Children: Row widget, Column widget, Stack widget, Wrap widget, ListView widget, etc.
Center class :
A widget that centers its child within itself.
All layout widgets have either of the following:
A child property if they take a single child—
for example, Center or Container
A children property if they take a list of widgets—
for example, Row, Column, ListView, or Stack.
Add the Text widget to the Center widget:
const Center(
child: Text('Hello World'),
),
Most probably I got the concept my SliverGrid entirely wrong,
What I am trying to do is, UI wise I want the collapsible SliverAppBar which is already available in Flutter. My main content body is set of images coming from API response and already parsed and loaded to a List variable.
Now here is my code:
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(expandedHeight: 150.0, backgroundColor: Colors.green),
SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3
),
delegate: SliverChildBuilderDelegate((BuildContext context, int index){
return Padding(
padding: EdgeInsets.symmetric(horizontal: 2.0, vertical: 2.0),
child: InkWell(
child: Card(
elevation: 8.0,
child: FadeInImage.memoryNetwork(fit: BoxFit.cover,image: myimagelist[index].thumbnail, placeholder:kTransparentImage,fadeInCurve: Curves.bounceIn,),
),
)
);
}),
),
],
),
I think this is probably because I can't tell the widget that how long is my data. For GridView.builder itemCount parameter is there, but SliverGrid doesn't have such an option. What do here?
SliverGrid widget doesn't have a property named itemCount. However, if you read the docs, you'll see that it's delegate property which takes a SliverChildBuilderDelegate has a property called childCount. You can use this property to set the number of childrens you want in your slivergrid.
delegate: SliverChildBuilderDelegate((BuildContext context, int index){
return Padding(
padding: EdgeInsets.symmetric(horizontal: 2.0, vertical: 2.0),
child: InkWell(
child: Card(
elevation: 8.0,
child: FadeInImage.memoryNetwork(fit: BoxFit.cover,image: myimagelist[index].thumbnail, placeholder:kTransparentImage,fadeInCurve: Curves.bounceIn,),
),
)
);
},
childCount: 3, // Your desired amount of children here
),
I have a situation where I need to wrap text with an input in Flutter. An example: 'The cat goes <TextField>, the dog goes bark.'
I'm using the Row class to format it this way, however, the row class doesn't wrap text.
Widget _buildQuestionText(String sentence) {
List splitSentence = sentence.split('\$guess');
return new Container(
child: Row(
children: <Widget>[
new Text(splitSentence[0]),
new Expanded(child: new TextField()),
new Text(splitSentence[1]),
]
),
);
}
Which creates:
I have looked at using the Flex class but was unable to achieve the format I wanted. How can I achieve text wrapping with an input in the middle of text?
Wrap accepts a list of children that wraps while row accepts a list of children that do not wrap.
return new Container(
child: new Wrap(
spacing: 8.0, // gap between adjacent chips
runSpacing: 4.0, // gap between lines
children: [
new Text(splitSentence[0]),
new Container(
width: 100.0,
child: new TextField(
style: new TextStyle(
fontSize: 16.0,
color: Colors.black
)
)
),
new Text(splitSentence[1]),
],
)
);
I am developing a screen where I have to show suggestions list below the textfield.
I want to achieve this
I have developed this so far
Following code shows textfield with suggestions in a list.
#override
Widget build(BuildContext context) {
final header = new Container(
height: 39.0,
padding: const EdgeInsets.only(left: 16.0, right: 2.0),
decoration: _textFieldBorderDecoration,
child: new Row(
children: <Widget>[
new Expanded(
child: new TextField(
maxLines: 1,
controller: _controller,
style: _textFieldTextStyle,
decoration:
const InputDecoration.collapsed(hintText: 'Enter location'),
onChanged: (v) {
_onTextChanged.add(v);
if (widget.onStartTyping != null) {
widget.onStartTyping();
}
},
),
),
new Container(
height: 32.0,
width: 32.0,
child: new InkWell(
child: new Icon(
Icons.clear,
size: 20.0,
color: const Color(0xFF7C7C7C),
),
borderRadius: new BorderRadius.circular(35.0),
onTap: (){
setState(() {
_controller.clear();
_places = [];
if (widget.onClearPressed != null) {
widget.onClearPressed();
}
});
},
),
),
],
),
);
if (_places.length > 0) {
final body = new Material(
elevation: 8.0,
child: new SingleChildScrollView(
child: new ListBody(
children: _places.map((p) {
return new InkWell(
child: new Container(
height: 38.0,
padding: const EdgeInsets.only(left: 16.0, right: 16.0),
alignment: Alignment.centerLeft,
decoration: _suggestionBorderDecoration,
child: new Text(
p.formattedAddress,
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: _suggestionTextStyle,
),
),
onTap: () {
_getPlaceDetail(p);
},
);
}).toList(growable: false),
),
),
);
return new Container(
child: new Column(
children: <Widget>[header, body],
),
);
} else {
return new Container(
child: new Column(
children: <Widget>[header],
),
);
}
}
Header(Textfield) and body(Suggestions List - SingleChildScrollView with ListBody) is wrapped inside the Column widget, and column expands based on the total height of the children.
Now the problem is as Column expands, layout system pushes other widgets on screen to the bottom. But I want other widgets to stay on their positions but suggestion list starts to appear on top of other widgets.
How can I show suggestions list on top of other widgets? And the suggestions list is dynamic, as user types I call the Google Places API and update the suggestions list.
I have seen there is showMenu<T>() method with RelativeRect positions but it doesn't fulfills my purpose, my suggestion list is dynamic(changing based on user input) and the styling for each item I have is different from what PopupMenuItem provides.
There is one possibility I can think of using Stack widget as root widget of this screen and arrange everything by absolute position and I put suggestion list as a last child of the stack children list. But it is not the right solution I believe.
What other possibilities I need to look into? What other Widgets can be used here in this use-case?
And again use-case is simple, overlaying suggestion list on other widgets on the screen and when user tap any of the item from the list then hiding this overlaid suggestion list.
The reason why your autocomplete list pushes down the widgets below it is because the List is being expanded on the Container. You can use Flutter's Autocomplete widget and it should inflate the autocomplete list over other widgets.
var fruits = ['Apple', 'Banana', 'Mango', 'Orange'];
_autoCompleteTextField() {
return Autocomplete(
optionsBuilder: (TextEditingValue textEditingValue) {
if (textEditingValue.text == '') {
return const Iterable<String>.empty();
}
return fruits.where((String option) {
return option
.toLowerCase()
.contains(textEditingValue.text.toLowerCase());
});
},
onSelected: (String selection) {
debugPrint('You just selected $selection');
},
);
}