flutter opening a drawer using a button inside a scaffold body - flutter

What I have
A custom button with onpress to open a drawer, my build snippet : (inside MyClassState)
Widget build(BuildContext context) => Scaffold(
key: _key,
body: Container(
child: Column(
children: [
Row(children: [
ElevatedButton(
child: Padding(
padding: const EdgeInsets.all(5.0),
child: Icon(
Icons.settings,
size: 38,
color: Colors.white,
),
),
onPressed: () => _key.currentState?.openEndDrawer(),
),
]),]),))
method globalkey _key is used (after reading some solution here)
Class MyClassState extends State<MyClass> {
GlobalKey<ScaffoldState> _key = GlobalKey();
...
}
What I expected
The drawer opens on press/tap
The current behaviour result
Nothing happens on tap, but I can open the drawer using slide gesture.
What did I do wrong in this case?

You didn't declared endDrawer in scaffold, Here is the your updated code
Widget build(BuildContext context) => Scaffold(
key: _key,
endDrawer: Drawer( /// this is missing in your code
child: Container(
width: 200,
color: Colors.red,
),
),
body: Container(
child: Column(
children: [
Row(children: [
ElevatedButton(
child: Padding(
padding: const EdgeInsets.all(5.0),
child: Icon(
Icons.settings,
size: 38,
color: Colors.white,
),
),
onPressed: () => _key.currentState?.openEndDrawer(),
),
]),]),))

Related

GetX update a widget with list of widgets

So what I want to do is pretty simple I have an integer called currentStep and I also have a list of widgets inside a builder when I click the button its updates both currentStep value and the widget that should be returned.
Here's the code:
Widget StackPositioned(BuildContext context) {
return Positioned(
top: 260,
width: MediaQuery.of(context).size.width,
child: Container(
width: MediaQuery.of(context).size.width,
margin: const EdgeInsets.symmetric(horizontal: 15),
child: Column(
children: <Widget>[
RegistrationHeader(context),
const Gap(20),
RegistrationSteps(context)[currentStep.value],
],
),
),
);
}
List of widgets:
List<Widget> RegistrationSteps(BuildContext context) {
return [
Column(
children: <Widget>[
Label(context, "Tam Adınız"),
const Gap(10),
FullNameInput(context),
const Gap(15),
Label(context, "E-Posta"),
const Gap(10),
EmailInputField(context),
const Gap(15),
Label(context, "Şifreniz"),
const Gap(10),
PasswordInputField(context),
....
And the updater:
TextButton(
onPressed: () => currentStep.value++,
style: TextButton.styleFrom(
backgroundColor: Colors.purple,
minimumSize: Size(MediaQuery.of(context).size.width, 50),
),
child: Text(
"Devam Et",
style: Displays.display2.copyWith(color: Colors.white),
),
),
My integer:
RxInt currentStep = 0.obs;
Edit: I have done this with setState() but, GetX is the project requirement.
Just wrap your RegistrationSteps(context)[currentStep.value],widget with Obx.
like this
Obx(() {
return RegistrationSteps(context)[currentStep.value];
});
It will make RegistrationSteps widget observable. that means if anyvalue changes inside it , it will update itself.

ListTile Card color onTap

I'm trying to implement the call button inside a card widget,
I want the whole card background to change color to blue (like selected) when I press the call button, and to be changed back to normal when I press any other card, like to make the call button switch for card selection,
tried to use the setState function but it didn't work since it changes color only when I'm tapping the whole card not a specific button in it.
How do I make the whole card selected when I press the call button and released when I press any other card (after I get back from the dialer application)
Here's my code:
_launchCaller() async {
const url = "tel:+972545522973";
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
}
return Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Card(
margin: EdgeInsets.fromLTRB(20.0, 6.0, 20.0, 0.0),
color: Colors.brown[30],
child: ListTile(
isThreeLine: true,
title: Row(
children: <Widget> [
Container(
child:Text(widget.helpRequest.category.description) ,
alignment: Alignment.topLeft,
),
Spacer(),
Container(
child:Text(formatter.format(now)),
alignment: Alignment.topRight,
),
]
)
,
subtitle: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
GetUserName(widget.helpRequest.sender_id, DataBaseService().userInNeedCollection),
Text(widget.helpRequest.description),
]
)
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
GestureDetector(
onTap: (){
_launchCaller();
** Here i think I should add the code **
},
onLongPress: () => print("Long Press: Call"),
child: Padding(
padding: EdgeInsets.all(16.0),
child: Icon(Icons.call,
size: 20.0,
color: Colors.green,
)
),
),
],
),
),
),
);
And this setState function I tried to use which didn't work well in my case (I was changing state on the onTap function):
void initState() {
super.initState();
color = Colors.transparent;
}
Final Output:
You can set the color of a specific card, but for that to happen, you need to have some way to reference that the selected card was clicked on, with this reference we can decide whether the card is selected and if yes then change the color according to our preference.
In the following example, I am more or less using the same card widget template that you stated in the question, then I am using the ListView.builder to render five cards, each having the same functionality.
Whenever the call button is pressed, the corresponding index of that specific card is assigned to the state selectedIndex and from this, we can assign the color to the selected Card.
Here is the full example:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int selectedIndex;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Padding(
padding: const EdgeInsets.only(top: 8.0),
child: ListView.builder(
itemCount: 5,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Card(
margin: EdgeInsets.fromLTRB(20.0, 6.0, 20.0, 0.0),
color: index == selectedIndex
? Colors.amberAccent
: Colors.brown[30],
child: ListTile(
isThreeLine: true,
title: Row(children: <Widget>[
Container(
child: Text("Some Text"),
alignment: Alignment.topLeft,
),
Spacer(),
Container(
child: Text("Some Text"),
alignment: Alignment.topRight,
),
]),
subtitle: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("Some Text"),
])),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
GestureDetector(
onTap: () {
setState(() {
selectedIndex = index;
});
},
onLongPress: () => print("Long Press: Call"),
child: Padding(
padding: EdgeInsets.all(16.0),
child: Icon(
Icons.call,
size: 20.0,
color: Colors.green,
),
),
),
],
),
),
),
);
},
),
),
);
}
}
Keep track of the clicked item and pass the index of the list
int clickedItemPosition = -1;
bool isChecked(currentPosition) => clickedItemPosition == currentPosition;
Then in your card
//..
Card(
margin: EdgeInsets.fromLTRB(20.0, 6.0, 20.0, 0.0),
color: isChecked(index) ? Colors.blue : Colors.transparent,
//..
In Gesture detector update the clickedItemPosition
//...
GestureDetector(
onTap: () => setState(() => clickedItemPosition = index),
//..

Is there a way to have a floatingActionButton and a SpeedDial together?

I'm experimenting with SpeedDial and I wanted to know if it's possible to have a SpeedDial and a floatingActionButton in the same screen.
The next code has worked for me for having two FloatingActionButton, but when adding the speeddial, the limits of the screen go to hell (it says bottom overflowed by infinity pixels).
Widget build(BuildContext context) {
return Scaffold(
appBar: searchBar.build(context),
body: Container(),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
buildSpeedDial(),
FloatingActionButton(
onPressed: (){},
child: Icon(Icons.add),
backgroundColor: Colors.green,
),
FloatingActionButton(
onPressed: (){},
child: Icon(Icons.add),
backgroundColor: Colors.green,
),
],
)
);
}
I'm using the same buildSpeedDial() as in the example of flutter_speed_dial 1.2.5 page.
The console shows this:
Error: Cannot hit test a render box that has never been laid out.
The hitTest() method was called on this RenderBox: RenderFlex#aa804 NEEDS-LAYOUT NEEDS-PAINT:
needs compositing
creator: Column ← Container ← Positioned ← Stack ← SpeedDial ← Column ← Transform ← RotationTransition ← Transform ← ScaleTransition ← Stack ← _FloatingActionButtonTransition ← ⋯
parentData: right=0.0; bottom=0.0; offset=Offset(0.0, 0.0)
constraints: MISSING
size: MISSING
direction: vertical
mainAxisAlignment: end
mainAxisSize: max
crossAxisAlignment: end
textDirection: ltr
verticalDirection: down
Unfortunately, this object's geometry is not known at this time, probably because it has never been laid out. This means it cannot be accurately hit-tested.
If you are trying to perform a hit test during the layout phase itself, make sure you only hit test nodes that have completed layout (e.g. the node's children, after their layout() method has been called).
One solution would be to constrain the speedDial as follows:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Flutter Speed Dial')),
body: buildBody(),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
SizedBox(
height: 60,
width: 60,
child: buildSpeedDial(),
),
SizedBox(height: 10),
FloatingActionButton(
onPressed: () {},
child: Icon(Icons.add),
backgroundColor: Colors.green,
),
SizedBox(height: 10),
FloatingActionButton(
onPressed: () {},
child: Icon(Icons.add),
backgroundColor: Colors.green,
),
],
),
);
}
Which would give this result:
SpeedDial is using the Stack widget while building itself. So, I have a dirty solution using Stack, too:
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_speed_dial/flutter_speed_dial.dart';
void main() {
runApp(MaterialApp(home: MyApp(), title: 'Flutter Speed Dial Examples'));
}
class MyApp extends StatefulWidget {
#override
MyAppState createState() => MyAppState();
}
class MyAppState extends State<MyApp> with TickerProviderStateMixin {
ScrollController scrollController;
bool dialVisible = true;
#override
void initState() {
super.initState();
scrollController = ScrollController()
..addListener(() {
setDialVisible(scrollController.position.userScrollDirection ==
ScrollDirection.forward);
});
}
void setDialVisible(bool value) {
setState(() {
dialVisible = value;
});
}
Widget buildBody() {
return ListView.builder(
controller: scrollController,
itemCount: 30,
itemBuilder: (ctx, i) => ListTile(title: Text('Item $i')),
);
}
SpeedDial buildSpeedDial() {
return SpeedDial(
animatedIcon: AnimatedIcons.menu_close,
animatedIconTheme: IconThemeData(size: 22.0),
// child: Icon(Icons.add),
onOpen: () => print('OPENING DIAL'),
onClose: () => print('DIAL CLOSED'),
visible: dialVisible,
curve: Curves.bounceIn,
children: [
SpeedDialChild(
child: Icon(Icons.accessibility, color: Colors.white),
backgroundColor: Colors.deepOrange,
onTap: () => print('FIRST CHILD'),
label: 'First Child',
labelStyle: TextStyle(fontWeight: FontWeight.w500),
labelBackgroundColor: Colors.deepOrangeAccent,
),
SpeedDialChild(
child: Icon(Icons.brush, color: Colors.white),
backgroundColor: Colors.green,
onTap: () => print('SECOND CHILD'),
label: 'Second Child',
labelStyle: TextStyle(fontWeight: FontWeight.w500),
labelBackgroundColor: Colors.green,
),
SpeedDialChild(
child: Icon(Icons.keyboard_voice, color: Colors.white),
backgroundColor: Colors.blue,
onTap: () => print('THIRD CHILD'),
labelWidget: Container(
color: Colors.blue,
margin: EdgeInsets.only(right: 10),
padding: EdgeInsets.all(6),
child: Text('Custom Label Widget'),
),
),
],
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Flutter Speed Dial')),
body: buildBody(),
floatingActionButton: Stack(
alignment: Alignment.bottomRight,
fit: StackFit.expand,
overflow: Overflow.visible,
children: [
Stack(
alignment: Alignment.bottomRight,
fit: StackFit.expand,
overflow: Overflow.visible,
children: [
buildSpeedDial(),
],
),
// Here is a FAB
Stack(
alignment: Alignment.bottomCenter,
children: [
FloatingActionButton(
onPressed: () {
print("object");
},
child: Icon(Icons.add),
backgroundColor: Colors.green,
),
],
),
// Here, one more FAB!
Stack(
alignment: Alignment.bottomLeft,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(40, 0, 0, 0),
child: FloatingActionButton(
onPressed: () {
print("object");
},
child: Icon(Icons.remove),
backgroundColor: Colors.red,
),
),
],
),
],
),
);
}
}
It will look like this:
I modified the approach of Akif to make two buttons one on top of the other. Here's the result:
floatingActionButton: Stack(
alignment: Alignment.bottomRight,
children: [
Positioned(
bottom: 70,
child: Container(
child: FloatingActionButton(
onPressed: (){
Navigator.push(context, MaterialPageRoute(builder:(context)=> FormNewActivity()));
},
child: Icon(Icons.add,color:Colors.white,size: 30),
backgroundColor: Colors.green,
)
),
),
Stack(
alignment: Alignment.bottomRight,
children: [
buildSpeedDial(),
],
),
],
),
The result looks like this:

Separated two component in Alert Dialog window

How to separate into two parts the components of alert dialog as it's shown in the image below.
i.e. "Ok" button separated from main body
there you go a working code
class Demo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[400],
body: Dialog(
elevation: 0,
backgroundColor: Colors.transparent,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
height: 256,
color: Colors.white,
),
const SizedBox(height: 20,),
FlatButton(onPressed: (){}, child: const Text("OK"),shape: const CircleBorder(),color: Colors.white,)
],
),
),
);
}
}

Add a main drawer to a button in flutter

I am new to flutter and I need to add the main drawer of the app to a button as you can see in the below picture(This is the upper section of the UI of the app)
Any ideas of having the main drawer apply to a button instead of having it normally assign it to the app bar. (This mobile app doesn't have an app bar)
#override
Widget build(BuildContext context) {
var mediaQuery = MediaQuery.of(context);
return Scaffold(
key: scaffoldKey,
body: Stack(
children: <Widget>[
_buildWidgetAlbumCover(mediaQuery),
getMainContentWidget(mediaQuery),
_buildWidgetMenu(mediaQuery),
_buildWidgetFloatingActionButton(mediaQuery),
Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
DrawerHeader(
child: Text('Drawer Header'),
decoration: BoxDecoration(
color: Colors.blue,
),
),
ListTile(
title: Text('Item 1'),
onTap: () {
},
),
ListTile(
title: Text('Item 2'),
onTap: () {
},
),
],
),
),
],
),
);
}
Widget _buildWidgetMenu(MediaQueryData mediaQuery) {
return Padding(
padding: EdgeInsets.only(
left: 2.0,
top: mediaQuery.padding.top + 2.0,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
IconButton(
icon: Icon(
Icons.menu,
color: Colors.white,
size: 25,
),
padding: EdgeInsets.all(2.0),
// onPressed: () => scaffoldKey.currentState.openDrawer(),
),
],
),
);
}
enter image description here
This is output which I get after having this code. This drawer can't even change or as it is fixed one. I want the normal drawer which is also attached to the _buildWidgetMenu instead of the app bar drawer.
If youre using a sacffold it has a drawer property that you can make use of eg cookbook
return Scaffold(
appBar: AppBar(title: Text(title)),
body: Center(child: Text('My Page!')),
drawer: Drawer(...
opening your drawer from button clicks
var scaffoldKey = GlobalKey<ScaffoldState>();
//required key
Scaffold(
key: scaffoldKey,
drawer: new Drawer(
child: new ListView(
padding: EdgeInsets.zero,...
body:Center(..
IconButton(
icon: Icon(Icons.menu),
//open drawer
onPressed: () => scaffoldKey.currentState.openDrawer(),
),