Flutter Background Processes Using Android Alarm Manager and Isolates - flutter

I'm trying to get a timer (down to the hundredths of seconds) to work in Flutter even when the app is closed. I initially tried to use isolates as I thought they would work yet after testing with a Pixel 4 running Android 11 I found that it was still not firing correctly when the app was closed. After some googleing I came across Android Alarm Manager and I have everything set up again yet it doesn't appear that the periodic function is firing correctly.
Heres the BLoC map for triggering the counter:
Stream<TimerState> _mapTimerStartedToState(TimerStarted start) async* {
AndroidAlarmManager.initialize();
port.listen((_) async => await _incrementCounter());
startCounter();
print(_counter);
yield TimerRunInProgress(start.duration);
}
Here's the startCounter() function:
void startCounter() async {
prefs = await SharedPreferences.getInstance();
if (!prefs.containsKey(countKey)) {
await prefs.setInt(countKey, 0);
}
IsolateNameServer.registerPortWithName(
port.sendPort,
isolateName,
);
await AndroidAlarmManager.periodic(
Duration(milliseconds: 100),
// Ensure we have a unique alarm ID.
Random().nextInt(pow(2, 31)),
callback,
exact: true,
wakeup: true,
);
}
And then here's my callback:
static Future<void> callback() async {
print('Alarm fired!');
// Get the previous cached count and increment it.
final prefs = await
SharedPreferences.getInstance();
int currentCount = prefs.getInt(countKey);
await prefs.setInt(countKey, currentCount + 1);
// This will be null if we're running in the background.
print(currentCount);
uiSendPort ??= IsolateNameServer.lookupPortByName(isolateName);
uiSendPort?.send(null);
}
Am I on the right path here? Can AndroidAlarmManager do what I'm trying to do? I'm not exactly sure why the isolate approach didn't work on its own either, the only explanation I got was that I needed to use AndroidAlarmManager. Now, the events aren't firing at the 100 ms rate as I told them to and are instead firing 1 to several minutes apart.

Android restricts the frequencies for alarms. You cannot schedule alarms as frequently as 100 milliseconds with AlarmManager.
Please refer the note in red background on : https://developer.android.com/reference/android/app/AlarmManager
Note: Beginning with API 19 (Build.VERSION_CODES.KITKAT) alarm
delivery is inexact: the OS will shift alarms in order to minimize
wakeups and battery use. There are new APIs to support applications
which need strict delivery guarantees; see setWindow(int, long, long,
android.app.PendingIntent) and setExact(int, long,
android.app.PendingIntent). Applications whose targetSdkVersion is
earlier than API 19 will continue to see the previous behavior in
which all alarms are delivered exactly when requested.

Related

How to set a time-specific event in Flutter?

I am trying to get a switch widget to turn off at a specific time of the day.
I have read a lot of the documentations like these
https://stackoverflow.com/questions/15848214/does-dart-have-a-scheduler
https://pub.dev/packages/cron
https://pub.dev/packages/scheduled_timer
All of these which can only allow me to set duration instead of a specific time.
Thus, my only approach right now is by setting up the timer when the switch is turned on.
e.g. 8hrs then it turns off.
Problem: If the user turned on the switch late, the time that it turns off will also be delayed.
So is there an actual way to set an event at a specific time + works even after we onstop/terminate the application?
You can try to do something like this:
I'll simplify the specific time into :
...
var setTime = DateTime.utc(2022, 7, 11, 8, 48, 0).toLocal();
StreamSubscription? subscription;
...
Then you can assign a periodic stream listener:
...
// periodic to run every second (you can change to minutes/hours/others too)
var stream = Stream.periodic(const Duration(seconds: 1), (count) {
//return true if the time now is after set time
return DateTime.now().isAfter(setTime);
});
//stream subscription
subscription = stream.listen((result) {
// if true, insert function and cancel listen subscription
if(result){
print('turn off');
subscription!.cancel();
}
// else if not yet, run this function
else {
print(result);
}
});
...
However, running a Dart code in a background process is more difficult, here are some references you can try:
https://medium.com/flutter/executing-dart-in-the-background-with-flutter-plugins-and-geofencing-2b3e40a1a124
https://pub.dev/packages/flutter_background_service
I hope it helps, feel free to comment if it doesn't work, I'll try my best to help.
After some time I figured it out.
Format
cron.schedule(Schedule.parse('00 00 * * *'), () async {
print("This code runs at 12am everyday")
});
More Examples
cron.schedule(Schedule.parse('15 * * * *'), () async {
print("This code runs every 15 minutes")
});
To customize a scheduler for your project, read this

How do I get a dart Stream with uneven intervals?

I'm new to dart and Flutter and would love to get some advice on an algorithmic problem I'm facing.
I want to connect my Flutter app to a bluetooth device (that part is done, I am connected already) and send messages on uneven intervals. I have the messages in a list and for each of them I know at what time (milliseconds) I want to send the message.
So suppose the following messages are lined up:
start at 0ms
init_app at 100ms
user_pick_x at 500ms
user_start_x at 500ms (will be sent after user_pick_x, order should be guaranteed)
interrupt at 3500ms
I have found the documentation to create streams, but it always talks about a single interval value. https://dart.dev/articles/libraries/creating-streams.
Ideas:
Technically I can pass in a list of Duration objects and work with a custom generator async*, along with the message string.
Alternatively I can set the interval to the lowest time delta and check on each one whether a message / messages should be sent. In the case of the example that would be every 100ms.
It would be nice to be able to pause / cancel the stream as well. Which is something that streams can do natively.
I think the easiest is to just emit those messages at the specified intervals. Something like:
Future<void> _wait(int milliseconds) async =>
await Future<void>.delayed(Duration(milliseconds: milliseconds));
Stream<String> generateMessages() async* {
yield 'start';
await _wait(100);
yield 'init_all';
await _wait(400);
yield 'user_pick_x';
yield 'user_start_x';
await _wait(3000);
yield 'interrupt';
}
void main() {
generateMessages().listen((msg) {
print('${DateTime.now()}: $msg');
});
}
which will print:
2021-07-25 10:21:21.429: start
2021-07-25 10:21:21.531: init_all
2021-07-25 10:21:21.934: user_pick_x
2021-07-25 10:21:21.934: user_start_x
2021-07-25 10:21:24.938: interrupt
If you want to make sure that the listener of the stream receives events asynchronously - hence not interfering with the wait milliseconds, you can explicitly use the StreamController which by default calls the listeners asynchronously (make sure to import dart:async --- dart:io is only used in the example for the sleep to show that even on a blocking action it will run in parallel with the waiting):
import 'dart:async';
import 'dart:io';
Future<void> _wait(int milliseconds) async {
print('WAIT $milliseconds ms');
await Future<void>.delayed(Duration(milliseconds: milliseconds));
}
Stream<String> generateMessages() {
final controller = StreamController<String>(sync: false);
controller.onListen = () async {
controller.add('start');
await _wait(100);
controller.add('init_all');
await _wait(400);
controller.add('user_pick_x');
controller.add('user_start_x');
await _wait(3000);
controller.add('interrupt');
};
return controller.stream;
}
void main() {
generateMessages().listen((msg) {
sleep(const Duration(milliseconds: 120));
print('${DateTime.now()}: $msg');
});
}

Flutter- How to know AudioService is stopped?

I am working with audio_service flutter package. I want to pop a player page if Audio Service stops. How to get the Audio Service stopped event? I didn't find any events to check if service is stopped
(Answer update: Since v0.18, the service is effectively always running while the app is running, so there is no longer a need to check. The following answer is for v0.17 and earlier.)
AudioService.running will emit true when the service is running and false when it is not.
To listen to when it changes from true to false, you could try this:
// Cast runningStream from dynamic to the correct type.
final runningStream =
AudioService.runningStream as ValueStream<bool>;
// Listen to stream pairwise and observe when it becomes false
runningStream.pairwise().listen((pair) {
final wasRunning = pair.first;
final isRunning = pair.last;
if (wasRunning && !isRunning) {
// take action
}
});
If you instead want to listen to the stopped playback state, you need to ensure that your background audio task actually emits that state change in onStop:
#override
Future<void> onStop() async {
await _player.dispose();
// the "await" is important
await AudioServiceBackground.setState(
processingState: AudioProcessingState.stopped);
// Shut down this task
await super.onStop();
}
This way, you can listen for this state in the UI:
AudioService.playbackStateStream.listen((state) {
if (state.processingState == AudioProcessingState.stopped)) {
// take action
}
});

Flutter Blue Sending Quite Large File from BLE device to App

I need to send a file .txt from a device to my app (worst case almost 2mb). The BLE device divide the file into packages. I don't know if my method is correct, but I create a loop of characteristic.write/characteristic.read telling everytime what package the device has to send.
Here's my code:
for(int i = 0; i < packNumber.length; i++) {
initialValue = '9,50,100,$i,0,$checksumId,0/';
await characteristic
.write(utf8.encode(initialValue)).then((wValue) async {
await Future.delayed(Duration(milliseconds: 100)).then((value) async {
await characteristic.read().then((rValue) {
//do something with rVlaue
});
});
});
}
It works, but is it the best solution? And in case how can I speed up the transfer (for now I have to set a delay before reading, waiting for characteristic.write to finish)?
Thank you guys
You should change the remote device to send notifications instead of using reads. That gives maximum throughput.

Ensure processing of a REST call in flutter app in background

I need to ensure that a certain HTTP request was send successfully. Therefore, I'm wondering if a simple way exists to move such a request into a background service task.
The background of my question is the following:
We're developing a survey application using flutter. Unfortunately, the app is intended to be used in an environment where no mobile internet connection can be guaranteed. Therefore, I’m not able to simply post the result of the survey one time but I have to retry it if it fails due to network problems. My current code looks like the following. The problem with my current solution is that it only works while the app is active all the time. If the user minimizes or closes the app, the data I want to upload is lost.
Therefore, I’m looking for a solution to wrap the upload process in a background service task so that it will be processed even when the user closes the app. I found several posts and plugins (namely https://medium.com/flutter-io/executing-dart-in-the-background-with-flutter-plugins-and-geofencing-2b3e40a1a124 and https://pub.dartlang.org/packages/background_fetch) but they don’t help in my particular use case. The first describes a way how the app could be notified when a certain event (namely the geofence occurred) and the second only works every 15 minutes and focuses a different scenario as well.
Does somebody knows a simple way how I can ensure that a request was processed even when there is a bad internet connection (or even none at the moment) while allowing the users to minimize or even close the app?
Future _processUploadQueue() async {
int retryCounter = 0;
Future.doWhile(() {
if(retryCounter == 10){
print('Abborted after 10 tries');
return false;
}
if (_request.uploaded) {
print('Upload ready');
return false;
}
if(! _request.uploaded) {
_networkService.sendRequest(request: _request.entry)
.then((id){
print(id);
setState(() {
_request.uploaded = true;
});
}).catchError((e) {
retryCounter++;
print(e);
});
}
// e ^ retryCounter, min 0 Sec, max 10 minutes
int waitTime = min(max(0, exp(retryCounter)).round(), 600);
print('Waiting $waitTime seconds till next try');
return new Future.delayed(new Duration(seconds: waitTime), () {
print('waited $waitTime seconds');
return true;
});
})
.then(print)
.catchError(print);
}
You can use the plugin shared_preferences to save each HTTP response to the device until the upload completes successfully. Like this:
requests: [
{
id: 8eh1gc,
request: "..."
},
...
],
Then whenever the app is launched, check if any requests are in the list, retry them, and delete them if they complete. You could also use the background_fetch to do this every 15 minutes.