Creating an example to learn callbacks in Flutter. It's a simple programme to increase the counter onTap of a GestureDetetor But, the callback method is not working. The count increase on hot reload but not on tap. Below is the code with comments.
class BoxState extends State<ChangeBoxState>{
int _counter = 0;
//Callback method changes the state onTap of GestureDetector widget. It is not calling onTap.
increaseCount(){
setState(() {
++_counter;
print(_counter);
});
}
#override
Widget build(BuildContext context) {
// Passing the callback method,"increaseCount()" to stateless class where GestureDetector is defined.
return BoxWidget(onPressed: increaseCount(), counter: _counter,);
}
}
Stateless class:
class BoxWidget extends StatelessWidget{
BoxWidget({this.onPressed, this.counter});
final VoidCallback onPressed;
final int counter;
#override
Widget build(BuildContext context) {
// TODO: implement build
return Container(
decoration: BoxDecoration(color: Colors.blue[500]),
child: Column(
children: <Widget>[Center(child: Text('Hello, world!')),
GestureDetector(
onTap: onPressed, //Passing onPressed to onTap.
child: Container(
margin: const EdgeInsets.only(left:0.0, top:200.0, right:0.0, bottom:0.0),
height: 200.0,
width: 200.0,
decoration: BoxDecoration(
color: Colors.teal[200],
border: Border.all(color: Colors.yellow, width: 10.0, style: BorderStyle.solid),
borderRadius: BorderRadius.all(Radius.circular(20.0)),
),
child: Center(child: Text(counter.toString())),
),
),
],
),
);
}
}
remove the the bracket in increaseCount() because using the bracket you are creating an instance of your VoidCallback and this will run one time only so try this
return BoxWidget(onPressed: increaseCount, counter: _counter,);
You should provide the reference of increaseCount to the onPressed callback.
Here you are assigning increaseCount() (check braces) to the callback which first call increaseCount() function and its return value will be assigned to the onPressed. Thats why it is only incrementing one time on hot reload.
return BoxWidget(onPressed: increaseCount, counter: _counter,);
Related
I'm trying to make a calculator app using flutter where instead of taking input through the keyboard I want to take input through some buttons. The issue comes when I press a button but it does not display the corresponding data in the Text widget above.
All my classes are stateless except for the first MyApp class, which is Stateful.
I tried by creating a general variable outside all the classes and using that to transfer text from the button class to the display class but that did not work.
The general variable is "_calcText"
class DisplayAnswer extends StatelessWidget {
final String _text;
DisplayAnswer(this._text);
#override
Widget build(BuildContext context) {
return Expanded(
flex: 2,
child: Material(
color: Colors.greenAccent,
child: Ink(
padding: EdgeInsets.all(10.0),
child: Center(
child: Container(
constraints: BoxConstraints.expand(),
decoration: BoxDecoration(border: Border.all(color: Colors.black, width: 5.0), color: Colors.white),
child: Text(_text,style: TextStyle(fontSize: 50.0), textAlign: TextAlign.center,),
),
),
),
),
);
}
}
class NumButtons extends StatelessWidget {
final String _number;
NumButtons(this._number);
#override
Widget build(BuildContext context) {
return FlatButton(
onPressed: () {
_calcText = _calcText + _number;
print(_calcText);
DisplayAnswer(_calcText);
} ,
child: Text(_number.toString(), style: TextStyle(fontSize: 30.0),),
color: Colors.white
);
}
}
I want to display the value of _calcText in the Text widget of DisplayAnswer. I want _calcText to also change as other buttons are clicked, ie; if 2 is clicked Text should only display 2, if 5 is clicked after that it should display 25
The full code is here:
https://drive.google.com/open?id=1C4MLAkjowloicbjBP_uV8BfpPzhz4Yxf
Use Statefull widget insted of StatelessWidget.
Call the setState() method on onPressed function, after adition operation. It will build your widget with a new value.
On DisplayAnswer, you have to make a function to increment the value, than pass this function as parameter to NumButtons.
Pass a callback Function to NumButtons, like:
class NumButtons extends StatelessWidget {
final String _number;
final Function callback;
...
I new to flutter and i have a counter button that i want to prevent it from multiple touch.
The Tap Function is defined under Inkwell component (onTap: () => counterBloc.doCount(context)).
if i run this apps and doing multi touch, counter will go up quickly, but i dont want it happen. any idea ?
below are my code :
Expanded(
child: Container(
padding: EdgeInsets.only(right: 16),
alignment: Alignment.centerRight,
child: InkWell(
onTap: () => counterBloc.doCount(context),
child: Stack(
alignment: Alignment.center,
children: <Widget>[
Image.asset("assets/images/home/tap.png", scale: 11,),
StreamBuilder(
initialData: 0,
stream: counterBloc.counterStream,
builder: (BuildContext ctx, AsyncSnapshot<int> snapshot){
return Text("${snapshot.data}",style: TextStyle(color: Colors.white, fontSize: 120),);
},
),
],
)
)
)
)
you can use an AbsorbPointer
AbsorbPointer(
absorbing: !enabled,
child: InkWell(
onTap: (){
print('buttonClicked');
setState(() {
enabled = false;
});
},
child: Container(
width: 50.0,
height: 50.0,
color: Colors.red,
),
),
),
and when you want to enable the button again, set the enabled to true, don't forget to wrap it with a setState
Try this? It should solve your problem.
class SafeOnTap extends StatefulWidget {
SafeOnTap({
Key? key,
required this.child,
required this.onSafeTap,
this.intervalMs = 500,
}) : super(key: key);
final Widget child;
final GestureTapCallback onSafeTap;
final int intervalMs;
#override
_SafeOnTapState createState() => _SafeOnTapState();
}
class _SafeOnTapState extends State<SafeOnTap> {
int lastTimeClicked = 0;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
final now = DateTime.now().millisecondsSinceEpoch;
if (now - lastTimeClicked < widget.intervalMs) {
return;
}
lastTimeClicked = now;
widget.onSafeTap();
},
child: widget.child,
);
}
}
You can wrap any kind of widget if you want.
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: Column(
children: [
// every click need to wait for 500ms
SafeOnTap(
onSafeTap: () => log('500ms'),
child: Container(
width: double.infinity,
height: 200,
child: Center(child: Text('500ms click me')),
),
),
// every click need to wait for 2000ms
SafeOnTap(
intervalMs: 2000,
onSafeTap: () => log('2000ms'),
child: Container(
width: double.infinity,
height: 200,
child: Center(child: Text('2000ms click me')),
),
),
],
),
),
),
);
}
}
Another option is to use debouncing to prevent this kind of behaviour ie with easy_debounce, or implementing your own debounce.
You can also use IgnorePointer
IgnorePointer(
ignoring: !isEnabled
child: yourChildWidget
)
And when you disable the component, it starts ignoring the touches within the boundary of the widget.
I personally wouldn't rely on setState, I'd go with a simple solution like this:
Widget createMultiClickPreventedButton(String text, VoidCallback clickHandler) {
var clicked = false;
return ElevatedButton(
child: Text(text),
onPressed: () {
if (!clicked) {
clicked = true;
clickHandler.call();
}
});
}
You can also use a Stream to make counter to count only on debounced taps.
final BehaviourSubject onTapStream = BehaviourSubject()
#override
void initState() {
super.initState();
// Debounce your taps here
onTapStream.debounceTime(const Duration(milliseconds: 300)).listen((_) {
// Do something on tap
print(1);
});
}
I'm creating a dashboard which contain Tween animation for two widgets, Text and two Container. But, I want to make the two Container's opacity changing slowly from invisible to visible...so I used AnimatedOpacity. But I don't know how to do it...
Any help would be appreciated..
class _IntroState extends State<Intro> with SingleTickerProviderStateMixin {
Animation animation;
AnimationController animationController;
#override
void initState() {
super.initState();
animationController = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
);
animation = Tween(begin: -1.0, end: 0.0).animate(CurvedAnimation(
parent: animationController, curve: Curves.fastOutSlowIn));
animationController.forward();
}
#override
Widget build(BuildContext context) {
bool _visible = false;
final double width = MediaQuery.of(context).size.width;
return AnimatedBuilder(
animation: animationController,
builder: (BuildContext context, Widget child) {
return Scaffold(
//BODDY
body: ListView(
hildren:<Widget>[
new Stack(
children: <Widget>[
new Transform(
//ANIMATED OPACITY
new AnimatedOpacity(
opacity: _visible ? 0.0 : 1.0,
duration: Duration(milliseconds: 500),
child: new Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12.0),
child: new Row(
children: <Widget>[
Expanded(
child: Row(
children: <Widget>[
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Container(
child: Column(
children: <Widget>[
//THIS THE CONTAINER
new Container(. . .),
new Container(. . .)
Instead of AnimatedOpacity, use a FadeTransition widget. This gives you manual control over the animation:
#override
Widget build(BuildContext context) {
return FadeTransition(
opacity: animationController.drive(CurveTween(curve: Curves.easeOut)),
child: ...,
);
}
To make a StatelessWidget or StatefulWidget fade in automatically on creation, TweenAnimationBuilder provides an even easier solution:
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return TweenAnimationBuilder<double>(
tween: Tween<double>(begin: 0.0, end: 1.0),
curve: Curves.ease,
duration: const Duration(seconds: 1),
builder: (BuildContext context, double opacity, Widget? child) {
return Opacity(
opacity: opacity,
child: Container(width: 20, height: 20, color: Colors.red)
);
});
}
}
See my Codepen for a complete example: https://codepen.io/atok/pen/BaZVRPr
Best regards
For anyone who'd like to fade a widget automatically as soon as the page is rendered and still wants to use AnimatedOpacity, you can put the call to change the state of the opacity in the WidgetsBinding's addPostFrameCallback callback.
Put this code below in your initState.
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
_opacity = 1;
});
});
I totally recommend using #boformer 's answer above.
But, I played around with your code and wanted to show you how you can call setState to trigger the AnimatedOpacity, so you can see that it is working without onTap or GestureDetector as you were thinking in the comments above.
I got your code and played around with it. What I did is, simply added a status listener to your animation controller and when the controller is done. I triggered the visibility boolean in setState. Then it will change the visibility of the containers.
// When animation finished change the visibility.
animationController.addStatusListener((status){
if (status == AnimationStatus.completed) {
setState(() {
// This is opposite, because it's implemented opposite in your code.
_visible = false;
});
}
});
I'm trying to test the VoidCallback so I created the main file, that have a function called from a flat button in the widget, which is in a separate file, but did not work.
main.dart
import 'package:flutter/material.dart';
import 'controller_test.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Retrieve Text Input',
home: MyCustomForm(),
);
}
}
// Define a Custom Form Widget
class MyCustomForm extends StatefulWidget {
#override
_MyCustomFormState createState() => _MyCustomFormState();
}
class _MyCustomFormState extends State<MyCustomForm> {
final myController = TextEditingController();
#override
void initState() {
super.initState();
myController.addListener(_printLatestValue);
}
_printLatestValue() {
print("Second text field: ${myController.text}");
}
_test() {
print("hi there");
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Retrieve Text Input'),
),
body: Con(_test, myController)
);
}
}
controller_test.dart
import 'package:flutter/material.dart';
class Con extends StatelessWidget {
Con(this.clickCallback, this.tc);
final TextEditingController tc;
final VoidCallback clickCallback;
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: <Widget>[
TextField(
onChanged: (text) {
print("First text field: $text");
},
),
TextField(
controller: tc,
),
FlatButton(
onPressed: () => clickCallback,
child: Text("click me"),
)
],
),
);
}
}
When I click the FlatButton in the widget, nothing is happening, I was expecting hi there to be printed
there are two options here.
onPressed: () => fun() is like onPressed argument is an anonymous method that calls fun.
onPressed: fun is like onPressed argument is the function fun.
I just found it in another answer here
I was missing the (), so correct call is:
FlatButton(
onPressed: () => clickCallback(),
child: Text("click me"),
)
You can get callback from stateless widget to your current page by using Voidcallback class.
Just add this custom widget in your current page (widget.build()
function)
DefaultButton(
buttonText: Constants.LOGIN_BUTTON_TEXT,
onPressed: () => validateInputFields(),
size: size,
);
My custom widget class is
class DefaultButton extends StatelessWidget {
DefaultButton({this.buttonText, this.onPressed, this.size});
final String buttonText;
final VoidCallback onPressed;
final Size size;
#override
Widget build(BuildContext context) {
return MaterialButton(
minWidth: size.width,
onPressed: () => onPressed(), //callback to refered page
padding: EdgeInsets.all(0.0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(DEFAULT_BORDER_RADIUS),
),
child: Ink(
width: size.width,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: <Color>[
SECONDARY_COLOR_SHADE_LITE,
SECONDARY_COLOR_SHADE_DARK,
],
),
borderRadius: BorderRadius.circular(DEFAULT_BORDER_RADIUS),
),
child: Padding(
padding: EdgeInsets.only(left: 20, right: 20, top: 12, bottom: 12),
child: Text(
buttonText,
style: Theme.of(context).textTheme.button,
textAlign: TextAlign.center,
)),
),
);
}
}
In your callback make sure to call setState if any variables change. I repopulate an list in my provider and then use assign that list to the variable list which I convert into a list of cards. The variable list needs state refreshed to see it.
I have the following code:
#override
Widget build(BuildContext context) {
return new Container(
height: 72.0, // in logical pixels
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 8.0),
decoration: new BoxDecoration(color: Colors.white),
// Row is a horizontal, linear layout.
child: new MaterialButton(
child: new Text(
_sprinkler.name,
style: new TextStyle(color: Colors.white)
),
splashColor: Colors.blueAccent,
color: Colors.blue[800],
onPressed: () {
print("onTap(): tapped" + _sprinkler.name);
},
),
);
}
onPressed(), I want to change the Buttons style - to represent sprinkler activity.
Therefore, I would need to acces the MaterialButton Widget itself.
But how to I access it from within the callback?
Thanks a lot in advance, and sorry for the n00b question, I am new to Dart and Flutter ;)
You could make some of the properties a variable. Then you can call setState() in your onPressed() to change the property variable.
This example shows how to change your text color of the button by using this method:
Color textColor = Colors.white;
#override
Widget build(BuildContext context) {
return new Container(
height: 72.0, // in logical pixels
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 8.0),
decoration: new BoxDecoration(color: Colors.white),
// Row is a horizontal, linear layout.
child: new MaterialButton(
child: new Text(
_sprinkler.name,
style: new TextStyle(color: textColor)
),
splashColor: Colors.blueAccent,
color: Colors.blue[800],
onPressed: () {
this.setState(() {
textColor = Colors.red;
})
},
),
);
}
You probably want to use a StatefulWidget, something like this:
class MyWidget extends StatefulWidget {
_MyWidgetState createState() => new _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
Color c = Colors.blue.shade500;
Widget build() => new MaterialButton(
color: c,
onPressed: () => setState(() {
c = Colors.red.shade500;
}),
);
}
Thanks for your comments. The correct solution is actualy what you recommended and looks like so:
class SprinklerListItem extends StatefulWidget {
// This class is the configuration for the state. It holds the
// values (in this nothing) provided by the parent and used by the build
// method of the State. Fields in a Widget subclass are always marked "final".
final Sprinkler _sprinkler;
SprinklerListItem(this._sprinkler);
#override
_SprinklerListItemState createState() {
return new _SprinklerListItemState(this._sprinkler);
}
}
class _SprinklerListItemState extends State<SprinklerListItem> {
final Sprinkler _sprinkler;
_SprinklerListItemState(this._sprinkler);
Color textColor = Colors.white;
Color bgColor = Colors.blue[800];
#override
Widget build(BuildContext context) {
return new Container(
height: 72.0, // in logical pixels
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 8.0),
decoration: new BoxDecoration(color: Colors.white),
// Row is a horizontal, linear layout.
child: new MaterialButton(
child: new Text(
_sprinkler.name,
style: new TextStyle(color: textColor)
),
splashColor: Colors.blueAccent,
color: bgColor,
onPressed: () {
this.setState(() {
textColor = Colors.grey;
bgColor = Colors.red;
});
},
),
);
}
}