How to animate hiding AppBar in Flutter? - flutter

I need help with animating my AppBar.
My AppBar hides on DoubleTap, but there's no animation in it, it hides immediately. I want it to be animated. I tried to wrap my AppBar with SlideTransition and AnimatedContainer widget, but none of these work, because the error says I need a PreferredSize widget.
I would be extremely glad, if someone helped me!
I already checked out this answer, but the guy, who answered to this question, has the same problem. There's no animation. Show (slide in) or hide (slide out) flutter AppBar on screen tap
Here's the video of my AppBar:
https://streamable.com/it7ib
Here's the photo how my AppBar looks like:
Code:
import 'package:flutter/material.dart';
class GeneratedCouponScreen extends StatefulWidget {
#override
_GeneratedCouponScreenState createState() => _GeneratedCouponScreenState();
}
class _GeneratedCouponScreenState extends State<GeneratedCouponScreen> {
bool showAppBar = true;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: showAppBar ? AppBar() : null ,
backgroundColor: Colors.white,
body: GestureDetector(
onDoubleTap: () {
if (showAppBar) {
setState(() {
showAppBar = false;
});
}
else {
setState(() {
showAppBar = true;
});
}
},
child: SafeArea(
child: Container(
padding: EdgeInsets.all(16.0),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('DATA WYDANIA:', style: TextStyle(color: Colors.black),),
Text('10/09/2019', style: TextStyle(color: Colors.black))
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('UNIKALNY KOD:', style: TextStyle(color: Colors.black)),
Text('e-86-tC-9', style: TextStyle(color: Colors.black))
],
)
],
),
Column(
children: [
SizedBox(height: 8.0),
Image.asset('assets/images/coupon_hamburger.png',)
],
)
],
)
),
)));
}
}

One way of doing it is by using stack and AnimatedBuilder.
class GeneratedCouponScreen extends StatefulWidget {
#override
_GeneratedCouponScreenState createState() => _GeneratedCouponScreenState();
}
class _GeneratedCouponScreenState extends State<GeneratedCouponScreen>
with SingleTickerProviderStateMixin {
AnimationController _controller;
#override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 300),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: AnimatedBuilder(
animation: _controller,
builder: (context, child) => Stack(
children: <Widget>[
Transform.translate(
offset: Offset(0, -_controller.value * 64),
child: Container(
height: 56.0,
child: AppBar(
title: Text('Title'),
leading: Icon(
Icons.arrow_back,
),
),
),
),
GestureDetector(
onDoubleTap: () {
if (_controller.isCompleted) {
_controller.reverse();
} else {
_controller.forward();
}
},
child: Container(
margin: const EdgeInsets.only(top: 56.0),
padding: EdgeInsets.all(16.0),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'DATA WYDANIA:',
style: TextStyle(color: Colors.black),
),
Text('10/09/2019',
style: TextStyle(color: Colors.black))
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('UNIKALNY KOD:',
style: TextStyle(color: Colors.black)),
Text('e-86-tC-9',
style: TextStyle(color: Colors.black))
],
)
],
),
Column(
children: [
SizedBox(height: 8.0),
Image.network(
'http://via.placeholder.com/640x360',
)
],
)
],
),
),
),
],
),
),
),
);
}
}

First you have to create a new class
class MyAppBar extends StatefulWidget implements PreferredSizeWidget {
num height = 70.0;
#override
_MyAppBarState createState() => _MyAppBarState();
#override
Size get preferredSize => Size.fromHeight(height);
}
class _MyAppBarState extends State<MyAppBar> {
#override
Widget build(BuildContext context) {
return Container(
child: GestureDetector(
onDoubleTap: () {
// Your logic here
// You can do any stuff like animating your this appBar
//e.g: using Opacitiy or Transform and any other Widget depending on what kind of animation you want
// You can also descrease height by calling widget.height
},
child: AppBar(
//Your logic here
),
),
);
}
}
Then call it like this
return Scaffold(
appBar: MyAppBar(),);

Related

Flutter nested widgets setState does not work as intended

I have the following structure:
MainBlock. main statefull widget. contains two widgets BlockA and BlockB.
BlockA. contains a text and a button.
BlockB. contains another widget, BlockBCard, which will be used several times (two times in this example).
What works as intended? When I click on the button in BlockA, the content of the text field in BlockA and BlockBCard changers as desired.
Now to my problem:
In BlockB. In order to use setState, I changed BlockB to a StatefulWidget.
Clicking on the button in the BlockBCard, changes the text field in both BlockBCard’s as desired.
But the content of the text field in BlockA does not change.
how can I implement the following:
Click on the button in one of the BlockBCard’s, both the text field in BlockA and the two text fields in BlockBCard change?
Click on the button in one of the BlockBCard’s, the text field in BlockA changes and the text field in the BlockBCard changes but the text field in the second BlockBCard does not change.
Sample Code:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
int testCounter = 0;
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MainBlock(),
);
}
}
// ------------------------------------ Stateless Widget <<<
class MainBlock extends StatefulWidget {
#override
_MainBlockState createState() => _MainBlockState();
}
class _MainBlockState extends State<MainBlock> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
margin: EdgeInsets.all(30.0),
color: Color(0xFF122C39),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: BlockA(
counter: testCounter,
button: () {
setState(() {
testCounter++;
});
},
),
),
Expanded(
child: BlockB(),
),
],
),
),
);
}
}
class BlockA extends StatelessWidget {
final int counter;
final Function button;
BlockA({this.counter, this.button});
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.all(10.0),
color: Color(0xFF265672),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Center(
child: Text(
counter.toString(),
style: TextStyle(
color: Color(0xFFFFFFFF),
fontSize: 22.0,
),
),
),
Center(
child: GestureDetector(
onTap: button,
child: Text(
'Button',
style: TextStyle(
color: Color(0xFFFFFFFF),
fontSize: 22.0,
),
),
),
),
],
),
);
}
}
class BlockB extends StatefulWidget {
#override
_BlockBState createState() => _BlockBState();
}
class _BlockBState extends State<BlockB> {
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.all(10.0),
color: Color(0xFF265672),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: BlockBCard(
counter: testCounter,
button: () {
setState(() {
testCounter++;
});
},
),
),
Expanded(
child: BlockBCard(
counter: testCounter,
),
),
],
),
);
}
}
class BlockBCard extends StatelessWidget {
final int counter;
final Function button;
BlockBCard({this.counter, this.button});
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.all(30.0),
color: Color(0xFF4C93C7),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Center(
child: Text(
counter.toString(),
style: TextStyle(
color: Color(0xFFFFFFFF),
fontSize: 22.0,
),
),
),
Center(
child: GestureDetector(
onTap: button,
child: Text(
'Button',
style: TextStyle(
color: Color(0xFFFFFFFF),
fontSize: 22.0,
),
),
),
),
],
),
);
}
}
OK, I have now improved my code so that the button in the BlockBCard's calls a function myBrain that executes a calculation, the result of which is output in BlockA.
Now I would like to increase the text field in the BlockBCard's by 1, but only in the respective card in which the button is pressed and not in all cards. With my current code, all cards are incorrectly increased by 1.
In this example there are only two cards, but in the implementation there will be multiple cards.
Here is my current code. to simplify the example, I removed BlockB and placed the BlockBCard's directly into the MainBlock:
How can I increase the text field in the BlockBCard's by 1, but only in the respective card in which the button is pressed and not in all cards?
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MainBlock(),
);
}
}
// OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO MainBlock >>>
class MainBlock extends StatefulWidget {
#override
_MainBlockState createState() => _MainBlockState();
}
class _MainBlockState extends State<MainBlock> {
int totalCounter = 0;
int singleCounter = 0;
void myBrain() {
totalCounter = totalCounter + 5;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
margin: EdgeInsets.all(30.0),
color: Color(0xFF122C39),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: BlockA(
counter: totalCounter,
button: () {
setState(() {
totalCounter = 0;
});
},
),
),
Expanded(
child: BlockBCard(
counter: singleCounter,
button: () {
setState(() {
myBrain();
singleCounter++;
});
},
),
),
Expanded(
child: BlockBCard(
counter: singleCounter,
button: () {
setState(() {
myBrain();
singleCounter++;
});
},
),
),
],
),
),
);
}
}
// OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO MainBlock <<<
// OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO BlockA >>>
class BlockA extends StatelessWidget {
final int counter;
final Function button;
BlockA({this.counter, this.button});
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.all(10.0),
color: Color(0xFF265672),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Center(
child: Text(
counter.toString(),
style: TextStyle(
color: Color(0xFFFFFFFF),
fontSize: 22.0,
),
),
),
Center(
child: GestureDetector(
onTap: button,
child: Text(
'Button',
style: TextStyle(
color: Color(0xFFFFFFFF),
fontSize: 22.0,
),
),
),
),
],
),
);
}
}
// OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO BlockA <<<
// OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO BlockBCard >>>
class BlockBCard extends StatelessWidget {
final int counter;
final Function button;
BlockBCard({this.counter, this.button});
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.all(30.0),
color: Color(0xFF4C93C7),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Center(
child: Text(
counter.toString(),
style: TextStyle(
color: Color(0xFFFFFFFF),
fontSize: 22.0,
),
),
),
Center(
child: GestureDetector(
onTap: button,
child: Text(
'Button',
style: TextStyle(
color: Color(0xFFFFFFFF),
fontSize: 22.0,
),
),
),
),
],
),
);
}
}
// OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO BlockBCard <<<
You got the issue because when you change the value of blockB it could not rebuild ta blockA but it change the global value if you will want to change the blockA's from blockB you should pass as like blockA.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
int testCounter = 0;
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MainBlock(),
);
}
}
// ------------------------------------ Stateless Widget <<<
class MainBlock extends StatefulWidget {
#override
_MainBlockState createState() => _MainBlockState();
}
class _MainBlockState extends State<MainBlock> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
margin: EdgeInsets.all(30.0),
color: Color(0xFF122C39),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: BlockA(
counter: testCounter,
button: () {
setState(() {
testCounter++;
});
},
),
),
Expanded(
child: BlockB(
counter: testCounter,
button: () {
setState(() {
testCounter++;
});
}),
),
],
),
),
);
}
}
class BlockA extends StatelessWidget {
final int counter;
final Function button;
BlockA({this.counter, this.button});
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.all(10.0),
color: Color(0xFF265672),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Center(
child: Text(
counter.toString(),
style: TextStyle(
color: Color(0xFFFFFFFF),
fontSize: 22.0,
),
),
),
Center(
child: GestureDetector(
onTap: button,
child: Text(
'Button',
style: TextStyle(
color: Color(0xFFFFFFFF),
fontSize: 22.0,
),
),
),
),
],
),
);
}
}
class BlockB extends StatefulWidget {
final int counter;
final Function button;
BlockB({this.counter, this.button});
#override
_BlockBState createState() => _BlockBState();
}
class _BlockBState extends State<BlockB> {
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.all(10.0),
color: Color(0xFF265672),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: BlockBCard(
counter: testCounter,
button:widget.button,
),
),
Expanded(
child: BlockBCard(
counter: testCounter,
),
),
],
),
);
}
}
class BlockBCard extends StatelessWidget {
final int counter;
final Function button;
BlockBCard({this.counter, this.button});
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.all(30.0),
color: Color(0xFF4C93C7),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Center(
child: Text(
counter.toString(),
style: TextStyle(
color: Color(0xFFFFFFFF),
fontSize: 22.0,
),
),
),
Center(
child: GestureDetector(
onTap: button,
child: Text(
'Button',
style: TextStyle(
color: Color(0xFFFFFFFF),
fontSize: 22.0,
),
),
),
),
],
),
);
}
}
As #Jahidul Islam explained, you have to pass your data from MainBlock to BlocB as you do from MainBlock to BlocA.
In general in Flutter, you want to avoid using mutable global variable precisely for that reason. When a variable update, flutter does not know about it. If you call setState the widget tree bellow will be rebuild.
This is why you will often hear "Lift the state up" because the state has to be contained in a high enough widget so that it can be passed down through the widget tree to every widget that needs it. In your case, since BlocA and BlockBCard need the counter, it has to be created inside there lower common ancestor: MainBlock.
Here is the concrete implementation but the most important thing is to have understood the reasoning explained above:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MainBlock(),
);
}
}
class MainBlock extends StatefulWidget {
#override
_MainBlockState createState() => _MainBlockState();
}
class _MainBlockState extends State<MainBlock> {
int testCounter = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
margin: EdgeInsets.all(30.0),
color: Color(0xFF122C39),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: BlockA(
counter: testCounter,
button: () {
setState(() {
testCounter++;
});
},
),
),
Expanded(
child: BlockB(
counter: testCounter,
button: () {
setState(() {
testCounter++;
});
},
),
),
],
),
),
);
}
}
class BlockA extends StatelessWidget {
final int counter;
final VoidCallback button;
BlockA({required this.counter, required this.button});
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.all(10.0),
color: Color(0xFF265672),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Center(
child: Text(
counter.toString(),
style: TextStyle(
color: Color(0xFFFFFFFF),
fontSize: 22.0,
),
),
),
Center(
child: GestureDetector(
onTap: button,
child: Text(
'Button',
style: TextStyle(
color: Color(0xFFFFFFFF),
fontSize: 22.0,
),
),
),
),
],
),
);
}
}
class BlockB extends StatelessWidget {
final int counter;
final VoidCallback button;
BlockB({required this.counter, required this.button});
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.all(10.0),
color: Color(0xFF265672),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: BlockBCard(
counter: counter,
button: button,
),
),
Expanded(
child: BlockBCard(
counter: counter,
),
),
],
),
);
}
}
class BlockBCard extends StatelessWidget {
final int counter;
final VoidCallback? button;
BlockBCard({required this.counter, this.button});
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.all(30.0),
color: Color(0xFF4C93C7),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Center(
child: Text(
counter.toString(),
style: TextStyle(
color: Color(0xFFFFFFFF),
fontSize: 22.0,
),
),
),
Center(
child: GestureDetector(
onTap: button ?? (() {}),
child: Text(
'Button',
style: TextStyle(
color: Color(0xFFFFFFFF),
fontSize: 22.0,
),
),
),
),
],
),
);
}
}
If we refresh the MainBlock it's children will also get the updated state. We can use callback for it.
On BlocKB
class BlockB extends StatefulWidget {
final VoidCallback ontap;
const BlockB({
Key? key,
required this.ontap,
}) : super(key: key);
#override
_BlockBState createState() => _BlockBState();
}
class _BlockBState extends State<BlockB> {
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.all(10.0),
color: Color(0xFF265672),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: BlockBCard(
counter: testCounter,
button: () {
// setState(() { /// we dont need it while parent widget will refress it
testCounter++;
// });
widget.ontap();
},
),
),
Expanded(
child: BlockBCard(
counter: testCounter,
button: () {
/// you may also wanted to increment here
testCounter++;
widget.ontap();
},
),
),
],
),
);
}
}
On MainBlock
Expanded(
child: BlockB(
ontap: () {
setState(() {});
},
),
),

Navigate the page

I am new to Flutter. I know how to navigate from a page to another page, but the problem is, my navigation does not work. In my short experience, I realized that you cannot navigate from the stateless to the stateful widget or the opposite one.
I will share my code below. So could you please tell me what kind of mistake I am making?
Thank you. :)
Actually I was planning to remove the button because this page could be the intro page like it could show the image and text I want and after 3 seconds it could go to the MainApp() page. Maybe if you have any idea how to do it. I would like to hear that too.:)
void main() {
runApp(Intro());
}
class Intro extends StatefulWidget {
#override
_IntroState createState() => _IntroState();
}
class _IntroState extends State<Intro> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
backgroundColor: Colors.yellow.shade300,
body: SafeArea(
child: (Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Image.asset(
'assets/dinoapp2.png',
height: 200.0,
width: 200.0,
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.only(top: 40.0),
child: Text(
'Big Title',
textAlign: TextAlign.center,
style: TextStyle(
fontFamily: 'Russo One Regular',
fontSize: 30.0,
color: Colors.blueAccent),
),
),
),
Container(
margin: EdgeInsets.fromLTRB(120.0, 20, 120, 100),
child: RaisedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => MainApp()),
);
},
padding: EdgeInsets.all(20.0),
textColor: Colors.black,
color: Colors.white,
child: Text(
"Start",
style: TextStyle(fontSize: 20.0),
),
),
)
],
)),
),
),
);
}
}
class MainApp extends StatefulWidget {
#override
_MainAppState createState() => _MainAppState();
}
class _MainAppState extends State<MainApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
backgroundColor: Colors.blue,
body: SafeArea(
child: Center(
child: Text('This page I want to go after I press Start button'),
),
),
),
);
}
}
Try This
void main() {
runApp(MaterialApp(
home: Intro(),
));
}
class Intro extends StatefulWidget {
#override
_IntroState createState() => _IntroState();
}
class _IntroState extends State<Intro> {
#override
void initState() {
Future.delayed(Duration(seconds: 3), () {
Route route = MaterialPageRoute(builder: (context) => MainApp());
Navigator.push(context, route);
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.yellow.shade300,
body: SafeArea(
child: (Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Image.asset(
'assets/dinoapp2.png',
height: 200.0,
width: 200.0,
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.only(top: 40.0),
child: Text(
'Big Title',
textAlign: TextAlign.center,
style: TextStyle(
fontFamily: 'Russo One Regular',
fontSize: 30.0,
color: Colors.blueAccent),
),
),
),
Center(
child: CircularProgressIndicator(),
)
],
)),
),
);
}
}
class MainApp extends StatefulWidget {
#override
_MainAppState createState() => _MainAppState();
}
class _MainAppState extends State<MainApp> {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.blue,
body: SafeArea(
child: Center(
child: Text('This page I want to go after I press Start button'),
),
),
);
}
}
Just Add this function in your intro class
#override
void initState() {
super.initState();
Future.delayed(const Duration(seconds: 2), () {
Navigator.push( context, MaterialPageRoute(builder: (context) =>
MainApp()),
); });
}
I don't know why but I copied your code into Intro class. It didn't work. :(

Change String value from one stateful widget to another stateful widget

I have a Stateful widget:
class PractiseInterview extends StatefulWidget {
#override
_PractiseInterviewState createState() => _PractiseInterviewState();
}
class _PractiseInterviewState extends State<PractiseInterview> {
String outputText = '0';
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Practise'),
),
body: Container(
child: Column(
children: <Widget>[
Text(
outputText, //**outputText is here**
),
Expanded(
child: Divider(),
),
Column(
children: <Widget>[
Row(
children: <Widget>[
ChnageText(text: 'changed',),
],
),
],
)
],
),
),
);
}
}
NOTICE that my outputText = '0' here.
Then I have a stateless widget for my OutlineButton in stateful widget:
class ChnageText extends StatelessWidget {
const ChnageText({ this.text});
final String text;
buttonPressed(String text) {
if (text == 'changed') {
//TODO: change outputText value to 'changed' at the click of this button
}
}
#override
Widget build(BuildContext context) {
return Expanded(
child: OutlineButton(
onPressed: () {
buttonPressed(text);
},
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Text(
text,
style: TextStyle(
fontSize: 15.0,
fontWeight: FontWeight.bold,
),
),
),
),
);
}
}
I have mentioned TODO in my stateless widget, there at the click of the button I want to change outputText = '0' to outputText = 'changed'. I don't know how to do this
I am new to flutter and I am not able to understand this. Basically I am making a calculator where when a button is clicked, then the value of the button should be displayed.
Pass a callback to ChnageText from the parent that changes outputText and calls setState.
class ChnageText extends StatelessWidget {
const ChnageText({this.text, this.callback});
final String text;
final VoidCallback callback;
buttonPressed(String text) {
if (text == 'changed') {
callback();//Call callback here
}
}
#override
Widget build(BuildContext context) {
return Expanded(
child: OutlineButton(
onPressed: () {
buttonPressed(text);
},
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Text(
text,
style: TextStyle(
fontSize: 15.0,
fontWeight: FontWeight.bold,
),
),
),
),
);
}
}
class _PractiseInterviewState extends State<PractiseInterview> {
String outputText = '0';
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Practise'),
),
body: Container(
child: Column(
children: <Widget>[
Text(
outputText, //**outputText is here**
),
Expanded(
child: Divider(),
),
Column(
children: <Widget>[
Row(
children: <Widget>[
ChnageText(text: 'changed', callback: () {setState((){outputText = 'changed';});}),// Pass callback here
],
),
],
)
],
),
),
);
}
}
Ideally, you wouldn't have to do this at all. What's in ChnageText doesn't need to be in its own StatelessWidget. Putting all of that directly in the parent removes this problem.
class _PractiseInterviewState extends State<PractiseInterview> {
String outputText = '0';
buttonPressed(String text) {
if (text == 'changed') {
setState(() {
outputText = 'changed';
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Practise'),
),
body: Container(
child: Column(
children: <Widget>[
Text(
outputText, //**outputText is here**
),
Expanded(
child: Divider(),
),
Column(
children: <Widget>[
Row(
children: <Widget>[
//Put ChnageText directly into the build of the parent
Expanded(
child: OutlineButton(
onPressed: () {
buttonPressed('changed');
},
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Text(
'changed',
style: TextStyle(
fontSize: 15.0,
fontWeight: FontWeight.bold,
),
),
),
),
),
],
),
],
)
],
),
),
);
}
}

Passing stateless widget from one screen to another screen in Flutter

I am trying to create a form builder, In one screen I want have small container with icon and text with gesture detector, when I tap on the container it should contruct a new DatePicker Widget. I know how to do it in the same screen, but onTap I want construct the Datepicker in another screen.
This is Form Component Class.
class FormComponents extends StatefulWidget {
#override
_FormComponentsState createState() => _FormComponentsState();
}
class _FormComponentsState extends State<FormComponents> {
final GlobalKey<FormBuilderState> _fbKey = GlobalKey<FormBuilderState>();
int _count = 0;
final formData = FormWidgetData(widget: DatePicker());
#override
Widget build(BuildContext context) {
List<Widget> datePicker = List.generate(_count, (index) => DatePicker());
return Scaffold(
appBar: AppBar(
title: Text('Form Components'),
),
body: Container(
margin: EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
FormBuilder(
key: _fbKey,
initialValue: {
'date': DateTime.now(),
'accept_terms': false,
},
autovalidate: true,
child: Column(
children: <Widget>[
Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
setState(() {
_count = _count + 1;
});
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text('Date Picker'),
duration: Duration(seconds: 2),
),
);
},
child: Container(
margin: EdgeInsets.all(16.0),
padding: EdgeInsets.all(8.0),
height: 100.0,
width: 120.0,
color: Colors.black,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Icon(
Icons.date_range,
color: Colors.white,
),
Text(
'Date Picker',
style: TextStyle(
color: Colors.white, fontSize: 18.0),
),
],
),
),
);
},
),
Container(
height: 200.0,
margin: EdgeInsets.all(8.0),
child: Expanded(
child: ListView(
children: datePicker,
),
),
),
],
),
),
],
),
),
);
}
}
This is my Date Picker Class
class DatePicker extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: FormBuilderDateTimePicker(
attribute: 'date',
inputType: InputType.date,
format: DateFormat('yyyy-MM-dd'),
decoration: InputDecoration(labelText: 'AppointmentTime'),
),
);
}
}
This is my Form Builder Class, How to do I Construct the above Date Picker class here.
class MyFormBuilder extends StatefulWidget {
final FormWidgetData data;
MyFormBuilder({this.data});
#override
_MyFormBuilderState createState() => _MyFormBuilderState();
}
class _MyFormBuilderState extends State<MyFormBuilder> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Form Builder'),
),
body: Container(
height: 300.0,
margin: EdgeInsets.all(8.0),
child: Expanded(
child: ListView(
children: <Widget>[],
),
),
),
);
}
}

No TabController for TabBarView flutter

I am trying add TabBar by using the below code:
TabBarView(
children: [
Icon(Icons.directions_car),
Icon(Icons.directions_transit),
Icon(Icons.directions_bike),
],
),
but I found the below error:
No TabController for TabBarView.
and this is whole code:
import '../providers/properties.dart';
import '../providers/cities.dart';
import '../providers/property.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../widgets/properties_grid.dart';
import '../app_theme.dart';
class MyHomePage extends StatefulWidget {
const MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
int currentTab = 0;
final PageStorageBucket bucket = PageStorageBucket();
var _showOnlyFavorites = false;
// List<HomeList> homeList = HomeList.homeList;
AnimationController animationController;
bool multiple = true;
#override
void initState() {
animationController = AnimationController(
duration: const Duration(milliseconds: 2000), vsync: this);
super.initState();
}
Future<bool> getData() async {
await Future<dynamic>.delayed(const Duration(milliseconds: 0));
return true;
}
#override
void dispose() {
animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
// final properties = Provider.of<Properties>(context, listen: false);
return Scaffold(
extendBody: true,
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {},
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
bottomNavigationBar: BottomAppBar(
elevation: 0,
shape: CircularNotchedRectangle(),
notchMargin: 10,
child: Container(
height: 60,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
MaterialButton(
padding: EdgeInsets.all(0),
minWidth: 155,
onPressed: () {
setState(() {
// currentScreen =
// Chat(); // if user taps on this dashboard tab will be active
currentTab = 1;
});
},
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(
Icons.home,
color: currentTab == 1 ? Colors.blue : Colors.grey,
),
Text(
'Home',
style: TextStyle(
color: currentTab == 1 ? Colors.blue : Colors.grey,
),
),
],
),
)
],
),
// Right Tab bar icons
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
MaterialButton(
padding: EdgeInsets.all(0),
minWidth: 60,
onPressed: () {
setState(() {
// currentScreen =
// Settings(); // if user taps on this dashboard tab will be active
currentTab = 3;
});
},
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(
Icons.view_list,
color: currentTab == 3 ? Colors.blue : Colors.grey,
),
Text(
'Property List',
style: TextStyle(
color: currentTab == 3 ? Colors.blue : Colors.grey,
),
),
],
),
),
MaterialButton(
padding: EdgeInsets.all(0),
minWidth: 77,
onPressed: () {
setState(() {
// currentScreen =
// Settings(); // if user taps on this dashboard tab will be active
currentTab = 4;
});
},
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(
Icons.location_searching,
color: currentTab == 4 ? Colors.blue : Colors.grey,
),
Text(
'Map',
style: TextStyle(
color: currentTab == 4 ? Colors.blue : Colors.grey,
),
),
],
),
),
],
)
],
),
),
),
backgroundColor: AppTheme.white,
body: Stack(
children: <Widget>[
FutureBuilder<bool>(
future: getData(),
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
if (!snapshot.hasData) {
return const SizedBox();
} else {
return Padding(
padding:
EdgeInsets.only(top: MediaQuery.of(context).padding.top),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
appBar(),
TabBarView(
children: [
Icon(Icons.directions_car),
Icon(Icons.directions_transit),
Icon(Icons.directions_bike),
],
),
Expanded(
child: FutureBuilder<bool>(
future: getData(),
builder: (BuildContext context,
AsyncSnapshot<bool> snapshot) {
if (!snapshot.hasData) {
return const SizedBox();
} else {
return ChangeNotifierProvider(
create: (context) => Properties(),
child: PropertiesGrid(_showOnlyFavorites),
);
}
},
),
),
],
),
);
}
},
),
],
),
);
}
Widget appBar() {
return SizedBox(
height: AppBar().preferredSize.height,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 8, left: 8),
child: Container(
width: AppBar().preferredSize.height - 8,
height: AppBar().preferredSize.height - 8,
),
),
Expanded(
child: Center(
child: Padding(
padding: const EdgeInsets.only(top: 4),
child:
Image.asset('assets/images/logo.png', fit: BoxFit.contain),
),
),
),
Padding(
padding: const EdgeInsets.only(top: 8, right: 8),
child: Container(
width: AppBar().preferredSize.height - 8,
height: AppBar().preferredSize.height - 8,
color: Colors.white,
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius:
BorderRadius.circular(AppBar().preferredSize.height),
child: Icon(
Icons.location_on,
color: AppTheme.dark_grey,
),
onTap: () {
setState(() {
multiple = !multiple;
});
},
),
),
),
),
],
),
);
}
So How Can I solve this problem...
How can TabBar get to know about the TabBarView? There should be a connection between them to change when tab press or if swap from view right?
So, to connect both two, you have to either wrap your parent widget using DefaultTabController or providing a TabController for TabBar and TabBarView to controll and configure Tabs.
Flutter cookbook example for DefaultTabController:
import 'package:flutter/material.dart';
void main() {
runApp(TabBarDemo());
}
class TabBarDemo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
bottom: TabBar(
tabs: [
Tab(icon: Icon(Icons.directions_car)),
Tab(icon: Icon(Icons.directions_transit)),
Tab(icon: Icon(Icons.directions_bike)),
],
),
title: Text('Tabs Demo'),
),
body: TabBarView(
children: [
Icon(Icons.directions_car),
Icon(Icons.directions_transit),
Icon(Icons.directions_bike),
],
),
),
),
);
}
}
Using TabController(Example from doc):
class MyTabbedPage extends StatefulWidget {
const MyTabbedPage({ Key key }) : super(key: key);
#override
_MyTabbedPageState createState() => _MyTabbedPageState();
}
class _MyTabbedPageState extends State<MyTabbedPage> with SingleTickerProviderStateMixin {
final List<Tab> myTabs = <Tab>[
Tab(text: 'LEFT'),
Tab(text: 'RIGHT'),
];
TabController _tabController;
#override
void initState() {
super.initState();
_tabController = TabController(vsync: this, length: myTabs.length);
}
#override
void dispose() {
_tabController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
bottom: TabBar(
controller: _tabController,
tabs: myTabs,
),
),
body: TabBarView(
controller: _tabController,
children: myTabs.map((Tab tab) {
final String label = tab.text.toLowerCase();
return Center(
child: Text(
'This is the $label tab',
style: const TextStyle(fontSize: 36),
),
);
}).toList(),
),
);
}
}