I have a simple timer which works fine when the app is running in foreground. I can listen to the stream and update the UI. However when the app is in the background it will not continue counting. How can I continue counting when the app is running in the background?
This is my code for the timer:
class SetTimer {
int _seconds = 0;
final _streamController = StreamController<int>.broadcast();
Timer? _timer;
// Getters
Stream<int> get stream => _streamController.stream;
// Setters
void start() {
_timer = Timer.periodic(const Duration(seconds: 1), (_) {
_seconds++;
_updateSeconds();
});
}
void _updateSeconds() {
// stop counting after one hour
if (_seconds < 3600) {
_streamController.sink.add(_seconds);
}
}
}
Try the below code -
I tested it & found it will count the number in the background & there is no problem.
I added a screen record video here, it will help you to understand.
import 'dart:async';
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 const MaterialApp(
title: 'Flutter Demo',
home: MyTest(),
);
}
}
class MyTest extends StatefulWidget {
const MyTest({Key? key}) : super(key: key);
#override
State<MyTest> createState() => _MyTestState();
}
class _MyTestState extends State<MyTest> {
final SetTimer _setTimer = SetTimer();
#override
void initState() {
_setTimer.start();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder<int>(
stream: _setTimer.stream,
builder: (
BuildContext context,
AsyncSnapshot<int> snapshot,
) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
} else if (snapshot.connectionState == ConnectionState.active
|| snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
return const Text('Error');
} else if (snapshot.hasData) {
return Center(
child: Text(
snapshot.data.toString(),
style: const TextStyle(color: Colors.red, fontSize: 40)
),
);
} else {
return const Text('Empty data');
}
} else {
return Text('State: ${snapshot.connectionState}');
}
},
),
);
}
}
class SetTimer {
int _seconds = 0;
final _streamController = StreamController<int>.broadcast();
Timer? _timer;
// Getters
Stream<int> get stream => _streamController.stream;
// Setters
void start() {
_timer = Timer.periodic(const Duration(seconds: 1), (_) {
_seconds++;
_updateSeconds();
});
}
void _updateSeconds() {
// stop counting after one hour
if (_seconds < 3600) {
_streamController.sink.add(_seconds);
}
}
}
As far as I know, there is no way to run the app in the background, like you want to. I had a similar problem with an app, I developed and didn't found the perfect solution.
As a workaround, I'm using the wakelock package to prevent the mobile device / app from going into standby. Maybe this could also be a solution for you.
Related
i have two screens A and B.In screen A iam calling a function periodically(i.e every 5 seconds).At the time of navigating to screen B i need to stop the function calling and when its back to screen A, the function call should be resumed.
Is there any way to do it?
Navigator doesn't expose the current route.
What you can do instead is use Navigator.popUntil(callback) as popUtil pass to the callback the current Route, which includes it's name and stuff.
final newRouteName = "/NewRoute";
bool isNewRouteSameAsCurrent = false;
Navigator.popUntil(context, (route) {
if (route.settings.name == newRouteName) {
isNewRouteSameAsCurrent = true;
}
return true;
});
if (!isNewRouteSameAsCurrent) {
Navigator.pushNamed(context, newRouteName);
}
Use This bool to check current screen and toggle your function .
From what i see is you can use the Timer class in the widget and manupulate based on your needs, I have created a sample example for you.
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 const MaterialApp(
home: PageOne()
);
}
}
class PageOne extends StatefulWidget {
const PageOne({Key? key}) : super(key: key);
#override
_PageOneState createState() => _PageOneState();
}
class _PageOneState extends State<PageOne> {
Timer? timer;
#override
void initState() {
// TODO: implement initState
super.initState();
timer = Timer.periodic(const Duration(seconds: 2), (timer) {
printMethod("init");
});
}
printMethod(String type){
print("This is the $type print statement ");
}
#override
void dispose() {
super.dispose();
timer?.cancel();
print("First Page timer cancelled");
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: TextButton(onPressed: () async {
timer?.cancel();
var result = await Navigator.push(context, MaterialPageRoute(builder: (context)=> const PageTwo() ));
if(result)
{
timer = Timer.periodic(const Duration(seconds: 2), (timer) {
printMethod("init");
});
}
}, child: const Text("go to next page"),),
),
);
}
}
class PageTwo extends StatefulWidget {
const PageTwo({Key? key}) : super(key: key);
#override
_PageTwoState createState() => _PageTwoState();
}
class _PageTwoState extends State<PageTwo> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Page two"),),
body: Center(
child: TextButton(onPressed: () async {
Navigator.of(context).pop(true);
}, child: const Text("go to prev page"),),
),
);
}
}
Let me know if it works
You can simply use bool to handle this case as follows :
class _ScreenAState extends State<ScreenA> {
bool runPeriodicFun = true;
void periodicFun() {
if (runPeriodicFun) {
//write your periodic logic
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: InkWell(
onTap: () {
setState(() {
runPeriodicFun = false;
});
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) => ScreenB()))
.then((value) {
setState(() {
runPeriodicFun = true;
});
periodicFun();
});
},
child: Container()),
);
}
}
when you go to Screen B then runPeriodicFun set to false and coming back to Screen A runPeriodicFun set to true. So it will use periodicFun block only when runPeriodicFun is true.
You will receive callback in then block in the Navigator.push method.
what i did is that, in timer function i checked the current page is at top of stack by
ModalRoute.of(context)!.isCurrent
what it did is that it will check the page by context which we pass if the page is at top it will continue the timer, if it navigates to another page then the above code will return false.
In false, i just stop the timer.
if(ModalRoute.of(context)!.isCurrent){
//timer running
}else{
_timer?.cancel();
}
so if the page is same timer will perform otherwise it will stop the timer.
Now for resuming the timer, under the build in screen where you called i just called the function from where timer get activated.
Widget build(BuildContext context) {
//call timer function
return SafeArea()}
i think this might solve the problem. if anyone identify any problem please comment it.
I have web api that returns last image taken from database so I wish to make timer to show latest image every few seconds using Flutter and Dart. Ideally I would like to load image, shot it to the user and request new image from server infinitely. My issue is that before new image is displayed, black screen appears so it makes flickering effect. How can I solve it? Here is my code:
import 'dart:async';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class TestWidget extends StatefulWidget {
const TestWidget({Key? key}) : super(key: key);
#override
_CameraWidgetState createState() => _CameraWidgetState();
}
class _CameraWidgetState extends State<TestWidget> {
late int a = 1;
final scaffoldKey = GlobalKey<ScaffoldState>();
late bool loaded = false;
late Future futureRecords;
Future getImage() async {
final responses = await http.get(
Uri.parse("https://0465-95-178-160-106.ngrok.io/profile/download?picNum=" +
(a++).toString()),
headers: {
"Accept": "application/json",
"Access-Control_Allow_Origin": "*",
});
if (responses.statusCode == 200) {
return responses.bodyBytes;
} else {
throw Exception('Failed to load image');
}
}
#override
void initState() {
futureRecords = getImage();
Timer.periodic(Duration(seconds: 5), (timer) async {
try {
//await getImage();
setState(() {
futureRecords = getImage();
});
} catch (Ex) {}
});
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: futureRecords,
builder: (context, snapshot) {
if (!snapshot.hasData)
return Center(
child: CircularProgressIndicator(),
);
Uint8List bytes = snapshot.data as Uint8List;
return Image.memory(
bytes,
fit: BoxFit.fill,
);
});
}
}
Solution
You wrap your Testwidget with Material or Scaffold,Materialapp widget.You can AnimatedSwitcher widget to avoid that flickering effect or default color
Material(
child: AnimatedSwitcher(
duration: Duration(seconds: 1),
child: widget,
),
);
Before (Flickering)
class MYAppWithFlicker extends StatelessWidget {
const MYAppWithFlicker({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return TestWidget();
}
}
After (Without Flickering)
class MYAppWithoutFlicker extends StatelessWidget {
const MYAppWithoutFlicker({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Material(child: TestWidget());
}
}
Sample Code
import 'dart:async';
import 'dart:typed_data';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart';
void main() {
runApp(MYAppWithoutFlicker());
}
class MYAppWithoutFlicker extends StatelessWidget {
const MYAppWithoutFlicker({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Material(
color: Colors.lightGreen,
child: TestWidget(),
);
}
}
class MYAppWithFlicker extends StatelessWidget {
const MYAppWithFlicker({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return TestWidget();
}
}
class TestWidget extends StatefulWidget {
const TestWidget({Key? key}) : super(key: key);
#override
_CameraWidgetState createState() => _CameraWidgetState();
}
class _CameraWidgetState extends State<TestWidget> {
late int a = 1;
final scaffoldKey = GlobalKey<ScaffoldState>();
late bool loaded = false;
late Future futureRecords;
Future getImage() async {
var s = "https://0465-95-178-160-106.ngrok.io/profile/download?picNum=" +
(a++).toString();
final responses = await get(
Uri.parse(a % 2 == 0
? "https://images.indianexpress.com/2019/09/toys.jpg"
: "https://ecommerce.ccc2020.fr/wp-content/uploads/2020/10/electronic-gadgets.jpeg"),
headers: {
"Accept": "application/json",
"Access-Control_Allow_Origin": "*",
});
if (responses.statusCode == 200) {
print(responses.bodyBytes);
return responses.bodyBytes;
} else {
throw Exception('Failed to load image');
}
}
#override
void initState() {
futureRecords = getImage();
Timer.periodic(Duration(seconds: 5), (timer) async {
try {
//await getImage();
setState(() {
futureRecords = getImage();
});
} catch (Ex) {}
});
super.initState();
}
#override
Widget build(BuildContext context) {
var widget;
return FutureBuilder(
future: futureRecords,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting)
widget =
Center(child: CircularProgressIndicator());
else if (snapshot.connectionState == ConnectionState.done) {
Uint8List bytes = snapshot.data as Uint8List;
widget = Image.memory(
bytes,
fit: BoxFit.fill,
);
}
// return AnimatedSwitcher(
// duration: Duration(seconds: 1),
// child: widget,
// );
return Material(
child: AnimatedSwitcher(
duration: Duration(seconds: 1),
child: widget,
),
);
});
}
}
After few days of trying various methods, I found out about gaplessPlayback property in Image.memory and when I set it to true, all magically worked as expected.
I am working on a small project on Flutter and Dart. I have several .dart pages in my application, and I would like an autoplay music to play at startup, and that this music continue playing even after switching to another Dart page.
I am using the "audioplayers: 0.17.0" package, but I can't launch my mp3 in autoplay, and the music stops playing when I click on a link which leads to a new page.
Here is the part of the code that I was able to do about this problem:
Thanks for your help!
import 'package:audioplayers/audio_cache.dart';
import 'package:flutter/material.dart';
import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/widgets.dart';
import 'route.dart'as route ;
import 'page2.dart';
void main() {
runApp(TinyAppli());
}
class TinyAppli extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Tiny Appli',
debugShowCheckedModeBanner: false,
theme: ThemeData(scaffoldBackgroundColor: const Color(0x000000),
primarySwatch: Colors.amber,
),
onGenerateRoute: route.controller,
initialRoute: route.homePage,
home: TinyApplication(title: 'Welcome to The Tiny Application !'),
);
}
}
class TinyApplication extends StatefulWidget {
TinyApplication({Key? key, required this.title}) : super(key: key);
final String title;
#override
_TinyApplicationState createState() =>
_TinyApplicationState();
}
class _TinyApplicationState extends State<TinyApplication> {
AudioPlayer audioPlayer = AudioPlayer();
AudioPlayerState audioPlayerState = AudioPlayerState.PAUSED;
late AudioCache audioCache;
String path = 'music.mp3';
#override
void initState() {
super.initState();
audioCache = AudioCache(fixedPlayer: audioPlayer);
audioPlayer.onPlayerStateChanged.listen((AudioPlayerState s) {
setState(() {
audioPlayerState = s;
});
});
}
#override
void dispose() {
super.dispose();
audioPlayer.release();
audioPlayer.dispose();
audioCache.play('music.mp3');
}
playMusic() async{
await audioCache.play(path);
}
pauseMusic() async {
await audioPlayer.pause();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: BaseAppBar(
title: Text('title'),
appBar: AppBar(),
widgets: <Widget>[musicPlay(),],
),
body: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
IconButton(
icon: Image.asset('assets/logo_conceptorweb.jpg'),
iconSize: 200,
onPressed: () =>
Navigator.pushReplacement(
context,
PageRouteBuilder(
pageBuilder: (context, animation1, animation2) => StartAppli(title: 'Tiny Appli',),
transitionDuration: Duration.zero,
),
),
)
],
),
),
]
)
)
);
}
IconButton musicPlay() {
return IconButton(
onPressed: () {
audioPlayerState == AudioPlayerState.PLAYING
? pauseMusic()
: playMusic();
},
icon: Icon(audioPlayerState == AudioPlayerState.PLAYING
? Icons.pause_rounded
: Icons.play_arrow_rounded
),
);
}
}
Extend your class with WidgetsBindingObserver and then override the lifecycle functions.
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
switch (state) {
case AppLifecycleState.resumed:
print("app in resumed");
//Play the Music
break;
case AppLifecycleState.inactive:
print("app in inactive");
//Stop the music
break;
case AppLifecycleState.paused:
print("app in paused");
break;
case AppLifecycleState.detached:
print("app in detached");
//Stop the music
break;
}
}
I made a util class for playing the music and pausing it on demand. You can modify it as per your demand.
class AudioUtils {
static AudioCache? _bgAudioCache;
static AudioPlayer? _bgAudioPlayer;
static AudioCache? getBGAudioCache() {
if (_bgAudioCache == null)
return _bgAudioCache = AudioCache();
else
return _bgAudioCache;
}
static void playBgMusic() async {
if (_bgAudioPlayer == null){
_bgAudioPlayer =
await getBGAudioCache()?.loop("bg-music.mp3", volume: 0.1);
} else _resumeBgMusic();
}
static void pauseBgMusic() {
_bgAudioPlayer?.pause();
}
static void _resumeBgMusic() {
_bgAudioPlayer?.resume();
}
static disposeBGMusic() {
_bgAudioPlayer?.dispose();
}
static bool isBGMusicPlaying() {
return _bgAudioPlayer?.state == PlayerState.PLAYING;
}
}
Finally, I handled the music pause and play on application lifecycle by this approach inspired from Ayar's answer above -
class _MainAppState extends State<MainApp> with WidgetsBindingObserver {
#override
void initState() {
.
.
.
WidgetsBinding.instance?.addObserver(this);
super.initState();
}
#override
void dispose() {
.
.
.
WidgetsBinding.instance?.removeObserver(this);
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
switch (state) {
case AppLifecycleState.resumed:
print("app in resumed");
AudioUtils.playBgMusic();
break;
case AppLifecycleState.inactive:
print("app in inactive");
AudioUtils.pauseBgMusic();//If you want to pause music while inactive.
break;
case AppLifecycleState.paused:
print("app in paused");
AudioUtils.pauseBgMusic();
break;
case AppLifecycleState.detached:
print("app in detached");
AudioUtils.disposeBGMusic();
break;
}
}
}
Make sure that _MainAppState is your master/main widget's state.
Flutter
Dart
I am a beginner in flutter and i am trying to add controller to streamBuilderWidget so i can dispose it but i have no idea where should i put the controller.. i tried this
the stream below as a widget not function
StreamController<QuerySnapshot> controller;
void dispose() {
super.dispose();
controller.close();
}
void initState() {
super.initState();
controller = StreamController<QuerySnapshot>();
}
StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance.collection("users").doc(widget.documentUid).snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) {
return Center(
child: circulearProgress(),
);
}
in this code it never disposed or closed the stream:(
Anyone who edits my code in the right way will be very grateful to him , thanks friends
StreamController is like a pipeline. In your case, that pipeline went from water supplier to your house, there is no need to worried about what goes in there.
But if you want to set up a pipeline from your washing machine to the draining hole, that is where you need to use StreamController.
Example:
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final NumberController controller = NumberController();
#override
void dispose() {
controller.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: SingleChildScrollView(
child: Column(
children: [
InputWidget(controller: controller,),
OutputWidget(controller: controller,)
],
),
),
),
);
}
}
class NumberController {
//This is the pipeline of "number"
final StreamController<int> controller = StreamController<int>.broadcast();
//This is where your "number" go in
Sink<int> get inputNumber => controller.sink;
//This is where your "number" go out
Stream<int> get outputNumber => controller.stream;
//Dispose
void dispose() {
controller.close();
}
}
class InputWidget extends StatelessWidget {
final NumberController controller;
const InputWidget({Key key, this.controller}) : super(key: key);
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
controller.inputNumber.add(Random().nextInt(10));
},
child: Text(
'Random Number'
),);
}
}
class OutputWidget extends StatelessWidget {
final NumberController controller;
const OutputWidget({Key key, this.controller}) : super(key: key);
#override
Widget build(BuildContext context) {
return StreamBuilder<int>(
stream: controller.outputNumber,
builder: (context, snapshot) {
return Text(snapshot.hasData ? snapshot.data.toString() : 'No data');
},
);
}
}
You don't have to use StreamController. StreamBuilder you are using closes the stream internally.
From your comments, you seem to want to close the listener in the method below:
void handleDelete() {
FirebaseFirestore.instance.collection("handleCountM").doc(currentUser.uid + widget.documentUid).collection("handleCountM2").limit(1).snapshots()
.listen((value) {
value.docs.forEach((element) {
element.reference.delete();
});
});
}
You can do that by getting a reference to the stream subscription and calling .cancel on the subscription.
Calling .listen on a stream returns a stream subscription object like this:
StreamSubscription handleDeleteStreamSubscription = FirebaseFirestore.instance.collection("handleCountM").doc(currentUser.uid + widget.documentUid).collection("handleCountM2").limit(1).snapshots()
.listen((value) {
value.docs.forEach((element) {
element.reference.delete();
});
});
Cancelling the subscription is done like this:
handleDeleteStreamSubscription.cancel();
I have created following 2 streams.
Stream 1 - Periodic Stream which is calling an async function at a particular time.
Stream 2 - Function which listens on stream 1 and then create stream 2
I wanted to add logic such that if stream 1 length is more that 0 then only create stream 2. But I am getting length of stream 1 as always 0.
My Code is as follows. (It runs after hot reload)
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
int _counter = 0;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyPage(),
);
}
}
class MyPage extends StatefulWidget {
#override
_MyPageState createState() => _MyPageState();
}
class _MyPageState extends State<MyPage> {
StreamController<List<String>> _myStreamController =
StreamController<List<String>>();
int _counter = 0;
Stream<List<String>> _stream() {
Duration interval = Duration(milliseconds: 100);
Stream<List<String>> stream =
Stream<List<String>>.periodic(interval, temp2);
return stream;
}
Future temp() async {
List<String> a = [];
_counter++;
a.add(_counter.toString());
return a;
}
List<String> temp2(int value) {
List<String> _localMsg = List();
temp().then((a) {
print(a);
_localMsg = a;
});
return _localMsg;
}
#override
void initState() {
super.initState();
_stream().listen((ondata) {
print(ondata);
if (ondata.length > 0) _myStreamController.sink.add(ondata);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
child: StreamBuilder(
stream: _myStreamController.stream,
builder: (context, snapshot) {
if (snapshot.data != null) {
return Text(snapshot.data.toString());
} else {
return Text("waiting...");
}
},
),
),
),
);
}
}