I have wrapped the body of the code below using GestureDetector, thus enabling me to use onVerticalDragEnd method available in the widget. When the app detects a Vertical Drag the _onRefreshing function is called, where it updates the Text widget after a delay of 2 seconds.
I want to include a Loading indicator while the _onRefreshing function is running.
How do I implement this task in Flutter?
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
dynamic balanceAvailable = 0;
void _onRefreshing(DragEndDetails details) async {
await Future.delayed(const Duration(seconds: 2));
if (details.velocity.pixelsPerSecond.dy > 0) {
setState(() {
balanceAvailable = 1000;
});
print('newbalance : $balanceAvailable');
print(details.velocity.pixelsPerSecond.dy);
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: GestureDetector(
onVerticalDragEnd: _onRefreshing,
child: Container(
width: double.infinity,
color: Colors.lightBlue,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
RaisedButton(
onPressed: () {},
child: Text("Button 1"),
),
SizedBox(height: 100.0),
Text('$balanceAvailable'),
],
),
),
),
),
);
}
}
You can return a CircularProgressIndicator inside a showdialog in your _onRefreshing method.
After the 2 seconds delay, you can remove it with Navigator.pop();
Maybe like this:
void _onRefreshing(DragEndDetails details) async {
showDialog(
context: context,
builder: (BuildContext context) {
return Center(
child: SizedBox(
height: MediaQuery.of(context).size.height/4,
width: MediaQuery.of(context).size.width/2,
child: CircularProgressIndicator(
valueColor: new AlwaysStoppedAnimation<Color>(Colors.red),
),
),
);
});
await Future.delayed(const Duration(seconds: 2));
if (details.velocity.pixelsPerSecond.dy > 0) {
setState(() {
balanceAvailable = 1000;
});
print('newbalance : $balanceAvailable');
print(details.velocity.pixelsPerSecond.dy);
}
Navigator.pop(context);
}
Related
I have an increment button with a callback function:
class IncrementButton extends StatelessWidget {
final VoidCallback callback;
const IncrementButton({
Key? key,
required this.callback,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
callback();
},
onLongPressStart: ((details) {
callback();
}),
......
);
}
}
And using it as:
IncrementButton(
callback: _increment,
),
The _increment function is as:
_increment() {
value += 1;
}
What I want is when the user taps once, the _increment function will be called. But when the user keeps a long press on the button, the same increment function should be called. But that doesn't happen. The _increment method is getting called only once for long Press
I guess you want the number to be incremented continually while long pressing the button/container. You need a timer which starts when onLongPress gets called. The timer stops when ´onLongPressEnd` gets called.
Try this:
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatefulWidget {
const MyApp({super.key});
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String action = "START";
Timer? timer;
int number = 0;
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Material App',
home: Scaffold(
appBar: AppBar(
title: const Text('Material App Bar'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(action),
SizedBox(height: 50),
Material(
borderRadius: BorderRadius.circular(20),
elevation: 20,
child: GestureDetector(
onTap: () => setState(() {
action = "Tap";
number++;
}),
onLongPress: () => setState(() {
timer = Timer.periodic(Duration(milliseconds: 50), (timer) {
setState(() {
action = "Longpress started";
number++;
});
});
}),
onLongPressEnd: (_) => setState(() {
action = "Longpress stopped";
timer?.cancel();
}),
child: Container(
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.red[50],
borderRadius: BorderRadius.circular(20)
),
width: 100,
height: 100,
child: Text('Tap')
),
),
),
SizedBox(height: 50,),
Text(number.toString())
],
),
),
),
);
}
#override
void dispose() {
timer?.cancel();
super.dispose();
}
}
Proof:
Change your onlongpressStart.
do{
callback();
} while(btnPressed);
And on onlongpressEnd
setState(() => btnPressed = false);
If nothing is done on the screen, I want to print something on the screen some time after the last action. How can I do that? How can I check the screen click status?
You can wrap Scaffold with GestureDetector and use onPanDown to capture the screen event, onTap doesn't win on hit test if there are inner clickable buttons. Also use behavior: HitTestBehavior.translucent,
Another notable thing is here, it is needed to be check on every second, because the checkup unit is on second. You can create a wrapper widget from it.
class ScreenT extends StatefulWidget {
const ScreenT({Key? key}) : super(key: key);
#override
State<ScreenT> createState() => _ScreenTState();
}
class _ScreenTState extends State<ScreenT> {
#override
void dispose() {
timer?.cancel();
super.dispose();
}
Timer? timer;
int maxDelaySec = 10;
int idleScreenCounter = 0;
#override
void initState() {
super.initState();
initTimer();
}
initTimer() {
timer = Timer.periodic(Duration(seconds: 1), (timer) {
idleScreenCounter++;
setState(() {}); //
});
}
onScreenTap() {
print("tapped on Screen");
idleScreenCounter = 0;
setState(() {});
}
#override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.translucent,
onPanDown: (_) => onScreenTap(),
child: Scaffold(
body: LayoutBuilder(
builder: (context, constraints) => SizedBox(
width: constraints.maxWidth,
height: constraints.maxHeight,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (maxDelaySec - idleScreenCounter > 0)
SizedBox(
height: 200,
child: Text(
" Tap the screen within ${maxDelaySec - idleScreenCounter}"),
),
if (maxDelaySec - idleScreenCounter < 0)
Container(
height: 100,
width: 100,
color: Colors.cyanAccent,
child: Text("Tap on screen"),
),
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
print("An action");
},
child: Text("A Button"),
),
ElevatedButton(
onPressed: () {
print("act");
},
child: Text("Elev"),
)
],
),
),
),
),
),
);
}
}
A naive approach could involve a Timer with dart:async.
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: _SomeWidget(),
);
}
}
class _SomeWidget extends StatefulWidget {
const _SomeWidget();
#override
State<_SomeWidget> createState() => _SomeWidgetState();
}
class _SomeWidgetState extends State<_SomeWidget> {
late Timer _timer;
#override
void initState() {
super.initState();
// It's up to you if you want the timer to start immediately with some effects or not.
_timer = Timer(const Duration(seconds: 1), () {});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
onTap: () {
// i.e. from the first interaction and so on
_timer.cancel();
_timer = Timer(const Duration(seconds: 1), () {
if (mounted) {
// !
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Some message')),
);
}
});
},
child: const Center(child: Text('My screen contents')),
),
);
}
}
The mounted check is very important, as Timer introduces an async gap, which may be dangerous when using context.
You can add a Gesture detector at the top level and start a timer on tap and on completion you can fire an event like the following
GestureDetector(
onTap: (){
startTimer();
}
child: Column(
children:[
//all other widgets
]
)
),
Then to define the timer
late Timer _timer;
void startTimer()
{
if(_timer != null && _timer.isActive) _timer.cancel();
_timer = Timer(
const Duration(seconds: 30),
() {
print("inactive for 30 seconds");
},
);
}
here in this case each time the user taps on the screen the timer is restarted and on 30th second the print is fired.
i have a book for flutter beginner programmer. At the first section it show me how to create splash screen, but the problem is that splash screen is doesn't show, just a blank black screen and after that the apps is showing.
This is my splash_screen.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:wisata_yogya/main.dart';
void main() =>runApp(SplashScreen());
// ignore: use_key_in_widget_constructors
class SplashScreen extends StatelessWidget{
#override
Widget build(BuildContext context){
return MaterialApp(
home: _SplashScreenBody(),
);
}
}
class _SplashScreenBody extends StatefulWidget{
#override
State<StatefulWidget> createState(){
return _SplashScreenBodyState();
}
}
class _SplashScreenBodyState extends State<_SplashScreenBody>{
#override
Widget build(BuildContext context){
Future.delayed(const Duration(seconds: 3), (){
Navigator.pushAndRemoveUntil(context,
// ignore: prefer_const_constructors
MaterialPageRoute(builder: (context) => MyApp()), (Route route) => false);
});
return const Scaffold(
body: Center(
child: Image(
image: AssetImage("graphics/logo.png"),
height: 75,
width: 75,
)
)
);
}
}
And there's no error in the code.
Method 1
You can use animated_splash_screen its easy to use,
home: AnimatedSplashScreen(
splash: Scaffold(
body: Center(
child: Image.asset(
'assets/logo.png',
width: double.infinity,
height: double.infinity,
),
),
),
duration: 100,
nextScreen: Login(),
splashTransition: SplashTransition.fadeTransition,
backgroundColor: Colors.white,
Method 2
Or Use Timer for delay some seconds in the initState()
Timer(
Duration(seconds: 2),
() => Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) => HomePage())));
class _SplashState extends State<Splash> {
#override
void initState() {
super.initState();
Timer(Duration(seconds: 3),
()=>Navigator.pushReplacement(context,
MaterialPageRoute(builder:
(context) =>First()
)
)
);
}
return Scaffold(
body: SizedBox.expand(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
child: Image.asset('Assets/Group.png'),
padding: const EdgeInsets.all(8.0),
height: 200,
width: 200,
),
CircularProgressIndicator(color: HexColor('#22178F'),),
],
),
),
);
Try to add timer in initState instead of what you doing something like this
void initState() {
super.initState();
Timer(Duration(seconds: 5),
()=>Navigator.pushReplacement(context,
MaterialPageRoute(builder:
(context) => HomeScreen()
)
)
);
}
I am making a login app where by i have created an OTP validation page. In this page i want to make a resend option which is clickable only after 30 seconds of page loading and once clicked becomes unclickable for ever.
I am new to flutter so I am sorry if this seems trivial.
You can follow this code.
class TestButton extends StatefulWidget {
#override
_TestButtonState createState() => _TestButtonState();
}
class _TestButtonState extends State<TestButton> {
bool firstStateEnabled = false;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
Timer(Duration(milliseconds: 30000), () {
setState(() {
firstStateEnabled = true;
});
});
return Scaffold(
body: Container(
child: firstStateEnabled
? Center(
child: Container(
width: 200,
height: 55,
child: RaisedButton(
onPressed: () {},
child: Text("Resend OTP"),
),
),
)
: Center(child: Container()),
),
);
}
}
Or if you need only one time the button than you can follow below codes.
Install timer_count_down.
Then, below code.
class TestButton extends StatefulWidget {
#override
_TestButtonState createState() => _TestButtonState();
}
class _TestButtonState extends State<TestButton> {
bool firstStateEnabled = false;
final CountdownController controller = CountdownController();
final int seconds = 30;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Container(
child: firstStateEnabled
? Center(
child: Container(
width: (200),
height: 55,
child: RaisedButton(
onPressed: () {
setState(() {
firstStateEnabled = false;
});
},
child: Text("Resend OTP"),
),
),
)
: Center(child: Container()),
),
Countdown(
controller: controller,
seconds: seconds,
build: (context, double time) {
return Container();
},
interval: Duration(milliseconds: 100),
onFinished: () {
setState(() {
firstStateEnabled = true;
;
});
},
)
],
),
);
}
}
Steps to reproduce:
Copy paste the below code in DartPad.dev/flutter
Hit run
Click the Do Api Call button
you should see two popups, one below and one above
After 5 seconds, the one below is desired to close not the one above, instead, the one above closes
How to close the one below and leave the one above open ?
import 'package:flutter/material.dart';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: CloseSpecificDialog(),
),
),
);
}
}
class CloseSpecificDialog extends StatefulWidget {
#override
_CloseSpecificDialogState createState() => _CloseSpecificDialogState();
}
class _CloseSpecificDialogState extends State<CloseSpecificDialog> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
child: Text('Do API call'),
onPressed: () async {
showDialogBelow();
showDialogAbove();
await Future.delayed(Duration(seconds: 5));
closeDialogBelowNotAbove();
},
)),
);
}
void showDialogBelow() {
showDialog(
context: context,
builder: (BuildContext contextPopup) {
return AlertDialog(
content: Container(
width: 350.0,
height: 150.0,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
CircularProgressIndicator(),
Text('I am below (you should not see this after 5 sec)'),
],
),
),
),
);
});
}
void showDialogAbove() {
showDialog(
context: context,
builder: (BuildContext contextPopup) {
return AlertDialog(
content: Container(
height: 100.0,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
CircularProgressIndicator(),
Text('I am above (this should not close)'),
],
),
),
),
);
});
}
/// This should close the dialog below not the one above
void closeDialogBelowNotAbove() {
Navigator.of(context).pop();
}
}
I had a similar requirement for my applications and had to spend quite some time to figure out the approach.
First I will tell you what advice I've got/read online which did not work for me:
Store BuildContext of each dialog from builder function when calling showDialog
Using Navigator.pop(context, rootNavigator: true)
removeRoute method on Navigator
None of these worked. #1 and #2 are a no-go because pop method can only remove the latest route/dialog on the navigation stack, so you can't really remove dialog that is placed below other dialog.
#3 was something I was hoping would work but ultimately it did not work for me. I tried creating enclosing Navigator for specific widget where I'm displaying the dialogs but pushing dialog as new route caused dialog being treated as page.
Solution: using Overlay widget
This is not a perfect solution but Overlay widget is actually used internally by other Flutter widgets, including Navigator. It allows you to control what gets placed in which order so it also means you can decide which element on overlay to remove!
My approach was to create a StatefulWidget which would contain a Stack. This stack would render whatever else passed to it and also Overlay widget. This widget would also hold references to OverlayEntry which are basically identifiers for dialogs themselves.
I'd use GlobalKey to reference the Overlay's state and then insert and remove dialogs (OverlayEntry) as I wished.
There is a disadvantage to this though:
No back button support on Android, so pressing back won't close the dialog.¹
Dialog positioning - you have to manage centering of your dialog yourself, as well as setting up the backdrop.²
Animations - you will have to implement these yourself as well. (You might want to fade in/ fade out backdrop, change position of dialog when opening and closing).
You can find interactive example on this dartpad or you can see the code here:
import 'dart:async';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#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> {
final GlobalKey<OverlayState> _overlay = GlobalKey<OverlayState>();
OverlayEntry? _dialog1;
OverlayEntry? _dialog2;
#override
void initState() {
super.initState();
Timer(const Duration(seconds: 3), () {
_openDialog1();
debugPrint('Opened dialog 1. Dialog should read: "Dialog 1"');
Timer(const Duration(seconds: 2), () {
_openDialog2();
debugPrint('Opened dialog 2. Dialog should read: "Dialog 2"');
Timer(const Duration(seconds: 3), () {
_closeDialog1();
debugPrint('Closed dialog 1. Dialog should read: "Dialog 2"');
Timer(const Duration(seconds: 5), () {
_closeDialog2();
debugPrint('Closed dialog 2. You should not see any dialog at all.');
});
});
});
});
}
#override
void dispose() {
_closeDialog1();
_closeDialog2();
super.dispose();
}
Future<void> _openDialog1() async {
_dialog1 = OverlayEntry(
opaque: false,
builder: (dialogContext) => CustomDialog(
title: 'Dialog 1', timeout: false, onClose: _closeDialog1));
setState(() {
_overlay.currentState?.insert(_dialog1!);
});
}
Future<void> _openDialog2() async {
_dialog2 = OverlayEntry(
opaque: false,
builder: (dialogContext) => CustomDialog(
title: 'Dialog 2', timeout: false, onClose: _closeDialog2));
setState(() {
_overlay.currentState?.insert(_dialog2!);
});
}
Future<void> _closeDialog1() async {
setState(() {
_dialog1?.remove();
_dialog1 = null;
});
}
Future<void> _closeDialog2() async {
setState(() {
_dialog2?.remove();
_dialog2 = null;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Stack(
children: <Widget>[
Align(
child:
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
TextButton(onPressed: _openDialog1, child: const Text('Open 1')),
TextButton(onPressed: _openDialog2, child: const Text('Open 2')),
])),
Align(
alignment: Alignment.bottomCenter,
child: Text(
'Opened 1? ${_dialog1 != null}\nOpened 2? ${_dialog2 != null}'),
),
Overlay(key: _overlay),
],
),
);
}
}
class CustomDialog extends StatefulWidget {
const CustomDialog({
Key? key,
required this.timeout,
required this.title,
required this.onClose,
}) : super(key: key);
final String id;
final bool timeout;
final String title;
final void Function() onClose;
#override
createState() => _CustomDialogState();
}
class _CustomDialogState extends State<CustomDialog>
with SingleTickerProviderStateMixin {
late final Ticker _ticker;
Duration? _elapsed;
final Duration _closeIn = const Duration(seconds: 5);
late final Timer? _timer;
#override
void initState() {
super.initState();
_timer = widget.timeout ? Timer(_closeIn, widget.onClose) : null;
_ticker = createTicker((elapsed) {
setState(() {
_elapsed = elapsed;
});
});
_ticker.start();
}
#override
void dispose() {
_ticker.dispose();
_timer?.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Positioned(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Stack(children: [
GestureDetector(
onTap: widget.onClose,
child: Container(
color: Colors.transparent,
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height)),
BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
child: AlertDialog(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
title: Text(widget.title),
content: SizedBox(
height: MediaQuery.of(context).size.height / 3,
child: Center(
child: Text([
'${_elapsed?.inMilliseconds ?? 0.0}',
if (widget.timeout) ' / ${_closeIn.inMilliseconds}',
].join('')))),
actions: [
TextButton(
onPressed: widget.onClose, child: const Text('Close'))
],
)),
]));
}
}
In my example you can see that when the app runs, it will start up Timer which will fire other timers. This only demonstrates that you are able to close/open specific dialogs programatically. Feel free to comment out initState method if you don't want this.
1: Since this solution does not use Navigator at all, you can't use WillPopScope to detect back button press. It's a shame, it'd be great if Flutter had a way to attach listener to back button press.
2: showDialog method does lot for you and you basically have to re-implement what it does within your own code.
Popping will remove route which is added the latest, and showDialog just pushes a new route with dialogue you can directly use the Dialog widgets in a Stack and manage the state using a boolean variable To Achieve same the effect,
class _MyHomePageState extends State<MyHomePage> {
bool showBelow = true;
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
await Future.delayed(Duration(seconds: 5));
setState(() {
showBelow = false;
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
if(showBelow) AlertDialog(
title: Text('Below..'),
content: Text('Beyond'),
),
AlertDialog(
title: Text('Above..'),
),
],
),
);
}
}
Remove
await Future.delayed(Duration(seconds: 5));
closeDialogBelowNotAbove();
Add Future.delayed
void showDialogAbove() {
showDialog(
context: context,
builder: (BuildContext contextPopup) {
Future.delayed(Duration(seconds: 5), () {
closeDialogBelowNotAbove();
});
return AlertDialog(
content: Container(
height: 100.0,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
CircularProgressIndicator(),
Text('I am above (this should not close)'),
],
),
),
),
);
});
}
Note: Navigator.pop() method always pop above alert/widget available on the screen, as it works with BuildContext which widget currently has.