Flutter: How to rerun widget or build - dart

I am trying to create a quiz application and in my playQuiz, after the player has answered the correct answer then new answers would be generated. I have tried to make a function "foo" and call it so it would change the image and texts in the buttons. Still, nothing changes, this only changes when I rotate my screen. The code is short and easy to understand.
class Game extends StatelessWidget {
final ForceSelection forceSelection;
Game({Key key, #required this.forceSelection}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: forceSelection.langSelection, // TODO should work and change later
theme: new ThemeData(
primaryColor: Colors.white,
),
home: PlayQuiz(),
);
}
}
class PlayQuizState extends State<PlayQuiz> {
final ForceSelection forceSelection; // TODO Does this get forceSelection
List<String> groundForce = [ 'sotamies', 'aliupseerioppilas', 'korpraali', 'alikersantti', 'upseerioppilas', 'kersantti', 'upseerikokelas', 'ylikersantti', 'vääpeli', 'ylivääpeli', 'sotilasmestari', 'vänrikki', 'luutnantti', 'yliluutnantti', 'kapteeni', 'majuri', 'everstiluutnantti', 'eversti', 'prikaatikenraali', 'kenraalimajuri', 'kenraaliluutnantti', 'kenraali'];
List<int> randomList = Helper.randomAnswers();
static var rng = new Random();
var corrAnsIndex = rng.nextInt(3);
int points = 0;
String quizImage = "";
String debugText = "";
PlayQuizState(this.forceSelection); // TODO Does this get langSelection
void _startGame() {
quizImage = 'images/' + groundForce[randomList[corrAnsIndex]].replaceAll('ä','a') + '.jpg';
}
void _nextGame() {
setState(() {
points += 1;
randomList = Helper.randomAnswers();
corrAnsIndex = rng.nextInt(3);
quizImage = 'images/' + groundForce[randomList[corrAnsIndex]].replaceAll('ä','a') + '.jpg';
debugText = forceSelection.langSelection + randomList.toString() + " " + groundForce[randomList[corrAnsIndex]].replaceAll('ä','a');
// TODO edited debug text and next is to check if langSelection is fin
});
}
void _endGame() {
setState(() {
//TODO Ask for name and send to database
});
}
#override
Widget build(BuildContext context) {
_startGame();
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image.asset(quizImage),
Text("randomList: " + randomList.toString()),
Text("Oikea vastaus: " +
groundForce[randomList[corrAnsIndex]] +
" corrAnsIndex: " +
corrAnsIndex.toString()),
Text(debugText + " points: " + points.toString()),
//Text("Voima valinta: " + forceSelection = Game.forceSelection),
RaisedButton(
child: Text(groundForce[randomList[0]]),
onPressed: () {
if (0 == corrAnsIndex) {
_nextGame();
//this.foo();
}
}),
RaisedButton(
child: Text(groundForce[randomList[1]]),
onPressed: () {
if (1 == corrAnsIndex) {
_nextGame();
}
}),
RaisedButton(
child: Text(groundForce[randomList[2]]),
onPressed: () {
if (2 == corrAnsIndex) {
_nextGame();
}
}),
RaisedButton(
child: Text(groundForce[randomList[3]]),
onPressed: () {
if (3 == corrAnsIndex) {
_nextGame();
}
}),
],
)),
);
}
}
class PlayQuiz extends StatefulWidget {
#override
PlayQuizState createState() => new PlayQuizState(ForceSelection("","")); //TODO how to pass vars to ""
}

You must use setState to rebuild the widget in order to update the view, also change your widget into a StatefulWidget, not StatelessWidget.
Docs: https://docs.flutter.io/flutter/widgets/State/setState.html
https://flutterdoc.com/stateful-or-stateless-widgets-42a132e529ed
I suggest you should spend more time to learn some fundamental ideas of flutter then continue your development.

Related

how to disable button after first click in flutter?

Clicked Button multiple times same time, open pages multiple times. How to fix this issue? I also uploaded the gif file on my application(double click on the image).
Container(
padding: EdgeInsets.all(10.0),
child: ButtonTheme(
minWidth: 10.0,
height: 40.0,
child: RaisedButton(
child: Text(
AppTranslations.of(context)
.text("loginpage_button"),
style: TextStyle(
color: Colors.white, fontSize: 15.0),
),
onPressed: () async{
(isOffline)
? _showSnackBar()
: checking2(usernameController, context, _url);
},
color: Colors.blue,
padding: EdgeInsets.all(20.0),
),
),
margin: EdgeInsets.only(top: 0.0),
)
I used this code, it's working, but user types username incorrectly, user cant click button second type. this is my code.
onPressed: () async {
if (_firstClick) {
_firstClick = false;
(isOffline)
? _showSnackBar()
: checking2(usernameController, context, _url);
}
Solved this in my application based on calculating time difference.
First, declare a DateTime variable and define the function as follows:
DateTime loginClickTime;
bool isRedundentClick(DateTime currentTime) {
if (loginClickTime == null) {
loginClickTime = currentTime;
print("first click");
return false;
}
print('diff is ${currentTime.difference(loginClickTime).inSeconds}');
if (currentTime.difference(loginClickTime).inSeconds < 10) {
// set this difference time in seconds
return true;
}
loginClickTime = currentTime;
return false;
}
In the login button call the function as follows to check for redundancy:
RaisedButton(
child: Text('Login'),
onPressed: () {
if (isRedundentClick(DateTime.now())) {
print('hold on, processing');
return;
}
print('run process');
},
),
Create a bool variable which will be true when the button is pressed, (hence, initial value is set to false).
bool _clicked = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: RaisedButton(
child: Text('Button'),
onPressed: _clicked
? null
: () {
setState(() => _clicked = true); // set it to true now
},
),
);
}
You can use a bool variable to save the state of your RaisedButton:
First create the variable a set its initial value :
var _firstPress = true;
Then add _firstPress inside your onPressed function :
Container(
padding: EdgeInsets.all(10.0),
child: ButtonTheme(
minWidth: 10.0,
height: 40.0,
child: RaisedButton(
child: Text(
AppTranslations.of(context).text("loginpage_button"),
style: TextStyle(color: Colors.white, fontSize: 15.0),
),
onPressed: () async {
// This is what you should add in your code
if (_firstPress) {
_firstPress = false;
(isOffline) ? _showSnackBar() : checking2(usernameController, context, _url);
}
},
color: Colors.blue,
padding: EdgeInsets.all(20.0),
),
),
margin: EdgeInsets.only(top: 0.0),
),
This way your onPressed function will only respond to the RaisedButton's first click.
I've written two classes for myself that may be helpful for others. They encapsulate the answer given by others in this thread so that you don't have a bunch of bools and assignment statements floating everywhere.
You pass your function to the class, and use the class' "invoke" method in place of the function. This currently does not support functions that need parameters, but is useful for the void case.
typedef void CallOnceFunction();
class CallOnce {
bool _inFunction = false;
final CallOnceFunction function;
CallOnce(CallOnceFunction function) :
assert(function != null),
function = function
;
void invoke() {
if (_inFunction)
return;
_inFunction = true;
function();
_inFunction = false;
}
}
typedef Future<void> CallOnceFuture();
class CallFutureOnce {
bool _inFunction = false;
final CallOnceFuture future;
CallFutureOnce(CallOnceFuture future) :
assert(future != null),
future = future
;
Future<void> invoke() async {
if (_inFunction)
return;
_inFunction = true;
await this.future();
_inFunction = false;
}
}
Update: Here's an example of both of these classes in action
/*Example*/
import 'package:flutter/material.dart';
class MyWidget extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return new MyWidgetState();
}
}
class MyWidgetState extends State<MyWidget> {
CallOnce _callOnce;
CallFutureOnce _callFutureOnce;
void myFunction() {
/*Custom Code*/
}
Future<void> myFutureFunction() async {
/*Custom Code*/
//await something()
}
#override
void initState() {
super.initState();
this._callOnce = CallOnce(this.myFunction);
this._callFutureOnce = CallFutureOnce(this.myFutureFunction);
}
#override
Widget build(BuildContext context) {
return Scaffold (
body: Center (
child: RaisedButton (
child: Text('Try Me'),
onPressed: this._callOnce.invoke,
),
),
floatingActionButton: FloatingActionButton (
child: Icon(Icons.save),
onPressed: this._callFutureOnce.invoke,
),
);
}
}
Some of the other solutions do not work for me, and some of them are not isolated in their own state and I implemented my solution to encapsulate the functionality in my custom widget. I implemented it for IconButton but you could modify it with any tappable widget. Cheers:
import 'package:flutter/material.dart';
class AppIconButton extends StatefulWidget {
const AppIconButton({
Key? key,
required this.onPressed,
required this.icon,
this.disableAfterClick = const Duration(milliseconds: 500),
}) : super(key: key);
final Function onPressed;
final Widget icon;
final Duration disableAfterClick;
#override
State<AppIconButton> createState() => _AppIconButtonState();
}
class _AppIconButtonState extends State<AppIconButton> {
bool _acceptsClicks = true;
#override
Widget build(BuildContext context) {
return IconButton(
onPressed: () {
if (_acceptsClicks) {
//if you want to disable the button
//use the variable with setState method
//but it's not my case
_acceptsClicks = false;
widget.onPressed();
Future.delayed(widget.disableAfterClick, () {
if (mounted) {
_acceptsClicks = true;
}
});
}
// else {
// debugPrint("Click ignored");
// }
},
icon: widget.icon,
);
}
}
Disabling multiple click events in a flutter with StatelessWidget.
Using as a shareable widget.
Simple example:
class SingleTapEvent extends StatelessWidget {
final Widget child;
final Function() onTap;
bool singleTap = false;
SingleTapEvent(
{Key? key, required this.child, required this.onTap, singleTap = false})
: super(key: key);
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
if (!singleTap) {
Function.apply(onTap, []);
singleTap = true;
Future.delayed(const Duration(seconds: 3)).then((value) => singleTap = false);
}
},
child: child);
}
}
Usage:
SingleTapEvent(
onTap: () {
print("Clicked");
},
child: Text("Click me"),
);
This question is answered here How do I disable a Button in Flutter?
All you need to use statefulWidget and create a variable to hold your condition, And change it according to your event. Your button will be enable or disable according to your variable's value.
Suppose initial state of your variable, isDisable = false,that means - your button is enable by default. And after first clicking change the value of your state variable isDisable = true.
Instead of using RaisedButton directly, you can turn it into a StatefulWidget. Then use the ChangeNotifier to change it state from enable to disable and control button press function.It will also help you to reuse it in different places. Here is an example how can you do that
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> {
final ValueNotifier<MyButtonState> _myButtonStateChangeNotifier =
ValueNotifier(MyButtonState.enable);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: MyButton(
buttonStateChangeNotifier: _myButtonStateChangeNotifier,
onPressed: _onButtonPressed,
text: "Click Me",
),
),
);
}
_onButtonPressed() {
print("Button Pressed");
_myButtonStateChangeNotifier.value = MyButtonState.disable;
}
}
enum MyButtonState {enable, disable}
class MyButton extends StatefulWidget {
final VoidCallback onPressed;
final String text;
final TextStyle textStyle;
final ValueNotifier<MyButtonState> buttonStateChangeNotifier;
MyButton({
#required this.onPressed,
this.text = "",
this.textStyle,
this.buttonStateChangeNotifier,
});
#override
_MyButtonState createState() => _MyButtonState();
}
class _MyButtonState extends State<MyButton> {
MyButtonState _myButtonState = MyButtonState.enable;
#override
void initState() {
super.initState();
if (widget.buttonStateChangeNotifier != null) {
widget.buttonStateChangeNotifier.addListener(_handleButtonStateChange);
_myButtonState = widget.buttonStateChangeNotifier.value;
}
}
#override
Widget build(BuildContext context) {
return RaisedButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(4)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(widget.text)
],
),
onPressed: _myButtonState == MyButtonState.enable
? _handleOnPress
: null,
);
}
_handleButtonStateChange() {
setState(() {
_myButtonState = widget.buttonStateChangeNotifier.value;
});
}
_handleOnPress() {
if (_myButtonState == MyButtonState.enable) {
widget.onPressed();
}
}
}
Thanks to #Mazin Ibrahim's suggestion above, setting a basic bool toggle flag works fine.
This implementation is based on handling the enable/disable logic at the callback level, independent of the widget layout details.
bool _isButtonEnabled = true;
MessageSql _messageSql = new MessageSql(); // DB helper class
final TextEditingController eCtrl = new TextEditingController();
_onSendMessage(String message) {
if (! _isButtonEnabled) {
return;
}
_isButtonEnabled = false;
_messageSql.insert(message).then((resultId) {
// only update all if save is successful
eCtrl.clear();
AppUi.dismissKeyboard();
_isButtonEnabled = true;
Future.delayed(const Duration(milliseconds: 400), () {
setState(() {});
})
.catchError((error, stackTrace) {
print("outer: $error");
});
}
Similar to what PreciseSpeech and Sharman implemented, I made a few changes and it works.
DateTime loginClickTime = DateTime.now();
#override
void initState() {
loginClickTime;
super.initState();
}
bool isRedundentClick(DateTime currentTime) {
if (loginClickTime == '') {
loginClickTime = currentTime;
print("first click");
return false;
}
print('diff is ${currentTime.difference(loginClickTime).inSeconds}');
if (currentTime.difference(loginClickTime).inSeconds < 10) {
//set this difference time in seconds
return true;
}
loginClickTime = currentTime;
return false;
}
Then in the OnPressed function
MaterialButton(
child:Text('Login'),
onPressed: (){
if(isRedundentClick(DateTime.now())){
print('hold on, processing');
return;
}
print('run process');
},
)

How to write a double back button pressed to exit app using flutter

I'm new to flutter, and I saw many android apps can exit when double press back button.
The first time press back button, app shows a toast"press again to exit app".
The following second press, app exits.
Of course, the time between two press must be not long.
How to do it in flutter?
This is an example of my code (I've used "fluttertoast" for showing toast message, you can use snackbar or alert or anything else)
DateTime currentBackPressTime;
#override
Widget build(BuildContext context) {
return Scaffold(
...
body: WillPopScope(child: getBody(), onWillPop: onWillPop),
);
}
Future<bool> onWillPop() {
DateTime now = DateTime.now();
if (currentBackPressTime == null ||
now.difference(currentBackPressTime) > Duration(seconds: 2)) {
currentBackPressTime = now;
Fluttertoast.showToast(msg: exit_warning);
return Future.value(false);
}
return Future.value(true);
}
You can try this package.
Inside a Scaffold that wraps all your Widgets, place the DoubleBackToCloseApp passing a SnackBar:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: DoubleBackToCloseApp(
child: Home(),
snackBar: const SnackBar(
content: Text('Tap back again to leave'),
),
),
),
);
}
}
The solution below must be considered deprecated because it causes a few issues that were tackled in the package mentioned. For instance, the app closes if the snack bar was dismissed by the user (see hcbpassos/double_back_to_close_app#2).
Old answer
You can also opt for a solution involving SnackBar. It's not as simple as Andrey Turkovsky's answer, but it's quite more elegant and you won't depend on a library.
class _FooState extends State<Foo> {
static const snackBarDuration = Duration(seconds: 3);
final snackBar = SnackBar(
content: Text('Press back again to leave'),
duration: snackBarDuration,
);
DateTime backButtonPressTime;
#override
Widget build(BuildContext context) {
return Scaffold(
// The BuildContext must be from one of the Scaffold's children.
body: Builder(
builder: (context) {
return WillPopScope(
onWillPop: () => handleWillPop(context),
child: Text('Place your child here'),
);
},
),
);
}
Future<bool> handleWillPop(BuildContext context) async {
final now = DateTime.now();
final backButtonHasNotBeenPressedOrSnackBarHasBeenClosed =
backButtonPressTime == null ||
now.difference(backButtonPressTime) > snackBarDuration;
if (backButtonHasNotBeenPressedOrSnackBarHasBeenClosed) {
backButtonPressTime = now;
Scaffold.of(context).showSnackBar(snackBar);
return false;
}
return true;
}
}
Unfortunately none of them worked for me, I have written one generic class (widget) to handle double tap exit. If someone is interested
class DoubleBackToCloseWidget extends StatefulWidget {
final Widget child; // Make Sure this child has a Scaffold widget as parent.
const DoubleBackToCloseWidget({
#required this.child,
});
#override
_DoubleBackToCloseWidgetState createState() =>
_DoubleBackToCloseWidgetState();
}
class _DoubleBackToCloseWidgetState extends State<DoubleBackToCloseWidget> {
int _lastTimeBackButtonWasTapped;
static const exitTimeInMillis = 2000;
bool get _isAndroid => Theme.of(context).platform == TargetPlatform.android;
#override
Widget build(BuildContext context) {
if (_isAndroid) {
return WillPopScope(
onWillPop: _handleWillPop,
child: widget.child,
);
} else {
return widget.child;
}
}
Future<bool> _handleWillPop() async {
final _currentTime = DateTime.now().millisecondsSinceEpoch;
if (_lastTimeBackButtonWasTapped != null &&
(_currentTime - _lastTimeBackButtonWasTapped) < exitTimeInMillis) {
Scaffold.of(context).removeCurrentSnackBar();
return true;
} else {
_lastTimeBackButtonWasTapped = DateTime.now().millisecondsSinceEpoch;
Scaffold.of(context).removeCurrentSnackBar();
Scaffold.of(context).showSnackBar(
_getExitSnackBar(context),
);
return false;
}
}
SnackBar _getExitSnackBar(
BuildContext context,
) {
return SnackBar(
content: Text(
'Press BACK again to exit!',
color: Colors.white,
),
backgroundColor: Colors.red,
duration: const Duration(
seconds: 2,
),
behavior: SnackBarBehavior.floating,
);
}
}
Use this class following way:
class Dashboard extends StatelessWidget {
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: DoubleBackToCloseWidget(
child: Container(
child: Column(
children: [
const Text('Hello there'),
const Text('Hello there again'),
],
),
),
),
),
);
}
}
The first time press back button, app shows a AlertDialog"press yes to exit app and press No to can't exit application".
This is an example of my code (I've used 'AlertDialog')
#override
Widget build(BuildContext context) {
return new WillPopScope(
onWillPop: _onBackPressed,
child: DefaultTabController(
initialIndex: _selectedIndex,
length: choices.length,
child: Scaffold(
appBar: AppBar(
),
),
),
);
}
Future<bool> _onBackPressed() {
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text('Are you sure?'),
content: Text('Do you want to exit an App'),
actions: <Widget>[
FlatButton(
child: Text('No'),
onPressed: () {
Navigator.of(context).pop(false);
},
),
FlatButton(
child: Text('Yes'),
onPressed: () {
Navigator.of(context).pop(true);
},
)
],
);
},
) ?? false;
}
This is my answer. I used AlertDialog() to achieve this
#override
Widget build(BuildContext context) {
return new WillPopScope(
onWillPop: _onBackPressed,
child: Scaffold(
appBar: AppBar(),
body: Container(),
),
);
}
Future<bool> _onBackPressed() {
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text('Confirm'),
content: Text('Do you want to exit the App'),
actions: <Widget>[
FlatButton(
child: Text('No'),
onPressed: () {
Navigator.of(context).pop(false); //Will not exit the App
},
),
FlatButton(
child: Text('Yes'),
onPressed: () {
Navigator.of(context).pop(true); //Will exit the App
},
)
],
);
},
) ?? false;
}
Simply use double_back_to_close_app library
https://pub.dev/packages/double_back_to_close_app
Add double_back_to_close_app under dependencies in pubspec.yaml file
dependencies:
double_back_to_close_app: ^1.2.0
Here example code
import 'package:double_back_to_close_app/double_back_to_close_app.dart';
import 'package:flutter/material.dart';
void main() => runApp(Example());
class Example extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: DoubleBackToCloseApp(
snackBar: const SnackBar(
content: Text('Tap back again to leave'),
),
child: Center(
child: OutlineButton(
child: const Text('Tap to simulate back'),
// ignore: invalid_use_of_protected_member
onPressed: WidgetsBinding.instance.handlePopRoute,
),
),
),
),
);
}
}
Just move your body contents to "DoubleBackToCloseApp's" child
The best solution without using a package use System
SystemChannels.platform.invokeMethod<void>('SystemNavigator.pop');
or
SystemNavigator.pop();
Full Code
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:fluttertoast/fluttertoast.dart';
class ExitApp extends StatefulWidget {
final Widget child;
const ExitApp({
Key? key,
required this.child,
}) : super(key: key);
#override
_ExitAppState createState() => _ExitAppState();
}
class _ExitAppState extends State<ExitApp> {
#override
build(BuildContext context) {
DateTime timeBackPressed = DateTime.now();
return WillPopScope(
child: widget.child,
onWillPop: () async {
final differeance = DateTime.now().difference(timeBackPressed);
timeBackPressed = DateTime.now();
if (differeance >= Duration(seconds: 2)) {
final String msg = 'Press the back button to exit';
Fluttertoast.showToast(
msg: msg,
);
return false;
} else {
Fluttertoast.cancel();
SystemNavigator.pop();
return true;
}
},
);
}
}
https://pub.dev/packages/flutter_close_app
This is my solution, it is very flexible and simple, does not depend on routing navigation, any page can close the App, such as my login page, and if it is Drawer and PageView, it can also flexibly support custom conditions, and does not need to rely on native method. The following functions are supported:
✅ Press back 2 times to close app
✅ Custom time interval
✅ Customize the prompt message
✅ Customize matching conditions
✅ Support Android
✅ One click to close app
✅ Support iOS
✅ Support MacOS
✅ Support Windows
✅ Support Linux
Easy to Use and Understand, double tap to exit;
Change the duration to 10000, and short toast message time;
import 'dart:io';
bool back = false;
int time = 0;
int duration = 1000;
Future<bool> willPop() async{
int now = DateTime.now().millisecondsSinceEpoch;
if(back && time >= now){
back = false;
exit(0);
}else{
time = DateTime.now().millisecondsSinceEpoch+ duration;
print("again tap");
back = true;
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("Press again the button to exit")));
}
return false;
}
return WillPopScope(
onWillPop: onWill,
child: Scaffold()
);
If you want a snackbar you should provide a scaffold key as it's related to a scaffold, so this key should make the trick of calling a snackbar outside of it's scaffold parent.
Here is a solution :
class Home extends StatelessWidget {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async{
DateTime initTime = DateTime.now();
popped +=1;
if(popped>=2) return true;
await _scaffoldKey.currentState.showSnackBar(
SnackBar(
behavior: SnackBarBehavior.floating,
content: Text('Tap one more time to exit.',textAlign: TextAlign.center,),
duration: Duration(seconds: 2),
)).closed;
// if timer is > 2 seconds reset popped counter
if(DateTime.now().difference(initTime)>=Duration(seconds: 2)) {
popped = 0;
}
return false;
},
child: Scaffold(
key: _scaffoldKey,
appBar: AppBar(title : Text("Demo")),
body: Text("body")
);
)
}
This is my solution, you can change backPressTotal value to the number of pressed you want!
int backPressCounter = 0;
int backPressTotal = 2;
#override
Widget build(BuildContext context) {
return Scaffold(
...
body: WillPopScope(child: getBody(), onWillPop: onWillPop),
);
}
Future<bool> onWillPop() {
if (backPressCounter < 2) {
Fluttertoast.showToast(msg: "Press ${backPressTotal - backPressCounter} time to exit app");
backPressCounter++;
Future.delayed(Duration(seconds: 1, milliseconds: 500), () {
backPressCounter--;
});
return Future.value(false);
} else {
return Future.value(true);
}
}
If the condition is that the user presses only twice, you can use the first solution of course.
If you want to increase the number of times you click, you can use this solution. Where the user has to press 3 times within two seconds so he can get out
DateTime currentBackPressTime;
/// init counter of clicks
int pressCount=1;
then :
Future<bool> onWillPop() async {
DateTime now = DateTime.now();
/// here I check if number of clicks equal 3
if(pressCount!=3){
///should be assigned at the first click.
if(pressCount ==1 )
currentBackPressTime = now;
pressCount+=1;
return Future.value(false);
}else{
if (currentBackPressTime == null ||
now.difference(currentBackPressTime) > Duration(seconds: 2)) {
currentBackPressTime = now;
pressCount=0;
return Future.value(false);
}
}
return Future.value(true);
}
You can look for time duration between the two consecutive back button clicks, and if the difference is within the desired duration then exit the app.
Here is the complete code sample for the counter app, which exits the app only if the difference between two consecutive back button clicks is less than 1 second (1000 ms)
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
void showSnackBar() {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
behavior: SnackBarBehavior.floating,
duration: Duration(milliseconds: 600),
margin: EdgeInsets.only(bottom: 0, right: 32, left: 32),
content: Text('Tap back button again to exit'),
),
);
}
void hideSnackBar() {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
}
DateTime oldTime = DateTime.now();
DateTime newTime = DateTime.now();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: WillPopScope(
onWillPop: () async {
newTime = DateTime.now();
int difference = newTime.difference(oldTime).inMilliseconds;
oldTime = newTime;
if (difference < 1000) {
hideSnackBar();
return true;
} else {
showSnackBar();
return false;
}
},
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
DateTime BackPressTime = DateTime.now();
#override
Widget build(BuildContext context) {
return Scaffold(
body: WillPopScope(
child: Home(),
onWillPop: exiteApp,
),
);
}
Future<bool> exiteApp() {
print("exite app");
DateTime now = DateTime.now();
if(now.difference(BackPressTime)< Duration(seconds: 2)){
return Future(() => true);
}
else{
BackPressTime = DateTime.now();
Fluttertoast.showToast(msg: "Press agin");
return Future(()=> false);
}
}

How do I convert an int into a string in Dart?

So at the moment, I have a number represented by counter. The number is incremental and continues to change. My goal is to add commas every 3 digits to make the number more readable in my app. The only issue that I am running into is that
String number = counter.replaceAllMapped(new RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'),
(Match m) => '${m[1]},');
refuses to take the integer counter as an int. How do I convert int counter into String middle before running it through
String number = middle.replaceAllMapped(new RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'),
(Match m) => '${m[1]},');
My goal is this:
counter = 1000;
middle = "1000";
number = "1,000";
Will my current solution even work? If not, how do I change my code to meet my goal?
Update:
Here is my code:
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int counter = 0;
void _incrementCounter() {
setState(() {
if (counter > 99999999) {
//WinLandingPage();
} else if (counter >= 99000000) {
counter++;
} else if (counter > 990000) {
counter += 1000000;
} else if (counter > 9900) {
counter += 10000;
} else if (counter > 99) {
counter += 100;
} else counter++;
});
}
//BELOW is where the format is taking place, and I have no idea how to
//get this part working. This is my broken attempt.
var f = new NumberFormat("#,###,###,###", "en_US");
var number = f.format(counter);
//What am I doing wrong here?
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar (
title: new Text(widget.title),
),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text(
'Try to get $number to 100,000,000.',
),
new Text(
'$number',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: new FloatingActionButton(
onPressed: _incrementCounter,
backgroundColor: Colors.pinkAccent,
tooltip: 'Increment',
child: new Icon(Icons.add),
),
);
}
}
What am I doing wrong in this piece of code? I am super unfamiliar with Dart, but I am doing my best to figure it out from trial, error, and support from you guys.
You can use the NumberFormat from the intl package to do that
An example from the README.md
var f = new NumberFormat("###.0#", "en_US");
print(f.format(12.345));
==> 12.34
Your example could be
var f = new NumberFormat("#,###", "en_US");
print(f.format(1000));
==> 1,000

screen not re-rendering after changing state

I'm just starting with Flutter, finished the first codelab and tried to add some simple functionality to it.
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Startup Name Generator',
theme: new ThemeData(primaryColor: Colors.deepOrange),
home: new RandomWords(),
);
}
}
class RandomWords extends StatefulWidget {
#override
createState() => new RandomWordsState();
}
class RandomWordsState extends State<RandomWords> {
final _suggestions = <WordPair>[];
final _biggerFont = new TextStyle(fontSize: 18.0);
final _saved = new Set<WordPair>();
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Startup Name Generator'),
actions: <Widget>[
new IconButton(
icon: new Icon(Icons.list),
onPressed: _pushSaved,
)
],
),
body: _buildSuggestions(),
);
}
Widget _buildSuggestions() {
return new ListView.builder(
padding: const EdgeInsets.all(16.0),
itemBuilder: (context, i) {
if (i.isOdd) return new Divider();
final index = i ~/ 2;
if (index >= _suggestions.length) {
_suggestions.addAll(generateWordPairs().take(10));
}
return _buildRow(_suggestions[index]);
},
);
}
Widget _buildRow(WordPair pair) {
final alreadySaved = _saved.contains(pair);
return new ListTile(
title: new Text(
pair.asPascalCase,
style: _biggerFont,
),
trailing: new IconButton(
icon: new Icon(alreadySaved ? Icons.favorite : Icons.favorite_border),
color: alreadySaved ? Colors.red : null,
onPressed: () {
setState(() {
if (alreadySaved) {
_saved.remove(pair);
} else {
_saved.add(pair);
}
});
},
));
}
void _pushSaved() {
Navigator.of(context).push(
new MaterialPageRoute(
builder: (context) {
final tiles = _saved.map(
(pair) {
return _buildRow(pair);
// new ListTile(
// title: new Text(
// pair.asPascalCase,
// style: _biggerFont,
// ),
// );
},
);
final divided = ListTile
.divideTiles(
context: context,
tiles: tiles,
)
.toList();
return new Scaffold(
appBar: new AppBar(
title: new Text('Saved Suggestions'),
),
body: new ListView(children: divided),
);
},
),
);
}
}
In the Save suggestions screen I built the same row as in the Sugestions Screen.
In the Saved Sugstions screen when you click the heart icon the element is removed from the array of saved items but the screen is not re-rendered.
what am I doing wrong here?
thanks!
Update
Actually your app is working perfectly fine with me :/
Because you are not communicating the state change with the icon change. You are already changing state based on alreadySaved, notice how you managed setState()
setState(() {
if (alreadySaved) {
_saved.remove(pair);
} else {
_saved.add(pair);
}
});
In the previous block you are only removing or adding to your favourite list based on the boolean value of alreadySaved and you are not telling setState to change anything else. That is why the following does not produce a re-render even though alreadySaved is switching values
///These two lines do not know what is happening
icon: new Icon(alreadySaved ? Icons.favorite : Icons.favorite_border),
color: alreadySaved ? Colors.red : null,
So you can instead do the following
icon: new Icon(_whichIcon), //initialized var _whichIcon = Icons.favorite_border
color: _whichIconColor, //Initialized var _whichIconColor = Colors.transparent
And your setState would be:
setState(() {
if (alreadySaved) {
_saved.remove(pair);
_whichIcon = Icons.favorite_border ;
_whichIconColor = Colors.transparent;
} else {
_saved.add(pair);
_whichIcon = Icons.favorite ;
_whichIconColor = Colors.red;
}
});
Or simpler you can do it like this, and keep your icon logic unchanged:
bool alreadySaved = false;
...
setState(() {
if (_saved.contains(pair)) {
_saved.remove(pair);
alreadySaved = false;
} else {
_saved.add(pair);
alreadySaved = true;
}
});

flutter: CircleAvatar with fallback text

I'm learning Flutter and would like to make a Widget just like the built-in CircleAvatar. However, I would like the behaviour to be
specify both an Image (NetworkImage) and initials (ie, BB)
while the image isn't loaded, show the initials
if the image does load, show the image and remove the initials
The following code sort of works, but when used in the Chat demo it falls apart as multiple MyAvatars are added.
Breakpointing on initState shows that it is always called with the first message text that is entered - not what I expected.
It also flickers as images "reload". It appears that the widgets are being reused in a way I don't understand.
class MyAvatar extends StatefulWidget {
NetworkImage image;
MyAvatar({this.text}) {
debugPrint("MyAvatar " + this.text);
if (text.contains('fun')) {
this.image = new NetworkImage("https://cdn3.iconfinder.com/data/icons/minicons-for-web-sites/24/minicons2-14-512.png");
}
}
final String text;
#override
MyAvatarState createState() {
return new MyAvatarState();
}
}
class MyAvatarState extends State<MyAvatar> {
bool showImage = false;
#override
initState() {
super.initState();
if (widget.image != null) {
var completer = widget.image.load(widget.image);
completer.addListener((info, sync) {
setState(() {
showImage = true;
});
});
}
}
#override
Widget build(BuildContext context) {
return !showImage ? new CircleAvatar(radius: 40.0, child: new Text(widget.text[0]))
: new CircleAvatar(radius: 40.0, backgroundImage: widget.image);
}
}
I'm still having trouble - full code
import 'package:flutter/material.dart';
// Modify the ChatScreen class definition to extend StatefulWidget.
class ChatScreen extends StatefulWidget { //modified
ChatScreen() {
debugPrint("ChatScreen - called on hot reload");
}
#override //new
State createState() {
debugPrint("NOT on hot reload");
return new ChatScreenState();
} //new
}
// Add the ChatScreenState class definition in main.dart.
class ChatScreenState extends State<ChatScreen> {
final List<ChatMessage> _messages = <ChatMessage>[];
final TextEditingController _textController = new TextEditingController(); //new
ChatScreenState() {
debugPrint("ChatScreenState - not called on hot reload");
}
#override //new
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: new Text("Friendlychat")),
body: new Column( //modified
children: <Widget>[ //new
new Flexible( //new
child: new ListView.builder( //new
padding: new EdgeInsets.all(8.0), //new
reverse: true, //new
itemBuilder: (_, int index) => _messages[index], //new
itemCount: _messages.length, //new
) //new
), //new
new Divider(height: 1.0), //new
new Container( //new
decoration: new BoxDecoration(
color: Theme.of(context).cardColor), //new
child: _buildTextComposer(), //modified
), //new
] //new
), //new
);
}
Widget _buildTextComposer() {
return new IconTheme(
data: new IconThemeData(color: Theme
.of(context)
.accentColor),
child:
new Container(
margin: const EdgeInsets.symmetric(horizontal: 8.0),
child: new Row(
children: <Widget>[
new Container( //new
margin: new EdgeInsets.symmetric(horizontal: 4.0), //new
child: new IconButton( //new
icon: new Icon(Icons.send),
onPressed: () =>
_handleSubmitted(_textController.text)), //new
),
new Flexible(
child: new TextField(
controller: _textController,
onSubmitted: _handleSubmitted,
decoration: new InputDecoration.collapsed(
hintText: "Send a message"),
)
),
])
)
);
}
void _handleSubmitted(String text) {
_textController.clear();
ChatMessage message = new ChatMessage(text: text);
setState(() {
_messages.insert(0, message);
});
}
}
const String _name = "Hardcoded Name";
class ChatMessage extends StatelessWidget {
ChatMessage({this.text, this.image, this.useImage});
final String text;
final NetworkImage image;
final Map useImage;
#override
Widget build(BuildContext context) {
var use = true; //useImage != null && useImage['use'];
var image = new NetworkImage("https://cdn3.iconfinder.com/data/icons/minicons-for-web-sites/24/minicons2-14-512.png");
if (text.contains('bad')) {
image = new NetworkImage("https://cdn3.iconfinder.com/data/icons/minicons-for-web-sites/24/minicons2-14-512.pngz");
}
return new Container(
margin: const EdgeInsets.symmetric(vertical: 10.0),
child: new Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Container(
margin: const EdgeInsets.only(right: 16.0),
child : new CustomCircleAvatar(initials: text[0], myImage: image)
),
new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Text(_name, style: Theme.of(context).textTheme.subhead),
new Container(
margin: const EdgeInsets.only(top: 5.0),
child: new Text(text),
),
],
),
],
),
);
}
}
class CustomCircleAvatar extends StatefulWidget {
NetworkImage myImage;
String initials;
CustomCircleAvatar({this.myImage, this.initials}) {
debugPrint(initials);
}
#override
_CustomCircleAvatarState createState() => new _CustomCircleAvatarState();
}
class _CustomCircleAvatarState extends State<CustomCircleAvatar>{
bool _checkLoading = true;
#override
void initState() {
if (widget.myImage != null) {
widget.myImage.resolve(new ImageConfiguration()).addListener((image, sync) {
if (mounted && image != null) {
setState(() {
_checkLoading = false;
});
}
});
}
}
#override
Widget build(BuildContext context) {
return _checkLoading == true ? new CircleAvatar(child: new Text(widget.initials))
: new CircleAvatar(backgroundImage: widget.myImage);
}
}
Enter 'fun' as a message, then 'bad' as the second -
image
The idea is that depending on what you enter, different images might load (or not). In the 'failed to load' case, the initials should remain.
You can achieve this functionality by adding a listener to ImageStream that you can obtain from ImageConfiguration,
Here, I am feeding the same data to my ListView you can of course customize this yourself by adding a List of images and initials as a field in any class and use ListView.builder instead to be able to loop on them by index.
class CustomCircleAvatar extends StatefulWidget {
NetworkImage myImage;
String initials;
CustomCircleAvatar({this.myImage, this.initials});
#override
_CustomCircleAvatarState createState() => new _CustomCircleAvatarState();
}
class _CustomCircleAvatarState extends State<CustomCircleAvatar>{
bool _checkLoading = true;
#override
void initState() {
widget.myImage.resolve(new ImageConfiguration()).addListener((_, __) {
if (mounted) {
setState(() {
_checkLoading = false;
});
}
});
}
#override
Widget build(BuildContext context) {
return _checkLoading == true ? new CircleAvatar(
child: new Text(widget.initials)) : new CircleAvatar(
backgroundImage: widget.myImage,);
}
}
Now you can use it like this:
void main() {
runApp(new MaterialApp (home: new MyApp()));
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: new Text("Custom Circle Avatar"),),
body: new ListView(children: new List.generate(20, (int index) {
return new Container(
height: 100.0,
width: 100.0,
child: new CustomCircleAvatar(myImage: new NetworkImage(
"https://www.doginni.cz/front_path/images/dog_circle.png"),
initials: "Dog",
),
);
}),),
);
}
}
This works really well and easy. Use the CachetNetworkImage and build the appropriate CircleAvatar.
return CachedNetworkImage(
httpHeaders: headers,
imageUrl: general.HOST + 'api/media/v2/' + id,
imageBuilder: (context, imageProvider) => new CircleAvatar(
radius: radius,
backgroundImage: imageProvider,
backgroundColor: backgroundColor),
errorWidget: (context, url, error) => CircleAvatar(
backgroundColor: backgroundColor,
radius: radius,
child: new Text(initials, style: textStyle,)),
);
The answer from #aziza was really the only one I could find on the topic for a while and it took me a while to read it and understand. I tried implementing it and there were some issues though I did get it to work eventually. I think I have a more readable (for me at least!)/up to date answer that might help someone stumbling upon this question:
class FallBackAvatar extends StatefulWidget {
final AssetImage image;
final String initials;
final TextStyle textStyle;
final Color circleBackground;
FallBackAvatar({#required this.image, #required this.initials, #required this.circleBackground, #required this.textStyle});
#override
_FallBackAvatarState createState() => _FallBackAvatarState();
}
class _FallBackAvatarState extends State<FallBackAvatar> {
bool _checkLoading = true;
#override
initState() {
super.initState();
// Add listeners to this class
ImageStreamListener listener = ImageStreamListener(_setImage, onError: _setError);
widget.image.resolve(ImageConfiguration()).addListener(listener);
}
void _setImage(ImageInfo image, bool sync) {
setState(() => _checkLoading = false);
//DO NOT DISPOSE IF IT WILL REBUILD (e.g. Sliver/Builder ListView)
dispose();
}
void _setError(dynamic dyn, StackTrace st) {
setState(() => _checkLoading = true);
dispose();
}
#override
Widget build(BuildContext context) {
return _checkLoading == true ? new CircleAvatar(
backgroundColor: widget.circleBackground,
child: new Text(widget.initials, style: widget.textStyle)) : new CircleAvatar(
backgroundImage: widget.image,
backgroundColor: widget.circleBackground,);
}
}
A couple of points, I'm manually disposing because I know after this there should be no more rebuilds (did you get the image? good! no more rebuilds unless you are part of a sliver or something OR did the image fail to load? well that's it then - no more rebuilds). This also handles the error case where the AssetImage (in my case, its Asset image but you could use any kind of image provider) is not there for whatever reason.
Second edit, because I have personal problems best left out of this answer. So I noticed that there was a slight delay in loading the profile images (like a second). But then the images came flooding in. Didn't like that transition so here is one with an AnimatedSwitcher:
class FallBackAvatar extends StatefulWidget {
final AssetImage image;
final String initials;
final TextStyle textStyle;
final Color circleBackground;
final double radius;
final int msAnimationDuration;
FallBackAvatar({#required this.image, #required this.initials, #required this.circleBackground, #required this.textStyle, #required this.radius, this.msAnimationDuration});
#override
_FallBackAvatarState createState() => _FallBackAvatarState();
}
class _FallBackAvatarState extends State<FallBackAvatar> {
bool _imgSuccess = false;
#override
initState() {
super.initState();
// Add listeners to this class
ImageStreamListener listener = ImageStreamListener(_setImage, onError: _setError);
widget.image.resolve(ImageConfiguration()).addListener(listener);
}
void _setImage(ImageInfo image, bool sync) {
setState(() => _imgSuccess = true);
}
void _setError(dynamic dyn, StackTrace st) {
setState(() => _imgSuccess = false);
dispose();
}
Widget _fallBackAvatar() {
return Container(
height: widget.radius*2,
width: widget.radius*2,
decoration: BoxDecoration(
color: widget.circleBackground,
borderRadius: BorderRadius.all(Radius.circular(widget.radius))
),
child: Center(child: Text(widget.initials, style: widget.textStyle))
);
}
Widget _avatarImage() {
return CircleAvatar(
backgroundImage: widget.image,
backgroundColor: widget.circleBackground
);
}
#override
Widget build(BuildContext context) {
return AnimatedSwitcher(
duration: Duration(milliseconds: widget.msAnimationDuration ?? 500),
child: _imgSuccess ? _avatarImage() : _fallBackAvatar(),
);
}
}
Actually the code can be even simpler:
if you want to put a text when the image is unavailable you should simply use foregroundImage instead of backgroundImage.
The text will displayed by default, when the image is loaded it will cover the text without having to deal with image loading status etc.
If you need to know if the image had an error you can intercept it with onForegroundImageError.
Example function:
Widget CircleAvatarTest(
{String? imageUrl,
String? text,
double radius = 35,
Color? backgroundColor}) {
return CircleAvatar(
radius: radius,
child: (text != null)
? Center(
child: Text(text,
style: TextStyle(
color: Colors.white,
fontSize: radius * 2 / text.length - 10,
)),
)
: null,
foregroundImage: imageUrl == null ? null : NetworkImage(imageUrl),
backgroundColor: backgroundColor,
//onForegroundImageError: (e,trace){/*....*/},
);
}
Here is the sample with stacked architecture where fallback is person icon.
ViewBuilder and ViewModel are just extended widgets from stacked architecture alternatives. #swidget is functional widget. You can achieve the same functionality via StatefulWidget.
#swidget
Widget avatarView({String userId, double radius = 24}) =>
ViewBuilder<AvatarViewModel>(
viewModelBuilder: () => AvatarViewModel(),
builder: (model) => CircleAvatar(
radius: radius,
backgroundColor: CColors.blackThird,
backgroundImage: NetworkImage(
Config.photoUrl + userId ?? userService.id,
),
child: model.isFailed ? Icon(EvaIcons.person, size: radius) : null,
onBackgroundImageError: (e, _) => model.isFailed = e != null,
),
);
class AvatarViewModel extends ViewModel {
bool _isFailed = false;
bool get isFailed => _isFailed;
set isFailed(bool isFailed) {
_isFailed = isFailed;
notifyListeners();
}
}

Resources