Flutter video_player restart video from start on tap - ios

I am using the flutter video_player package to play a short video file using in my application. I inspired from the flutter cookbook: Play and pause a video.
I would like to allow the user to tap on the video to restart it from beginning. So I wrapped the VideoPlayer with a GestureDetector.
I currently have the following code:
class MyVideoPlayer extends StatefulWidget {
final File videoFile;
MyVideoPlayer({Key key, this.videoFile}) : super(key: key);
#override
_MyVideoPlayerState createState() => _MyVideoPlayerState();
}
class _MyVideoPlayerState extends State<MyVideoPlayer> {
VideoPlayerController _controller;
Future<void> _initializeVideoPlayerFuture;
#override
void initState() {
_controller = VideoPlayerController.file(widget.videoFile);
_initializeVideoPlayerFuture = _controller.initialize();
super.initState();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _initializeVideoPlayerFuture,
builder: (context, snapshot) {
print(snapshot.connectionState);
if (snapshot.connectionState == ConnectionState.done) {
// Play video once it's loaded
_controller.play();
return AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: GestureDetector(
onTap: () async {
await _controller.seekTo(Duration.zero);
_controller.play();
},
child: VideoPlayer(_controller),
),
);
} else {
return CircularProgressIndicator();
}
},
);
}
}
The video plays well once the video file is loaded (once the connection state passed to done), however, when I try to tap on the video to replay it a second time, it doesn't replay the video from start. The audio starts playing again, but video doesn't restart playing. Any idea?
EDIT 1
Following #marcosboaventura suggestion, I tried to wrap the calls in a setState to trigger the build method again:
return AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: GestureDetector(
onTap: () async {
await _controller.seekTo(Duration.zero);
setState(() {
_controller.play();
});
},
child: VideoPlayer(_controller),
),
);
But still the video doesn't replay, only the sound. Any other idea?

I finally found a solution to my issue by calling initialize() again on the controller on the tap event if the video is no longer playing (i.e. the video finished already).
return AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: GestureDetector(
onTap: () {
if (!_controller.value.isPlaying) {
setState(() {});
_controller.initialize();
}
},
child: VideoPlayer(_controller),
),
);

You need to rebuild the VideoPlayer if you changes anything in video playback. The most simple solution to your case is just fire build method again with a setState call.
/// ... after some code
child: GestureDetector(
onTap: () async {
await _controller.seekTo(Duration.zero);
setState( () {
_controller.play();
} );
},
child: VideoPlayer(_controller),
),

I solve this problem in this way.
GestureDetector(
onTap() {
if(_controller.value.position==_controller.value.duration){
_controller.initialize();
}
}
)
_controller.value.duration store the video duration,
_controller.value.position store the actual position of the video and if the video reaches to the end the _controller.value.position will be equal by _controller.value.duration.

you can do something like this onTap
/// get the duration of the video
final duration = await _controller.position;
/// check if video has ended
if (duration.inSeconds ==_controller.value.duration.inSeconds) {
/// restart the video by setting current position to 0
_controller.seekTo(Duration.zero);
} else {
_controller.value.isPlaying
? _controller.pause()
: _controller.play();
}

Related

Flutter video_player replaying video loaded from assets

I am trying using the video_player package to try and load, play, and then replay a video from assets. The video loads correctly and plays fine on the first run through. However when I try to replay the video using _controller.seekTo(Duration.zero) and _controller.play() it remains frozen on the last frame of the video and won't replay back.
This occurs only in iOS but not in Android where it behaves as expected. And it only occurs for videos loaded from assets. If I load a video from a url using VideoPlayerController.network it also behaves as expected.
Below is the class I modified from the video_player example.
import 'package:video_player/video_player.dart';
import 'package:flutter/material.dart';
class VideoApp extends StatefulWidget {
#override
_VideoAppState createState() => _VideoAppState();
}
class _VideoAppState extends State<VideoApp> {
VideoPlayerController _controller;
#override
void initState() {
super.initState();
_controller = VideoPlayerController.asset('assets/video/MOV_GET_OUT_BED.mp4')
..initialize().then((_) {
// Ensure the first frame is shown after the video is initialized, even before the play button has been pressed.
setState(() {});
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Video Demo',
home: Scaffold(
body: Column(
children: <Widget>[
Center(
child: _controller.value.initialized
? AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: VideoPlayer(_controller),
)
: Container(),
),
GestureDetector(
onTap: () async {
await _controller.seekTo(Duration.zero);
setState( () {
_controller.play();
} );
},
child: Text("Restart video")
),
]
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
_controller.value.isPlaying
? _controller.pause()
: _controller.play();
});
},
child: Icon(
_controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
),
),
),
);
}
#override
void dispose() {
super.dispose();
_controller.dispose();
}
}
To auto replay, add this to your initState() method.
_controller.setLooping(true);

How to play HTTP live stream on IOS device with Flutter APP?

I'm looking for approaches to play video Live Stream in Flutter.
I tested on Chewie and Video_player plugins. It works well on Android but doesn`t on IOS devices. And unfortunately, debug console is empty...
Here is working .m3u8 file I tried to play.
Here is a simple reproducer:
import 'package:video_player/video_player.dart';
import 'package:flutter/material.dart';
void main() => runApp(VideoApp());
class VideoApp extends StatefulWidget {
#override
_VideoAppState createState() => _VideoAppState();
}
class _VideoAppState extends State<VideoApp> {
VideoPlayerController _controller;
#override
void initState() {
super.initState();
_controller = VideoPlayerController.network(
'https://streamvideo.luxnet.ua/news24/smil:news24.stream.smil/playlist.m3u8')
..addListener(() {
if (_controller.value.initialized) {
print(_controller.value.position);
}
})
..initialize().then((_) {
// Ensure the first frame is shown after the video is initialized, even before the play button has been pressed.
setState(() {});
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Video Demo',
home: Scaffold(
body: Center(
child: Stack(
children: <Widget>[
_controller.value.initialized
? AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: VideoPlayer(_controller),
)
: Container()
],
)
),
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.orange,
onPressed: () {
setState(() {
_controller.value.isPlaying
? _controller.pause()
: _controller.play();
});
},
child: Icon(
_controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
),
),
),
);
}
#override
void dispose() {
super.dispose();
_controller.dispose();
}
}
Maybe somebody has experience with playing Video Stream on IOS with Flutter? Thanks a lot! 🍺
The video_player plugin doesn't currently support the iOS simulators. Your code is likely working correctly on a physical device.
The problem is in:
if ([self duration] == 0) {
return;
}
It is in platform level FLTVideoPlayerPlugin.m . Line number 325. It seems like duration is zero for live videos. If you remove that part of code it should work.

Is there a way of getting feedback from a Future function in Flutter

I have this Future function that takes a lot of time to load, and it works basically like a video render (it executes some steps 100 times).
Is there a way I could display the progress in a widget?
I tried setting a global variable in MyApp() for example an int corresponding to the frame and via setState() I tried rebuilding the widget, but it didn't work, the application freezes and the widget doesn't get updated.
This is the function:
int _progress = 0;
Future _cycleGame() async {
await game.cycle((int value) {
print(value);
setState(() {
_progress = value;
});
}).whenComplete(() {
setState(() {});
});
}
The game.cycle() is a Future function that cyles 50 times.
game.cycle() function for reference:
Future cycle(Function cBack) async {
for (int i in Range(50)) {
if (!shouldFinish) {
cBack(i);
///Does things.
And as of now I'm using simply a text in the screen to the _progress value.
The vlaue gets printed right (one every tot time) but the text displaying the _progress only updates at the end of the function. What Am I doing wrong?
I tried googling for how to do it but I found nothing. Is this possible?
Edit 1.
Using the answer provided by Rémi Rousselet, I still couldn't get the code to work. The widget gets updated only when the stream ends.
this is the code.
import 'dart:io';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Foo(),
);
}
}
class Foo extends StatefulWidget {
#override
_FooState createState() => _FooState();
}
class _FooState extends State<Foo> {
Stream<int> _cycleStream;
#override
void initState() {
super.initState();
_cycleStream = cycle();
}
Stream<int> cycle() async* {
for (int i = 0; i <= 50; i++) {
sleep(Duration(milliseconds: 100));
yield i;
this.setState(() {
});
// does things
}
}
#override
Widget build(BuildContext context) {
return StreamBuilder<int>(
stream: _cycleStream,
initialData: 0,
builder: (context, snapshot) {
return Center(
child: Text(snapshot.data.toString())
);
},
);
}
}
Flutter Doctor
[√] Flutter (Channel beta, v1.5.4-hotfix.2, on Microsoft Windows [Versione 10.0.17134.706], locale it-IT)
• Flutter version 1.5.4-hotfix.2 at
• Framework revision 7a4c33425d (8 days ago), 2019-04-29 11:05:24 -0700
• Engine revision 52c7a1e849
• Dart version 2.3.0 (build 2.3.0-dev.0.5 a1668566e5)
Instead of a Future, you likely want a Stream.
Future only emits a value when they are completely finished. As such we can't use them to represent a value that changes over time (here a progress indicator).
A Stream on the other hand can emit an unlimited number of times.
Here's an implementation of your cycle function that emits the current progress every time it updates:
Stream<double> cycle() async* {
for (int i in Range(50)) {
yield i / 50;
// TODO: does things
}
}
You can then send that double into a CircularProgressIndicator using StreamBuilder.
Here's a full example:
class Foo extends StatefulWidget {
#override
_FooState createState() => _FooState();
}
class _FooState extends State<Foo> {
Stream<double> _cycleStream;
#override
void initState() {
super.initState();
_cycleStream = cycle();
}
Stream<double> cycle() async* {
for (int i = 0; i <= 50; i++) {
yield i / 50;
// does things
}
}
#override
Widget build(BuildContext context) {
return StreamBuilder<double>(
stream: _cycleStream,
initialData: 0,
builder: (context, snapshot) {
return CircularProgressIndicator(
value: snapshot.data,
);
},
);
}
}
Have you tried the Future Builder ?
Future < Widget > buildAfter() async {
return Future.delayed(Duration(seconds: 3), () => Text('Hello World!'));
}
return Scaffold(
body: FutureBuilder(
future: buildAfter(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: 10,
itemBuilder: (context, index) => ListTile(
title: snapshot.data,
subtitle: Text('Counter: $index'),
),
);
}
return Center(
child: CircularProgressIndicator(),
);
},
));
In this code when you will run it will display the CircularProgressIndiacator for 3 seconds because we have told the compiler to hold for 3 seconds in the Future function.
And when the 3 seconds are over it returns the data, FutureBuilder get's a notification that it has the data and to rebuild the widget with the new data available.
Note: It's not necessary to that your Future function has to return necessary data, all it needs to return something other than error so that listening Builder function can understand when it's should to rebuild itself.
Hopefully you can also Future Builder can be used within any widget.
If you want to emit data multiple time you can use a FutureBuilder with a bool and check if it's loading then show this else show that. Instead of Stream/Streambuilder and making things less complicated.
bool isLoading = false;
Future < Widget > buildAfter() async {
return Future.delayed(Duration(seconds: 3), () => Text('Hello World!'));
}
return Scaffold(
appBar: AppBar(
elevation: 0.0,
backgroundColor: greenColor,
title: const Text('Inbox'),
actions: < Widget > [
new IconButton(
icon: new Icon(Icons.edit),
onPressed: () {
setState(() {
isLoading = !isLoading;
});
}),
],
),
body: isLoading ?
Center(
child: CircularProgressIndicator(
backgroundColor: Colors.redAccent,
),
) :
FutureBuilder(
future: buildAfter(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: 10,
itemBuilder: (context, index) => ListTile(
title: Text('Code'),
subtitle: Text('Counter: $name'),
),
);
}
return Center(
child: CircularProgressIndicator(),
);
},
));
What's happening in this code is it check the bool isLoading
if isLoading == true => Show Circular Progress Indicator
if isLoading == false => Show future builder
So when you have to call it multiple times you can setState of bool isLoading to true. Then after your calculation is over you can setState of bool isLoading to false.
But it depends on the use case Streams are always better when you have incoming events at anytime, but if you know what you're doing else Streams make themselves complicated to understand
according to you said you couldn't get the code to work.
I've already try Remo's code and it's working fine.
I think the issue is your cycle() need await for simulating some delay.
Stream<double> cycle() async* {
for (int i = 0; i <= 50; i++) {
yield i / 50;
// does things
await Future.delayed(Duration(seconds: 1));
}
}

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);
}
}

Circular progress button based on hold (flutter)

I'm trying to make a button where when user hold it, there will be a progress, but if the user unpress the button before it finish, the progress will decrease. somthing like the one on the picture
As said by Arnold Parge, you can use the GestureDetector and listen to onTapDown and onTapUp. To create your desired LoadingButton, you can use the following Widget Structure:
- GetureDetector
- Stack
- CircularProgressIndicator // background circle
- CircularProgressIndicator // foreground circle
- Icon
To create the animation, you can add an AnimationController and bind the value of the foreground CircularProgressIndicator to AnimationController.value. Now you'll just have to the add the two listeners to to the GestureDetector:
onTapDown: When tapping on the button, the animation should start. Therefore we call AnimationController.forward()
onTapUp: When releasing the press, we want to check if the animation is already finished. We can use AnimationController.status to check the status. If it is AnimationStatus.forward it is still running. Hence we want to reverse the animation with calling AnimationController.reverse().
Here is the resulting button:
And the complete source code:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(),
home: Scaffold(
body: Center(child: LoadingButton()),
),
);
}
}
class LoadingButton extends StatefulWidget {
#override
LoadingButtonState createState() => LoadingButtonState();
}
class LoadingButtonState extends State<LoadingButton>
with SingleTickerProviderStateMixin {
AnimationController controller;
#override
void initState() {
super.initState();
controller =
AnimationController(vsync: this, duration: Duration(seconds: 1));
controller.addListener(() {
setState(() {});
});
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTapDown: (_) => controller.forward(),
onTapUp: (_) {
if (controller.status == AnimationStatus.forward) {
controller.reverse();
}
},
child: Stack(
alignment: Alignment.center,
children: <Widget>[
CircularProgressIndicator(
value: 1.0,
valueColor: AlwaysStoppedAnimation<Color>(Colors.grey),
),
CircularProgressIndicator(
value: controller.value,
valueColor: AlwaysStoppedAnimation<Color>(Colors.blue),
),
Icon(Icons.add)
],
),
);
}
}
Use GestureDetector.
Start the progress in onTapDown and reverse the progress in onTapUp if the progress is not complete or whatever your conditions are.

Resources