Flutter : Popup for each Listtile - flutter

I am working on a flutter project and I want to popup to get generated on clicking a particular tile. This is my code
This is my ListTile generator
Future<Widget> getRecordView() {
print("405 name " + name.toString());
print(nameArr);
var items = List<Record>.generate(int.parse(widget.vcont), (index) => Record(
name: nameArr[index],
type: typeArr[index],
address: addressArr[index],
state: stateArr[index],
phone:phoneArr[index],
city: cityArr[index],
id: idArr[index],
));
print("Started");
var listItems = items;
var listview = ListView.builder(
itemCount: int.parse(widget.vcont),
itemBuilder: (context,index){
return listItems[index] ;
}
);
return Future.value(listview);
}
The Popup I need on tap :
Future <bool> details(BuildContext context,String type) {
return Alert(
context: context,
type: AlertType.success,
title: "Submission",
desc: type, //The parameter
buttons: [
DialogButton(
child: Text(
"OKAY",
style: TextStyle(color: Colors.white, fontSize: 20),
),
onPressed: () => Navigator.pop(context),
color: Color.fromRGBO(0, 179, 134, 1.0),
radius: BorderRadius.circular(0.0),
),
],
).show();
}
I tried to wrap Record with GestureDetector and Inkwell, but I only got errors and Android Studio tells me that Record is not expected in that context. I looked up in the internet and couldnt find anything on this matter. Please help.

Record, as far I can see is just a model, and not a widget. Item Builder requires a widget. You should wrap what you are passing to the item builder with an actual widget like a Container(), ListTile(), .. etc. These widgets can be wrapped with Gesture Detector to perform the pop ups you want.
It would look like this
var listview = ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
// Tap on an item in the list and this will get executed.
},
// Return an actual widget, I'm using a ListTile here, but you can
// use any other type of widget here or a create custom widget.
child: ListTile(
// this will display the record names as list tiles.
title: Text(items[index].name),
),
);
},
);

Related

How Can I Escape Multiple Listed Widget (Button) State Management with Flutter Bloc?

I am managing states using Flutter Bloc. I have a list. When you press the + button, it is added to the list by sending a request to the API. Circular Progress Indicator is displayed in case of installation and successful icon in case of completion.
However, the situation changes for all buttons, not the relevant button. How do I change the state of only the button pressed?
return ListView.builder(
itemCount: state.model.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(
state.model[index].title ?? "",
style: TextStyle(fontWeight: FontWeight.w500),
),
subtitle: Text("${state.model[index].subTitle}"),
trailing: BlocConsumer<CarCubit, CarState>(
listener: (context, state) {
if (eatState is CarError) {
final snackBar = SnackBar(
content: Text(state.message ?? ""),
);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
}, builder: (context, state) {
return TextButton(
onPressed: state is CarInitial
? () {
context.read<CarCubit>().addList(
CarModel(
title: state.model[index].title,
category: state.model[index].category,
image: state.model[index].image,
));
}
: null,
child: carStateWidget(state));
}));
});
Widget carStateWidget(CarState state) {
if (state is CarLoading) {
return CircularProgressIndicator(
color: Colors.white,
);
} else if (state is CarCompleted) {
return Icon(
Icons.done,
color: Colors.white,
);
}
return Text("+", style: TextStyle(color: Colors.white));
}
You can create carId instance to the CarLoading state class and when emitting it just check if the current carId of the loading button of this car object (I have supposed that each car is an object and has a carId instance) matches the received id from the state.
Something like that:
Widget carStateWidget(CarState state, int carId) { // carId
if (state is CarLoading && state.carId == carId) { // check
return CircularProgressIndicator(
color: Colors.white,
);
} else if (state is CarCompleted) {
return Icon(
Icons.done,
color: Colors.white,
);
}
return Text("+", style: TextStyle(color: Colors.white));
}
Note, don't use methods to create widgets as this is not recommended even officially by flutter team for many reasons, you can check this.
Divide to win
Create a CarWidget with its own private cubit:
When a new element is added then a new CarWidget is added into the ListView. This widget is a simple Row with the car label AND the + button.
When the user press the + button then you can use a cubit to request your API, but note that this cubit is private to this CarWidget instance. This cubit can be initialized into initState.
A Cubit (or a Bloc) is a state management. As you mentioned each button (or row) must have its own state, so it's better and easier to have its state as close as possible to the widget.
Advantages:
1- It will be impossible to a widget's state to interfere with another widget
2- You won't build the whole tree but only the relevant widget.
3- The user will be able to add multiple cars even without waiting for the previous to complete (which is impossible with your code).

Flutter Dynamic PopupMenu Content

I'm trying to create a menu that has a 'load more' functionality. From an interface perspective, PopupMenuButton has worked nicely, but I've been unable to dynamically refresh its content.
I'm using redux and I can successfully dispatch the action to fetch more, and the store is updated, but I don't see the change until I close the menu and re-open it, despite wrapping the PopupMenuButton in a StoreConnector. I also have a check for fetchInProgress that should be changing the bottom 'more' item to a spinner while the fetch is in progress, but that state change isn't noticed either.
I'm relatively new to Flutter so I'm wondering if I'm missing something.
Gif of the behavior
#override
Widget build(BuildContext context) {
return StoreConnector<AppState, _ViewModel>(
converter: (store) => _ViewModel.fromStore(store, oneOnOneId),
builder: (ctx, vm) => PopupMenuButton(
onSelected: (callback) => callback(),
icon: Icon(Icons.expand_more),
itemBuilder: (_) =>
[...vm.pastOneOnOnes.map((m) {
return PopupMenuItem(
child: Center(child: Text(DateFormat('MM/dd/yyyy').format(m.meetingDate))),
value: () => {
Navigator.of(context).pushReplacementNamed(routeName,
arguments: {
'meetingId': m.id
})
}
);
}).toList(),
PopupMenuItem(
enabled: false,
child: Container(
height: 40,
width: double.infinity,
child: vm.fetchInProgress ?
Center(child: CircularProgressIndicator()) :
InkWell(
onTap: () => vm.fetchPastOneOnOnes(oneOnOneId, start: vm.pastOneOnOnes.length + 1),
child: Center(
child: Text('More', style: TextStyle(color: Colors.black))
)
),
),
value: null
)
]
),
);
}
}
You need to update the state when you make a change. When you call => vm.fetchPastOneOnOnes wrap it with setState :
onTap: () {
setState(){
vm.fetchPastOneOnOnes(...);}},

Flutter list view builder showing selected name instade of id

I'm new to flutter and I want to use list view using flutter list view builder.
I used this data model(user.equipments) to build the list view
in here equipmentName comming from equipment model as show following
Here is the actual issue, I need to buind the equipment name from equipment model instead of buinding this id "1603739590802". I tried few ways, but those were not woking as expected.
here is the current result
here is my code for list view builder
Container(
width: size.width,
child: ListView.builder(
shrinkWrap: true,
itemCount: _user.getSingleUser.equipments.length,
itemBuilder: (context, index) {
return ListTile(
tileColor: Colors.blue[100],
onTap: () {
_addModalBottomSheet(bc,size,_user.getSingleUser.equipments[index].id);
},
leading:Icon(Icons.fiber_manual_record_rounded),
trailing: IconButton(
onPressed: () {
singleUser.equipments.removeAt(index);
_user.setSingleUser(singleUser);
},
icon: Icon(
Icons.close_outlined,
color: Colors.red,
),
iconSize: 20,
),
title: Text(
'${_user.getSingleUser.equipments[index].equipmentName} :
${_user.getSingleUser.equipments[index].yearsOfUsing} years',
),
);
},
),
),
Any suggestion would be appreciated.
Assuming equipmentList is the list of equipments :
equipmentList.firstWhere(
(e) => e.id == _user.getSingleUser.equipments[index].equipmentName).name
The name equipmentName is misleading, it should be equipmentId.

Cupertino Picker content is squished together (see image below)

I created a Cupertinopicker widget that contains the 7 days of the week, but when you open the picker, they appear very squished together. Not sure why this is as another Cupertinopicker I created works fine.
I've copied exactly the code for the other Cupertinopicker I implemented (obviously changing the necessary components of the code), but it doesn't work in my second implementation.
Widget _buildWeeklyItemPicker() {
return Container(
height: 250,
child: CupertinoPicker(
itemExtent: 7.0,
backgroundColor: CupertinoColors.white,
onSelectedItemChanged: (index1) {
setState(() {
selectedWItemString2 = daysOfTheWeek[index1];
});
},
children: List<Widget>.generate(
daysOfTheWeek.length,
(index1) {
return Center(
child: Text(daysOfTheWeek[index1]),
);
},
),
),
);
}
^^ that's building the CupertinoPicker
List<String> daysOfTheWeek = [
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday'
'Sunday'
];
^^ that's my list of items that should appear in the picker
InkWell(
child: Text(
selectedWItemString2 == null ? "_______" : selectedWItemString2),
onTap: () async {
await showModalBottomSheet<int>(
context: context,
builder: (BuildContext context) {
return _buildWeeklyItemPicker();
},
);
},
),
^^ tapping the InkWell above open up the CupertinoPicker
I have attached a picture of what it should look like and what it does look like.
It is because you've set itemExtent: 7.0. Increase the value according to your requirement.

Flutter call back to originating widget on back button

I have a Future Builder that builds a ListView.builder, The builder ListTiles are build by another widget that have an ontap function. I have figured out how to get something to run on the final widget by using the back button, but have not been able to figure out how to call something on the original widget on back button. For instance, I need to refresh the top level data when the user clicks back button and not just the data in the secondary widget which is already working.
I hope this makes sense, any help would be great.
UPDATE Here is the code. I am simplifying what I am showing because making a simple example will lose the context. Below you see that I have a FutureBuilder that returns a ListBuilder that returns a new ChatWidget. This is the top level, this Future needs to be refreshed when I click on the back button. However the onTap to trap the callback is in the ChatWidget.
new Expanded(
child: new RefreshIndicator(
child: new FutureBuilder<List<Map>>(
future: chatlist,
builder: (BuildContext context, AsyncSnapshot<List<Map>> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none: return new Text('Waiting to start');
case ConnectionState.waiting: return new Text('Loading...');
default:
if (snapshot.hasError) {
return new Text('Error: ${snapshot.error}');
} else {
return new ListView.builder(
itemBuilder: (context, index) {
ChatServerList mychat = new ChatServerList(snapshot.data[index]['msgkey'],snapshot.data[index]['refid'],snapshot.data[index]['referralname'], snapshot.data[index]['oid'],snapshot.data[index]['sendname'],snapshot.data[index]['pid'],snapshot.data[index]['receivename'],snapshot.data[index]['pgrpid'],snapshot.data[index]['prid'],snapshot.data[index]['message'],);
bool isgroup = true;
if (mychat.grpid == 0) {
isgroup = false;
}
return new ChatWidget(mychat: mychat,threaded: threaded, isgroup: isgroup);
},
itemCount: snapshot.data.length,
);
}
}
},
),
onRefresh: _onRefresh
),
)
This is built in the ChatWidget, you notice the _onTap:
new MyListTile(
leading: new CircleAvatar(
child: _chatAvatar(),
//child: !isgroup ? _indMsg() : _grpMsg(), radius: 18.0,
),
//subtitle: new Text(widget.mychat.oname),
title: new Text(widget.mychat.referralname),
trailing: new Text(widget.mychat.oname, textAlign: TextAlign.right,),
//isThreeLine: true,
//onTap: doTap(threaded),
onTap: _onTap,
onLongPress: _doArchive,
),
new MyListTile(
leading: new Text(' '),
title: new Text(submymessage, textAlign: TextAlign.left,
style: new TextStyle(fontSize: 15.0,
color: Colors.grey, fontStyle: FontStyle.italic),),
trailing: _unreadBabdge(),
onTap: _onTap,
onLongPress: _doArchive,
),
That _onTap is below and has the code to handle the back button.
_onTap() async {
ChatDB.instance.updateRead(widget.mychat.msgkey);
if (threaded) {
//TODO
} else {
Route route = new MaterialPageRoute(
settings: new RouteSettings(name: "/ChatServerDivided"),
builder: (BuildContext context) => new ChatServerDivided(mychat: widget.mychat, title: "Chat Messages",),
);
//Navigator.of(context).push(route);
var nav = await Navigator.of(context).push(route);
if(nav==true||nav==null){
unread = ChatDB.instance.getUnread(widget.mychat.msgkey);
}
}
}
So what I am trying to find is if this code can somehow commmunicate up to the originating widget so that I can run the original Future again. I hope this makes more sense and is easier to understand.
Yes you can do that. Couldn't see exactly where to fit it into your code but I'll give you the way to handle this. The navigator calls are all Futures which means you can await them on the calling side. It seems like you're just missing passing a value to the .pop call. Below is an example.
Where you navigate you can await for your result
var navigationResult = await Navigator.push(
context,
new MaterialPageRoute(
builder: (context) => Page2()));
Then you can check the navigationResult with a simple if.
if(navigationResult == 'rerun_future') {
uploadFiles(); // Perform your custom functionality here.
}
The way you pass that information back is that when you do a pop call (to navigate back) you'll pass the value 'rerun_future' in there.
Navigator.of(context).pop('rerun_future')
Additionally if you also want to add this functionality to the back button you should surround your Scaffold with WillPopScope, return false to onWillPop and supply a leading item to the appBar where you perform your custom pop call. Example below from this post
#override
Widget build(BuildContext context) {
return new WillPopScope(
onWillPop: () async => false,
child: new Scaffold(
appBar: new AppBar(
title: new Text("data"),
leading: new IconButton(
icon: new Icon(Icons.ac_unit),
onPressed: () => Navigator.of(context).pop('rerun_future'), //<----- pass value here too
),
),
),
);
}