How to set a time-specific event in Flutter? - 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

Related

Fire something inside a listener only if n seconds have passed since receiving last event

I am listening to an event, however, I don't want to print the event every time. There is an event being sent every second but I don't want my print to work every second. How can I make the print inside this listener to fire only, if 10 seconds is past since last event?
For e.g I receive an event, I use the print. I want to store the event somewhere, if 10 seconds is passed since last event, accept another event -> print and so on.
_controller.onLocationChanged.listen((event) {
print(event);
});
You may try something related to an asynchronous method as such. The following code will set the _isListening variable to true after 10 seconds, which will enable the listener to do it's action once again.
class YourClass{
bool _isListening = true;
void yourMethod() {
_controller.onLocationChanged.listen((event) {
if(_isListening){
_isListening = false;
print(event);
Future.delayed(const Duration(seconds: 10)).then((_) => _isListening=true);
}
});
}
}
Edit: Thanks to #pskink, the proper way to do it would be by using the debounceTime method. So in proper way:
_controller.onLocationChanged
.debounceTime(Duration(seconds: 10))
.listen((event) {
print(event);
});
Use the Timer like below:
Timer(const Duration(seconds: 10), (){
print("...");
});

Executing Function Every Second Day in Flutter (Timed)

I have this simple send mail function in Flutter, and I would like it to be executed (sent) for example every 48 hours. How would I go around doing that? Is there a simple way to time when it is executed? I don't think code is necessary here, but let me know if you need my send mail function (it is regular Mailer function).
You could use the Timer class:
const everySecondDay = const Duration(hours: 48);
final timer = Timer.periodic(everySecondDay, (Timer t) => sendMailFunction());
Then cancel it when appropriate:
timer.cancel();

Flutter Background Processes Using Android Alarm Manager and Isolates

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.

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.

Drag an event in fullCalendar component with a specific duration

I've seen the solution to drag and drop external events in fullcalendar. But, in this demo, all the external events have a duration of 2 hours (because defaultEventMinutes parameter is set to 120). I'm trying to change this demo in order to manage events with different durations. Say, "My event 1" is 45min long, "My event 2" is 165min, etc.
At the beginning I though there may be an attribute to store the duration in the eventObject, but according to the documentation, it's not the case.
Then, I thought it would be possible to change the value of 'defaultEventMinutes' when starting dragging the event. But apparently, I can't do it without rebuilding the whole calendar.
According to you, what is the best means to meet this requirement?
Thanks in advance for your advice...
Worked on this as well and have solved the duration shown on fullCalendar this way:
Having a custom "setOptions" function for fullCalendar.
Having a property for fullCalendar called "dragMinutes" that can be set during elements $(this).draggable({start:...}).
Here is the code for the custom setOptions:
...
function Calendar(element, options, eventSources) {
var t = this;
// hack for setting options that updates
function setOptions(new_options, refresh) {
$.extend(options, new_options);
if (refresh) {
var viewName = currentView.name;
changeView(viewName, true);
}
}
// exports ...
t.setOptions = setOptions;
...
Heres the code for handling "dragMinutes" option in fullCalendar:
/* External Dragging
--------------------------------------------------------------------------------*/
function dragStart(_dragElement, ev, ui) {
hoverListener.start(function (cell) {
clearOverlays();
if (cell) {
if (cellIsAllDay(cell)) {
renderCellOverlay(cell.row, cell.col, cell.row, cell.col);
} else {
var d1 = cellDate(cell);
if (opt('dragMinutes'))
var d2 = addMinutes(cloneDate(d1), opt('dragMinutes'));
else
var d2 = addMinutes(cloneDate(d1), opt('defaultEventMinutes'));
renderSlotOverlay(d1, d2);
}
}
}, ev);
}
And heres how i make event draggable and update the "dragMinutes":
// make the event draggable using jQuery UI
$(this).draggable({
containment: 'document',
// return a custom styled elemnt being dragged
helper: function (event) {
return $('<div class="uv-planning-dragging"></div>').html($(this).html());
},
opacity: 0.70,
zIndex: 10000,
appendTo: 'body',
cursor: 'move',
revertDuration: 0,
revert: true,
start: function (e, ui) {
// set the "dragMinutes" option in fullCalendar so shown interval about to be added is correct.
var data = $(this).data('eventObject');
if (data) {
var min = data.jsonProps.durationMsec / 1000 / 60;
if (macroCalendar.calendar) {
macroCalendar.calendar.fullCalendar('setOptions', { dragMinutes: Math.round(min) }, false);
}
}
},
stop: function (e, ui) {
// further process
}
});
Hope it helps.
If anyone still visits the thread and don't find the solution, the solution would be to set the duration parameter in event div... and then call draggable on that div.
$(this).data('event', {
title: 'new event title', // use the element's text as the event title
id: $(this).attr('id'),
stick: true, // maintain when user navigates (see docs on the renderEvent method)
duration: '03:00:00' // will set the duration during drag of event
});
Currently, the best solution I have found is adding a duration attribute on my event Object, then the code to create my fullCalendar looks like this:
$('#calendar').fullCalendar({
header: {
left: 'prev,next today',
center: 'title',
right: 'month,agendaWeek,agendaDay'
},
editable: true,
droppable: true, // this allows things to be dropped onto the calendar !!!
drop: function(date, allDay) { // this function is called when something is dropped
// retrieve the dropped element's stored Event Object
var originalEventObject = $(this).data('eventObject');
// we need to copy it, so that multiple events don't have a reference to the same object
var copiedEventObject = $.extend({}, originalEventObject);
// assign it the date that was reported
copiedEventObject.start = date;
// HERE I force the end date based on the start date + duration
copiedEventObject.end = new Date(date.getTime() + copiedEventObject.duration * 60 * 1000);
copiedEventObject.allDay = allDay;
// render the event on the calendar
// the last `true` argument determines if the event "sticks" (http://arshaw.com/fullcalendar/docs/event_rendering/renderEvent/)
$('#calendar').fullCalendar('renderEvent', copiedEventObject, true);
// is the "remove after drop" checkbox checked?
if ($('#drop-remove').is(':checked')) {
// if so, remove the element from the "Draggable Events" list
$(this).remove();
}
}
});
The only drawback is when you're dragging the event, the event duration looks like defaultEventMinutes and not the actual duration, but I don't know how to fix it
These special properties can either be specified in the provided event object, or they can be standalone data attributes:
<!-- DURATION OF 3 hours EVENT WILL PROPAGATE TO CALENDAR WHEN DROPPED -->
<div class='draggable' data-event='1' data-duration='03:00' />
https://fullcalendar.io/docs/dropping/eventReceive/
With the latest fullcalendar v2.0.2, if you want the overlay to be of the particular duration, you can update in this function of fullcalendar-arshaw.js
function dragStart(_dragElement, ev, ui) {
hoverListener.start(function(cell) {
clearOverlays();
if (cell) {
var seconds = duration_in_minutes * 1000 * 60 ;
// we need to pass seconds into milli-seconds
if (d1.hasTime()) {
d2.add(seconds);
renderSlotOverlay(d1, d2, cell.col);
}
else {
d2.add(calendar.defaultAllDayEventDuration);
renderDayOverlay(d1, d2, true, cell.col);
}
}
}, ev);
}
Here, pass your duration in the external events object and that object you can fetch in _dragElement and then convert it into milli-seconds and pass it in d2.add(seconds). This will create the shadow of that mili-seconds on that calendar.
For non-external events you can use the fullcalendar settings:
defaultTimedEventDuration: (hours+':00:00'),
forceEventDuration: true,
// defaultEventMinutes: hours*60, // not needed
and in the event data you do not set the end property (or you null it):
eventData = {
title: title,
start: start,
// end: end, // MUST HAVE no end for fixedduration
color: '#00AA00',
editable: true, // for dragging
};
Ref: http://fullcalendar.io/docs/event_data/defaultTimedEventDuration/
Tip: In case you want to prevent the resizing of the events which is possible due to editable: true, you can use CSS to hide the handle: .fc-resizer.fc-end-resizer { display:none; }
Since v4 some of the above options are not working at all. The problem i was facing was as follows:
All day items for me have a duration, but not a start time. When i select a start time by dragging, the start time is set but as soon as i set the end date ( which is done similar as above answers ), the end date is reset again.. there is something buggy going on in the setDate function... the end date is set, this part works, then it does a comparisson on itself to find out the time difference between the dates, but the date is already set by the system itself causing the difference to be 0 which is causing the enddate to be set to null again......
A giant pain in my neck i got to say... it works perfect when staying within the timeline, but that's about it.
I managed to 'fix', more like destroy it by using this line in the eventDrop event, but it will also work in any other events you may use:
update your event with ajax here, since you have the start and end date *
calendar.refetchEvents(); in the success function
This is going to refetch all the events, it sounds pretty killer for performance but it doesn't seem to take up much time, try it for yourself.
This way my titles, times etc are always up to date and the calendar is showing the right end date.