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(...);}},
Related
I need to add a loader to the button. That is, when you click on the Save button, you need to show the CircularProgressIndicator. I wanted to implement this through Bloc, because if I do this through setState (() {}), then I will have to rebuild the widget and the new added data on the page will disappear. Therefore, I want to do it through Bloc. I created a variable there and assign values to it, the values change but the loader does not update, tell me how can I make the loader show?
return BlocBuilder<EditPhotoCubit, EditPhotoState>(
builder: (context, state) {
return SizedBox(
height: size.height,
width: size.width,
child: _child(size, topPadding, context, cubitEditPhoto,
mainState.charging),
);
});
}
return const SizedBox();
});
...
SizedBox(
width: 148,
child: cubitEditPhoto.isLoading
? const CircularProgressIndicator(
color: constants.Colors.purpleMain)
: DefaultButtonGlow(
text: 'Save',
color:Colors.purpleMain,
shadowColor: Colors.purpleMain,
textStyle: Styles.buttonTextStyle,
onPressed: () async {
cubitEditPhoto.isLoading = true;
await cubitEditPhoto
.uploadImage(widget.chargingId)
.then((value) {
cubitEditPhoto.isLoading = false;
});
},
),
),
cubit
class EditPhotoCubit extends Cubit<EditPhotoState> {
EditPhotoCubit() : super(EditPhotoInitial());
bool isLoading = false;
}
I have a standard PopupMenuButton with CheckedPopupMenuItems:
PopupMenuButton<String>(
icon: const Icon(Icons.sort_by_alpha),
onSelected: (String result) {
if(result == optionOne){ doSomething(); }
},
itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
CheckedPopupMenuItem<String>(
value: optionOne,
child: Text("optionOne"),
),
),
],
),
The checkmark for which item is selected appears to the left- I'd like to have it on the right, but can't find how to do this.
EDIT:
Is the only way really to not use CheckedPopupMenuItem completely and build the functionality myself?
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),
),
);
},
);
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.
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
),
),
),
);
}