How to show the menu of PopupMenuButton from the code - flutter

PopupMenuButton opens a menu when user clicks on it. But how do I do open the same menu at the same place as the PopupMenuButton from the code(e.g. due to another user event)?
I am trying to create a nested menu in the AppBar. Like when user clicks on "Sort by" then menu changes to something else. Is there another way to achieve this? I could not find it on the internet.

If you take a look at the sources of PopupMenuButton you can see that it uses showMenu method which is accessible even out of the button's context (potentially comes along with imported material Dart class).
Define a GlobalKey variable and apply it to your PopupMenuButton to have a reference to the context of your button - that's how we get the position of the button on the screen.
Create an array of items for the menu, later to reuse it in both the programmatic call and the physical button itself.
Calculate the position for the menu to appear at, and make the showMenu call.
showMenu returns a Future - listen to its completion to get the chosen item.
// Define these variables somewhere accessible by both the button on `build` & by the programmatic call.
final popupButtonKey = GlobalKey<State>(); // We use `State` because Flutter libs do not export `PopupMenuButton` state specifically.
final List<PopupMenuEntry> menuEntries = [
PopupMenuItem(
value: 1,
child: Text('One'),
),
PopupMenuItem(
value: 2,
child: Text('Two'),
),
];
In your build:
PopupMenuButton(
key: popupButtonKey,
initialValue: null,
itemBuilder: (_) => menuEntries,
// ...
);
To show the menu programmatically:
// Here we get the render object of our physical button, later to get its size & position
final RenderBox popupButtonObject = popupButtonKey.currentContext.findRenderObject();
// Get the render object of the overlay used in `Navigator` / `MaterialApp`, i.e. screen size reference
final RenderBox overlay = Overlay.of(context).context.findRenderObject();
// Calculate the show-up area for the dropdown using button's size & position based on the `overlay` used as the coordinate space.
final RelativeRect position = RelativeRect.fromRect(
Rect.fromPoints(
popupButtonObject.localToGlobal(Offset.zero, ancestor: overlay),
popupButtonObject.localToGlobal(popupButtonObject.size.bottomRight(Offset.zero), ancestor: overlay),
),
Offset.zero & overlay.size, // same as: new Rect.fromLTWH(0.0, 0.0, overlay.size.width, overlay.size.height)
);
// Finally, show the menu.
showMenu(
context: context,
elevation: 8.0, // default value
items: menuEntries,
initialValue: null,
position: position,
).then((res) {
print(res);
});

Related

How do i modify the data of an existing variable in flutter?

I want to make an editable TextWidget in flutter but I don't really know how to go around it, I did some research, but still can't find a good solution.
Here's my sample code below.
I have a variable called
int qty = 1;
and so I called the variable in TextWidget
Column(
children: [
Text(
"${qty}",
style: TextStyle(),
)
],
),
I want to have these features that make user tab on the value to change it if they want, upon tap, a pop-up dialog will show to give the user the ability to change the existing value to whatever the user wants.
Please if anyone knows how, please help.
You will need a statfull widget to call setState and make the UI update with the new value stored in your qty variable. (I'am assuming that you are not using any state managment).
I wrote a possible solution for what you need.
Let look into some considerations:
Text will show whatever is in the qty as long we call setState after (or do it inside) we change the value of qty.
You need some widget to detect your tap. If you want to the text be 'clicable' then it should be wraped inside that widget.
The onTap/onPress call back of that widget should show a new widget. For this you can use the already made showDialog() and pass it a Dialog Widget. in here you will put your ui for that.
In some point of that UI you need to introduce the new value. So you can use a simple TextField that will save the introduced value, where you can assign it to qty, without forgetting to call setState! Note that it deal with strings, so you neet to do an int.parse() ou double.parse accordingly to you qty var type.
And I think that's it.
The could be other ways of doing it. This is a good and simple approach for your need.
I wrote a piece of code to help or somelse how is trying to do it:
InkWell(
// can be gesture detector, button, etc
onTap: () => showDialog(
context: context,
builder: (context) => Dialog(
child: Container(
color:
Colors.white60, // change it accordingly to you
height: 80, // change it accordingly to you
width: 200, // change it accordingly to you
child: Column(
children: [
const Text('Change your value here'),
TextField(
decoration:
InputDecoration(hintText: qty.toString()),
onChanged: (insertValue) => setState(() {
qty = int.parse(insertValue);
}),
// you can use other callBack function (like onComplete,
// onSaved), wich is more eficient than calling setState eveytime,
// but you have to do the needed adtaptions. Like onSave
// needs a key to call the save function. is easy just google it.
),
],
)),
)),
child: Text(
"${qty}",
),
),
What you are probably looking is a DropdownButton.
You would have something like this:
int qty = 1;
List<int> listOfValues = [1,2,3,4];
and then in your column you would have
DropdownButton<int>(
// This are the list of items that will appear in your dropdown menu.
// items is all the options you want your users to be able to select from,
// and it take a list of `DropdownMenuItem`. So instead of creating a `DropdownMenuItem`
// for each of the items in `listOfValues`, we iterate through it and return
// a `DropdownMenuItem`
items: listOfValues
.map((item) => DropdownMenuItem<int>(
value: item,
child: Text('$item'),
))
.toList(),
value: qty,
onChanged: (value) {
if (value != null) {
setState(() {
qty = value;
});
}
},
),
For more information on DropDownButton, check the following links:
https://api.flutter.dev/flutter/material/DropdownButton-class.html
https://www.youtube.com/watch?v=K8Y7sWZ7Q3s
Note: In a scenario where you want to increase the quantity of an item, like in a shopping cart, maybe having a button increment qty by 1 would be better.

How to animate ListView rendering in flutter?

Okay so there are a couple of questions already asked regarding this but no one is doing what I want to do.
Context :
I've a bloc which maintains a stateCounter. Now whenever user visits the onboarding page, they are shown three different subpages (not exactly pages but 3 different content items). i.e.,
stateCounter == 1 | Fetch image from map at index = stateCounter + Fetch title from map at index = stateCounter + Fetch subtitle from map at index = stateCounter
When user clicks Next button, stateCounter increases and the Map's next child is shown which re-renders my page (to move to the next item).
I'm also using a Dismissble widget on each child so rendered, so that user can also increase the stateCounter when he/she drags from end to start (to achieve a sliding effect)
Now everything works like a charm but the only problem here is that when the next data is rendered (when stateCounter increases and we fetch the next series of image, title and subtitle), the change is not so subtle.
Here, I would want to show some kind of animation so that It looks good to the user. How can i do that ??
Here is the ListView that gets rendered on the screen based on stateCounter value:
ListView onBoardingSubscreens(stateCounter, context, bloc, controller) {
return ListView(
children: [
skipBtn(context, bloc),
renderImage(stateCounter, 0, context),
const SpaceRenderer(
heightFactor: 3,
),
renderScroller(stateCounter, 1, context),
const SpaceRenderer(
heightFactor: 3,
),
titleText(context, stateCounter),
const SpaceRenderer(
heightFactor: 2,
),
captionText(stateCounter),
Timer(
controller: controller,
secondsRemaining: 6,
onFinished: () {
stateChangeLogic(stateCounter, context, bloc, controller);
controller.restart();
},
),
],
);
}
As I mentioned above, this is rendered inside : ConstrainedBox which is rendered inside a Dismissible and a Column in the end.
Kindly help. I already tried using AnimatedContainer etc. but no animations are being shown. I'm open to using a package (if needed)

MiniPlayer shows only when toggling between tabs in Flutter

I have created a Miniplayer in Flutter which looks like this:
Miniplayer(
controller: miniplayerController,
minHeight: 60,
onDismissed: (){
context.read(selectedVideoProvider).state = null;
},
maxHeight: size.height - topPadding - 60,
builder: (height, percentage){
if (selectedVideo == null) {
return const SizedBox.shrink();
}
if (height <= size.width / 16 * 9){
return Row(
children: [
Text('This is a miniplayer'),
IconButton(
onPressed: (){
context.read(selectedVideoProvider).state = null;
},
icon: Icon(Icons.close)
)
],
);
}
return AnotherScreen();
}
);
Also as you can see I have a selectedVideoProvider StateProvider (I use flutter_riverpod for a state management). Also I wrapped my Miniplayer with a Visibility widget:
Consumer(
builder: (context, watch, _){
final selectedVideo = watch(selectedVideoProvider).state;
return Visibility(maintainState: true, visible: selectedVideo != null, child: MyMiniplayerWidget());
}
),
Here is the screen of the app:
Miniplayer appears when I click on these pictures in my ListView. When I click for the first time everything works great (miniplayer appears properly). However when i dismiss my miniplayer I can not open a new miniplayer by clicking on another picture. Only when i go to another tab (tabs are on my bottom navigation bar) and then go back to this screen the miniplayer appears with the MIN size:
But I want it to pop up when I click on the pictures. And again, when I click on the picture for the first time everything works great.
What is wrong with my code? Why miniplayer appears only when I toggle between tabs?
PS. if I make maintainState = false in my Visibility widget miniplayer appears immediately after clicking on the picture, however it appears with the MIN size, but I want it to appear in MAX size. Could you please also explain me why it doesn't work, if I make
context.read(miniPlayerControllerProvider).state.animateToHeight(state: PanelState.MAX);
this when I click on the picture (this is a StateProvider for my miniplayerController) (this line of code makes my miniplayer appear with MAX size when I click on the picture when maintainState = true in Visibility widget)?
I had the same problem with miniplayer and solved it with the following code:
Miniplayer(
valueNotifier: ValueNotifier(MediaQuery.of(context).size.height),

SliverList setState to Affect Only Selected Item Flutter

I have a button on every item on my SliverList. When I click a specific list item button, I wish it change to a different widget by using setState. Only that specific item button should change to a different widget while the rest on the list retains its own item button.
Example below. The problem of course is that when I click any button on the list, all buttons on every item on the list changes. What is needed so that it affects only the specific item on the list whose button was pressed?
SliverList(
delegate: SliverChildBuilderDelegate((BuildContext context, int index) {
final item = gd[index];
return Container(
child: Column(
children: [
item.mypicwidget(context,item.pid),
_shownewwidget
? item.newwidget(context)
: RaisedButton(
onPressed: () {
setState(() {
_shownewwidget=true;
});
},
child: const Text('Press Me'),
),
]
),
);
Figured this out.
I believe the approach is to add to the existing list an object key to represent whether widget1 or widget2 to render, eg. tag: 'normal' or tag: 'clicked'. Depending on index.tag, either widget1 or widget2 will be displayed. So for a widget with a button wherein the tag is normal, the button will have a function when clicked changes the index.tag of the item from normal to clicked. When the Sliverlist sees this new value, it automatically renders the corresponding widget.

Lost FloatingActionButton State when change Portrait to Landsacpe

I'm building an apps, that contain floatingactionbutton which I made with my own custom, when it pressed it shows 3 menu icons below it.
There are 2 problems:
When the orientation change to landscape, and we press floatingactionbutton it won't show instead of it will show when we press long, continue no 2
In current landscape it shows with long press like I said in no 1, and when we back again to portrait it need long press again to make it show
I've been trying some ways to make it fix, but it still doesn't work.
Here's the code and screenshot
When portrait menu show up
When changing to landscape, menu icons are missing and need long press to make it show
for floatingActionButton
floatingActionButton: OrientationBuilder(
builder: (BuildContext context, Orientation orientation){
return orientation == Orientation.landscape
? _buildMenu(context)
: _buildMenu(context);
},
),
_buildMenu() that call floatingActionButton
Widget _buildMenu(BuildContext context){
final icons = [ Icons.swap_vert, Icons.check_circle_outline, Icons.filter_list ];
var nowOrientation = MediaQuery.of(context).orientation;
var b = Container(
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints){
return OverlayBuilder(
showOverlayTrue: true,
overlayBuild: (BuildContext overlayContext){
RenderBox box = context.findRenderObject() as RenderBox;
final center = box.size.center(box.localToGlobal(const Offset(0.8, 0.8)));
return new Positioned(
top: Offset(center.dx, center.dy - icons.length * 35.0).dy,
left: Offset(center.dx, center.dy - icons.length * 35.0).dx,
child: new FractionalTranslation(
translation: const Offset(-0.5, -0.6),
child: FabIcons(
icons: icons,
),
),
);
},
);
},
),
);}
To make it simple in viewing I put some code on github
OverlayBuilder Class https://github.com/ubaidillahSriyudi/StackOverflowhelp/blob/master/OverlayBuilderClass
Fabicons Class
https://github.com/ubaidillahSriyudi/StackOverflowhelp/blob/master/FabIcons
Thanks so much to someone that could help it
Happens because every time you change orientation, your FloatingActionButton rebuilds and loses state.
You should find a way to save the state of FabIcons widget.
Simple solution :
Constructing FabIcons widget with
icons,
iconTapped;
These variables should be saved in the Parent Widget, so every time you call _buildMenu(context); you pass in those variables