Flutter PopupMenuButton - don't close the menu when selected - flutter

I have a list of checkbox items (PopupMenuItem) inside my popup menu, which is triggered by a popupMenuButton. I want the user to be able to select a number of checkboxes, but as soon as one item is selected it closes the window.
Is there any way to prevent this? I need it to stay open, or alternatively force it open again immediately.
(I tried creating my own PopupItem class to override the "handleTap()", but I need to update the state of the parent menu view, which I can't call from another class. So Ive removed that again.)
class TopicsNotificationMenu extends StatefulWidget {
List<Topic> topics = [];
TopicsNotificationMenu(this.topics);
#override
_TopicsNotificationMenuState createState() =>
_TopicsNotificationMenuState();
}
class _TopicsNotificationMenuState extends State<TopicsNotificationMenu> {
_TopicsNotificationMenuState();
_updateTopics(_tp){
setState(() {
if(_tp.value == true){
_tp.value = false;
}else{
_tp.value = true;
_registerTopic(_tp.name);
}
});
}
#override
Widget build(BuildContext context) {
return PopupMenuButton(
onSelected: (value) {
_updateTopics(value);
},
itemBuilder: (BuildContext context) {
return widget.topics.map((var tp) {
var _icon = (tp.value == true) ? Icons.check_box : Icons.check_box_outline_blank;
return PopupMenuItem(
value: tp,
child: ListTile(
leading: Icon(_icon),
title: Text(tp.name),
),
);
}).toList();
});
}

You can try to reopen it each time after the user's selection. I made an example here
Alternatively, I would advise creating your own widget with the desired behaviour.

I had to create my own widget for this. In summary, I wanted a floating list in the top-right of my screen with a list of checkboxed items. When I press the items they become checked/unchecked but the window remains open until I click off it. I used the following widget tree:
An OverlayEntry widget so that I could place it anywhere floating above the app
-> added the SafeArea widget so that my padding would include the notification bar at the top of the phone
-> a Gesture Detector, so that on tapping off it I could close it
-> a column with CrossAxisAlignment.end so that it was placed in the top-right
-> a container widget with some padding
-> a Material for elevation shading and to contain a list
-> The Listview
-> The List Tiles with icon and Text for each item. The icon was either the ticked or unticked graphic, depending on it's value (which is stored as a boolean in an array)
onTap of a ListTile it updated the state of the widget and dispayed it again. There is no visual blinking, it is instant.
onTap of the Gesture Detector it simply removes the widget.

Related

Scroll position lost if tab changed while scrolling animation is still ongoing

I have 3 views which are accessible via the bottom navigation tab. Each view has its own ListView, which looks like this:
// primary = bottomTabNavigation.index //
ListView(
controller: primary ? null : scrollController,
key: const PageStorageKey<String>('view1'),
primary: primary,
physics: primary
? AlwaysScrollableScrollPhysics()
: NeverScrollableScrollPhysics(),
children: const [
Text("A"),
SizedBox(height: 1000),
Text("B"),
],
),
If I start a big swipe on view1, and switch to view2 via bottom tab navigator, the scroll position when I come back to view1 is still at the top. Somehow, the scroll position only saves upon the scrolling animation completing.
Is there some way to switch tabs and store the last position (without waiting for animation)?
Create a Key outside the build method
final _key = GlobalKey();
Step 1: make your widgets staefulWidget.
Step 2: now use AutomaticKeepAliveClientMixin using with keyword.
class _DealListState extends
State<DealList> with
AutomaticKeepAliveClientMixin<DealList>
{
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
// your current widget build
}}
It will keep your listview and other states when you moves from one page to another.
Note: if it's impossible to change every page to stateful widget then just make a new StatefulWidget that use AutomaticKeepAliveClientMixin and will take a child widget from outside and now you can use this widget to wrap your already present widget and can be used through the app.

Custom Event listeners in flutter

I have a widget with a list and a button with a tree dot icon in every row that shows and hides a panel in its own row. I only want one panel open in the list. When I click on a row button, I'd like to close the panels of the other rows list.  All the buttons in the list are siblings. I'd like to send an event to the other rows' code to close the panels. Which is the correct manner of flutter?  
I have tried NotificationListener but it does not work because the components to be notified are not their parents.
The question is if the correct thing to do is to use the event_listener library or to use streams. I'm new to flutter/dart and streams seem too complex to me. It's a very simple use case and in this entry
Flutter: Stream<Null> is allowed?
they say
*
Some peoples use streams as a flux of events instead of a value
changing over time, but the class isn't designed with this in mind.
They typically try to represent the following method as a stream:
So with simple events with 0 or 1 argument. event_listener or Streams?
This is the screen I'm working on. I want that when one yellow button panel opens the other one closes.
Your question is broad and it seems to be a design question, i.e. it doesn't have a right answer.
However, I don't think you should use Streams or EventListeners at all in this case, because you should not make components in the same layer communicate with each other. Components should only communicate with their parents and children, otherwise your code will increase in complexity really fast. That's even documented in flutter_bloc.
Other than that, if you don't lift state up, i.e. move the responsibility of triggering the removal of the other rows to a parent Widget, than you're fighting against Flutter instead of letting it help you.
It's easy to create a parent Widget, just wrap one Widget around it. What you want to do is hard, so why would try to communicate with sibling widgets instead of using what's Flutter designed to do?
This is a suggestion:
class _NewsSectionState extends State<NewsSection> {
Widget build(BuildContext context) {
return ListView.builder(
itemCount: newsInSection.length;
itemBuilder: (_, int index) => NewsTile(
title: Text('${newsInSection[index].title}')
onDismiss: () => onDismiss(index),
// I don't know how you set this up,
// but () => onDismiss(Index)
// should animate the dismiss of the Row with said index
),
);
}
}
class NewsRow extends StatefulWidget {
final void Function() onDismiss;
#override
State<NewsRow> _createState => _NewsRowState();
}
class _NewsRowState extends State<NewsRow> {
Widget build(BuildContext context) {
return Row(
children: [
// title
// home button
// fav button
// remove button
IconButton(
Icons.close,
onPressed: widget.onDismiss,
),
],
);
}
}

How to prevent subtree with callback from rerendering in Flutter?

I'm new to Flutter and have some performance concerns. For my app, I have created a custom sidebar menu, inspired by
For this purpose, I created a stateful top-level widget that acts as the parent screen. It contains a Stack widget with the navigation screen on the button, and a content screen on top. The user should be able to open/close the menu in two ways:
By pressing the hamburger menu icon at the top left of the content screen (either when it is fully opened, or moved to the side as in the first pic)
By swiping right when the menu is open, and left when the menu is closed.
To satisfy point 2, I added a GestureDetector on the parent screen, such that the swipes are detected in the entire screen, which animates the content screen to the side/back in full view. To satisfy point 1, I pass an onPress callBack to the content screen (which passes it to the hamburger iconButton), which also does the top level animation. However, reading the documentation (stateful performance considerations), it seems that such a top-level stateful widget can be harmful for performance, as the rebuild passes down. I can't make my content screen a const widget (which is a proposed solution) because of the callback. This is obviously suboptimal, since in the content screen, only the icon has an animated change when the menu opens (the icon changes from a hamburger to an arrow).
How can I minimize the number of rerenders in the subtree? Is there a way to pass the screen as a const widget, even though it has a callback? Or is the current approach satisfactory?
The code, as I have it currently, is as follows:
class ParentScreen extends StatefulWidget {
const ParentScreen({Key? key}) : super(key: key);
#override
_ParentScreenState createState() => _ParentScreenState();
}
class _ParentScreenState extends State<ParentScreen> {
bool isMenuOpen = false;
double xOffset = 0;
double yOffset = 0;
double rotationAngle = 0;
double scaleFactor = 1;
double toRadians(double degrees) => degrees * math.pi / 180.0;
void animateMenu() {
setState(() {
...
isMenuOpen = !isMenuOpen;
});
}
#override
Widget build(BuildContext context) {
return SafeArea(
// Detect user swipe to navigate between the screens
child: GestureDetector(
onHorizontalDragEnd: (DragEndDetails details) {
if (details.primaryVelocity != null) {
if (details.primaryVelocity! > 0) {
// Right swipe, close menu if open
if (isMenuOpen) animateMenu();
} else if (details.primaryVelocity! < 0) {
// Left swipe, open menu if closed
if (!isMenuOpen) animateMenu();
}
}
},
child: Scaffold(
body: Stack(
children: <Widget>[
const DrawerScreen(), // Screen with navigation information
AnimatedContainer(
transform: Matrix4.translationValues(xOffset, yOffset, 0)
..scale(scaleFactor)
..rotateZ(toRadians(rotationAngle)),
duration: const Duration(milliseconds: 300),
child: HomeScreen( // Screen with custom content
onMenuPress: animateMenu,
),
),
],
),
),
),
);
}
}
Well, you don't need to make the parent widget a stateful widget.
First make the actual menu including it's animation it's on widget which draws over everything else.. (Similar to what you said in a Stack widget).
Then create a object (typically called a BLoC in flutter-world) which lives outside the widget tree - either a ChangeNotifier or a Stream and inject it into the stateless widgets (easiest is by using the provider package, but you can also use an InheritedWidget.
When you want to show the menu you would just change the state of this external object which will notify the menu widget to expand.

How do I keep my widget state when reordering them via Draggable&DragTarget?

I'm using this reorderables package. This package works by having a list of children widgets that are each wrapped with a Draggable and put inside a DragTarget. Before that the childs key is assigned to a GlobalObjectKey.
After the dragTarget is created, it is assigned(or rebuild?) to a KeyedSubTree:
dragTarget = KeyedSubtree(key: keyIndexGlobalKey, child: dragTarget);
According to the comments in the package source code, this should preserve the child widgets state (toWrap) when being dragged:
// We pass the toWrapWithGlobalKey into the Draggable so that when a list
// item gets dragged, the accessibility framework can preserve the selected
// state of the dragging item.
final GlobalObjectKey keyIndexGlobalKey = GlobalObjectKey(toWrap.key);
The reordering itself happens not with the DragTarget accepting the Draggable dragged into it, but rather by using the DragTarget around each child to get index of the current position the Draggable is hovering over. When the Draggable is let go, a reorder function will get called, which removes the widget (that was being dragged) from the list and inserting it into the new position.
Now comes my problem: The state of the widget is not being preserved. I made a simple TestWidget to test this:
class TestWidget extends StatefulWidget{
#override
_TestWidgetState createState() => _TestWidgetState();
}
class _TestWidgetState extends State<TestWidget> {
Color boxColor;
#override
void initState() {
super.initState();
boxColor= Colors.blue;
}
#override
Widget build(BuildContext context) {
return Column(
children: [
Container(
decoration: BoxDecoration(color: boxColor),
child: Text("Test"),
),
FlatButton(
onPressed: (){
setState(() {
boxColor = Colors.red;
});
},
padding: EdgeInsets.all(8.0),
child: Text("Change to Red"),
color: Colors.grey,
)
],
);
}
}
This widget has a Container with a initial blue background (boxColor) and a button. When the button is pressed, it will change the boxColor to red. The moment the dragging on the widget is initiated, it is rebuild and defaults to the initial state (at least the Draggable feedback is). After the reordering that doesn't change and the widget is still in it's default state.
My plan here is to have a list of different custom widgets, where the User can modify their content and if they are not happy with the order, they can drag those widgets around and rearrange them.
My question is: How do I preserve the state of my widgets?
I'm thinking of creating a class for each widget with all state relevant variables and use that to build my widgets but that seems very bloated and not really in the mind of flutter. Isn't that supposed to be the role of the state of the StatefulWidget?
EDIT:
So I solved my problem by creating an additional class for my widget state with ChangeNotifier and then moving all my variables that I want to keep track of into this class. So I basically now have two lists, one for my widgets in the reorderable list and one for their states. I still think that this is kinda scuffed. If a widget in my list has additional children of its own, I would need to create separate state classes for each of them that need it and save them somewhere. This can get very messy, very quickly.

How do I show AlertDialog in Flutter

I am starting to learn Flutter and am working on a Calculator app. When I want to prevent the user from some action (let's say divide by zero), I want to display a Dialog showing an error message. This requires a context, but when I pass context, this results in an error.
The examples that I have seen that do display an alert dialog all appear to be the result of a button being pressed, and this uses the context that is present when the app Widget is created. My situation is that the dialog is displayed outside the creation of the widget, and it appears that the context is not valid there.
How can I display a dialog as a result of an action taken by the user rather than the clicking a button within the Widget that has been created for the app? An example would be great.
The error that I am getting is as follows:
I/flutter ( 6990): The getter 'modalBarrierDismissLabel' was called on null.
While I presume from what I have read that I need to restructure the code and reposition the Alert Dialog, I have no idea how to do that. The examples that I have seen that work result from a Widget created on construction that consequently uses the context available at that point. In my case, I'm attempting to create the alert dialog as a result of an outcome from the result of what a user has done, not from the pressing of a widget button.
Some of my relevant code is as follows:
} else if (pendingOperator == "/") {
if (secondValue != 0) {
setNewValue(Decimal.parse(firstValue.toString()) /
Decimal.parse(resultString));
} else {
_showAlert(context, "Divide by zero is invalid");
}
}
class MyAppState extends State<MyApp> {
Decimal firstValue;
String pendingOperator;
bool clearCurrentValue = true;
String resultString = "0";
void _showAlert(BuildContext context, String text) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text("Error"),
content: Text(text),
));
}
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new SafeArea(
child: new Material(
color: Colors.black,
child: Column(
The need to show the alert is indirectly the result of a button being pressed. When that button is created, pass the context to the function that is called and use that context in the call to _showAlert.