I am new to Flutter, and trying to understand how to to perform a series of actions based on a state of the widget.
I have a VERY basic app, based on the default new Flutter app with the clicks counter. I'd like that when the counter hits 10, the counter text highlights in red for 500ms (showing '10') and then the counter gets reset back to 0 and the text goes back to black (showing '0').
I was able to change the color to red when _counter==10, but unsure how to change it back to black and reset the counter after a set period of time. I'd also want to make the button "unclickable" during the 500ms.
It's simple just follow the code below,
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
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 _counter = 0;
bool isButtonEnabled = true;
Future<void> _incrementCounter() async {
_counter++;
if (_counter == 10) {
setState(() {
isButtonEnabled = false;
});
await Future.delayed(Duration(milliseconds: 500));
setState(() {
_counter = 0;
isButtonEnabled = true;
});
} else {
setState(() {});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style:
TextStyle(color: isButtonEnabled ? Colors.black : Colors.red),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: isButtonEnabled ? _incrementCounter : null,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
You're done.
Related
I just started learning to code with Flutter & Dart. I could not fully understand the working principle of Flutter. There are no errors on the code I shared below. But I am not getting the result I want. On the code, the counter starts from 0. And there is a text on the counter that shows whether this counter is even or odd. This is done when the button is clicked. But the button only works once. The counter increases but the text does not change.
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: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
final String title;
const MyHomePage({
Key? key,
required this.title,
}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
String evenNumber = "Even Number";
String oddNumber = "Odd Number";
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(_counter / 2 == 0 ? evenNumber : oddNumber),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
Change this line:
_counter / 2 == 0 ? evenNumber : oddNumber
To:
_counter % 2 == 0 ? evenNumber : oddNumber
The '%' operator evaluates the remainder that you need. Not the half (_counter/2) of counter value.
You do not get your desired result because you are using using the normal division instead of modulo. With / you only check if the result is 0 and with % you can check if the number is even. To fix this change your Text widget to the following.
Text(_counter % 2 == 0 ? evenNumber : oddNumber),
Your full code should look like this now.
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: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
final String title;
const MyHomePage({
Key? key,
required this.title,
}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
String evenNumber = "Even Number";
String oddNumber = "Odd Number";
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(_counter / 2 == 0 ? evenNumber : oddNumber),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
I have a list and when I press the checkbox, I need the text to be stroken.
This is my method for checking the checkbox.
Any idea how I can implement the change of new textStyle here?
my method:
void toggleDone(TodoTask task, bool newValue) {
print("Status before processing");
task.status = newValue;
notifyListeners();
}
}
If u want to change sth depending on the state you can do it like that with tenary operator:
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 _counter = 0;
bool change = false;
void _incrementCounter() {
setState(() {
_counter++;
change = !change;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
style: TextStyle(color: change ? Colors.red :Colors.black87),
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
In this example text color will change when u click the button. So you need to change this 'control variable' inside your function and widget will rebuild.
You can use TextDecoration.lineThrough property of TextStyle to put a line through or strike through text. There are several examples of how to use TextStyle in Flutter docs. The code shows how to change the TextStyle when Checkbox value is changed.
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(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool _value = false;
//void toggleDone(TodoTask task, bool newValue) {
void toggleDone(bool newValue) {
print("Status before processing");
_value = newValue;
//task.status = newValue;
//notifyListeners();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Flutter Demo Home Page"),
),
body: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Checkbox(
value: _value,
onChanged: (val) => setState(() => toggleDone(val))),
Text('TextDecoration LineThrough Demo',
style: _value
? TextStyle(
decoration: TextDecoration.lineThrough, fontSize: 25)
: TextStyle(fontSize: 25)),
],
),
),
);
}
}
I am having a problem with the NotificationListener in flutter. I've build a simple testing app because I am struggling with it.
After clicking on the FlatButton the Notification should be dispatched and then caught by the NotificationListener in onNotification.
So the expected console output would be:
"TestNot"
"Bubble"
But all I am getting is "TestNot".
So the notification is not caught by the listener.
Any idea what I could be doing wrong?
Thank you :-)
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyNotification extends Notification {
final String title;
const MyNotification({this.title});
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
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 _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: NotificationListener<MyNotification>(
onNotification: (notification) {
print("Bubble");
return true;
},
child: Center(
child: Column(
// horizontal).
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
FlatButton(onPressed: () {print("TestNot"); MyNotification(title: "TestNot").dispatch(context);}, child: Text("TestNot")),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
)),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
When you need a child to notify its parent, you can use NotificationListener.
But, when you need communications to be implemented inversely, in other words, a parent to notify its children, you can use ValueListenableBuilder
A nice doc about it available here:
https://medium.com/flutter-community/flutter-notifications-bubble-up-and-values-go-down-c1a499d22d5f
"Flutter, notifications ‘bubble up’ and values ‘go down’"
You cannot receive the notification at the same level of where it was dispatched. Please refer to docs : https://api.flutter.dev/flutter/widgets/NotificationListener-class.html
NotificationListener class :
A widget that listens for Notifications bubbling up the tree.
I've updated your code to make it work.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyNotification extends Notification {
final String title;
const MyNotification({this.title});
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
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 _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: NotificationListener<MyNotification>(
onNotification: (MyNotification notification) {
print("Bubble");
return true;
},
child: Center(
child: Column(
// horizontal).
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
MyChild(),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
)),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class MyChild extends StatelessWidget {
#override
Widget build(BuildContext context) {
return FlatButton(
onPressed: () {
print("TestNot");
MyNotification(title: "TestNot").dispatch(context);
},
child: Text("TestNot"));
}
}
When testing, press the increment button a few times to view if the app restarts.
I was trying to "restart" a toy app in Flutter. I was using the old counter example and after modifying it the behavior is not the expected.
For example, with this code:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
UniqueKey _materialKey;
UniqueKey _homeKey;
#override
void initState() {
super.initState();
_awaitAndRun();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
key: _materialKey,
navigatorKey: _navigatorKey,
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(
key: _homeKey,
title: 'Flutter Demo Home Page',
),
);
}
Future<void> _awaitAndRun() async {
print('Starting delay... Please press the button before the time ends.');
await Future<dynamic>.delayed(Duration(seconds: 5));
setState(() {
_materialKey = UniqueKey();
_homeKey = UniqueKey();
});
print('Has been the screen reloaded?');
}
}
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 _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
The awaitAndRun function should restart the App because I am setting the _materialKey and _homeKey as new instances. As part of it, the MyHomePage widget also should be rebuilt (because the key value has changed) but it didn't.
I can understand it's because the _navigatorKey is "saving" the state of the MaterialApp but the MyHomePage should be rebuild because its key has changed!
It's even more strange because if I remove the _materialKey (as shown in the code below), the MyHomePage widget gets rebuilt.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
UniqueKey _materialKey;
UniqueKey _homeKey;
#override
void initState() {
super.initState();
_awaitAndRun();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
//key: _materialKey,
navigatorKey: _navigatorKey,
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(
key: _homeKey,
title: 'Flutter Demo Home Page',
),
);
}
Future<void> _awaitAndRun() async {
print('Starting delay... Please press the button before the time ends.');
await Future<dynamic>.delayed(Duration(seconds: 5));
setState(() {
_materialKey = UniqueKey();
_homeKey = UniqueKey();
});
print('Has been the screen reloaded?');
}
}
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 _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
So it doesn't have any sense for me, why does this happen? Why when I set the _materialKey the MyHomePage widget doesn't get rebuilt?
It's pretty normal. Flutter always target safe and clean state and doesn't keep any item usually time. You can use a lot of methods, so I think should provider
You need to game states keep to app state, so I created ToysGame(sample) and added count property, you can use and change this property so unaffected state changes.
Firstly wrap single provider instance to your app page.
home: Provider(
create: (context) => ToysGame(),
child: MyHomePage(
key: _homeKey,
title: 'Flutter Demo Home Page',
),
),
After doesn't keep count on in page state, you should use toys state with provider.
Text(
Provider.of<ToysGame>(context).count.toString(),
style: Theme.of(context).textTheme.headline4,
),
And you want to change value
void _incrementCounter() {
Provider.of<ToysGame>(context, listen: false).count++;
setState(() {});
}
Finally, it's done. I added on my stackhelpover repo with restart app lib name.
(You read provider package and maybe other feature help for you [ change notifier or multi-provider ])
StackHelpOver
How can I implement a triple-click button in flutter?
On triple-click, the button will store an entry in Firebase database.
Its pretty easy. You can use a GestureDetector() to check for the number of taps then you can provide your logic if there are 3 taps.
GestureDetector(
onTap: () {
int now = DateTime.now().millisecondsSinceEpoch;
if (now - lastTap < 1000) {
print("Consecutive tap");
consecutiveTaps ++;
print("taps = " + consecutiveTaps.toString());
if (consecutiveTaps == 3){
// Do something
}
} else {
consecutiveTaps = 0;
}
lastTap = now;
},
child: ...
)
Implementing "triple click" button in flutter might not be possible. But, if you really want to make it work ASAP then a simple method could be to maintain a counter for the number of clicks done. Once the count reaches 3, you need to add your entry to Firestore.
I have modified the code from the counter app boilerplate of flutter.
Hope you have cloud_firestore in your pubspec.yaml file. If not then add it and put the services.json as well in the app folder of android or respective directory of ios.
cloud_firestore: ^0.13.4+1
So, now you can have a look at the code that I am using.
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.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 _counter = 0;
_incrementCounter() {
setState(() {
_counter++;
});
if (_counter == 3) {
Firestore.instance
.collection('/sampleData')
.add({'data': "data"}).catchError((e) {
print(e);
});
setState(() {
_counter = 0;
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
I have edited the _incrementCounter function. I added a conditional statement to check the _counter if it is 3 or not. Then, I am adding the Firestore entry. Later on, the most important bit is to set the _counter as 0 so that the next time the user presses the button 3 times, then the code will work accordingly. You can customize it according to your needs.
But remember, triple clicks have not been yet invented in flutter and this is just a work-around solution and don't use it for Real-life Development applications as this would be a very bad practice.