So I have an issue where my existing Flutter Workmanager background task is not updating automatically following the app's update.
I have the example code which sets up a periodic Workmanager job.
void main() async
{
WidgetsFlutterBinding.ensureInitialized();
Workmanager().initialize(
callbackDispatcher
);
await Workmanager().registerPeriodicTask(
"ExampleTask",
"ExampleTaskPeriodicTask",
initialDelay: Duration(minutes: 15,
frequency: Duration(hours: 1)
);
}
void callbackDispatcher() async
{
Workmanager().executeTask((task, inputData) async
{
await DoSomething();
return Future.value(true);
});
}
If I update DoSomething() in my background task, currently it does not update for the user and it would continue doing the old thing, not the new thing.
Is there any way to update the background task without the user having to do anything following the apps automatic update?
Through testing and debugging using adb to update the app (flutter run is not suitable), the resolution was to add existingWorkPolicy: ExistingWorkPolicy.append to the periodic task registry.
Related
GetX translation is not working on notifications that are created when the app is in the terminated state.
This is how I show the notifications. Below translations work in foreground notifications but not in the background (terminated)
showNotification(message){
...
var title = 'some_title'.tr
var body = 'some_body'.tr
...
}
And rest of the code, FirebaseMessaging.onBackgroundMessage(onBackgroundMessage) and onBackgroundMessage defined as required
Future<void> onBackgroundMessage(RemoteMessage message) async {
showNotification(message);
return Future.value();
}
void main() async {
...
FirebaseMessaging.onBackgroundMessage(onBackgroundMessage);
runApp(const MyApp());
}
Note: I know, this issue is happening because the app is not running yet. GetMaterialApp did not run, as a result, GetX didn't work too
Note: I'm not having any issue with navigations or showing notifications.
Im using Flutter and flutter_bloc to make an app where it periodically sends an API request to my server and requests data.
It was working perfectly at first before i implemented the periodic functionality using Timer, this is my bloc file:
class HomeBeehivesBloc extends Bloc<HomeBeehivesEvent, HomeBeehivesState> {
HomeBeehivesBloc() : super(BeehivesInitial()) {
on<LoadBeehives>((event, emit) => _onBeehivesLoaded(emit));
}
Future<void> _onBeehivesLoaded(Emitter<HomeBeehivesState> emit) async {
emit(BeehivesLoading());
final repository = BeehivesRepository();
const duration = Duration(seconds: 5);
Timer.periodic(duration, (timer) async {
try {
await repository.getHomeBeehives().then((beehives) async {
emit(BeehivesLoadedSuccessfully(beehives: beehives));
});
} catch (exception) {
log(exception.toString());
}
});
}
}
But im getting this exception:
'package:bloc/src/emitter.dart': Failed assertion: line 114 pos 7: '!_isCompleted':
emit was called after an event handler completed normally.
This is usually due to an unawaited future in an event handler.
Please make sure to await all asynchronous operations with event handlers
and use emit.isDone after asynchronous operations before calling emit() to
ensure the event handler has not completed.
**BAD**
on<Event>((event, emit) {
future.whenComplete(() => emit(...));
});
**GOOD**
on<Event>((event, emit) async {
await future.whenComplete(() => emit(...));
});
Ive tried searching everywhere for a solution, but honestly this is the first time i used the new version of the bloc package (never used emit before), and i would like some suggestion as to how to solve this issue.
Side question: Is it a good idea to implement the periodic timer there ? because i have seen some people implement it within the frontend (Stateful widget for example), i would also love any suggestions about this.
Thank you very much!
Your code does not wait for the callback inside Timer.periodic to complete - the _onBeehivesLoaded method finishes executing, hence when the callback tries to emit a new state (BeehivesLoadedSuccessfully), you get this error.
To resolve this, instead of emitting a new state inside the callback, you should add a new event to the BLoC and handle it later as any other BLoC event.
First of all, create a new event, like HomeBeehivesLoaded:
class HomeBeehivesLoaded extends HomeBeehivesEvent {
final List<Beehive> beehives; // <-- Not sure if Beehive is the right type, just an assumption
const HomeBeehivesLoaded(this.beehives);
}
Register a new event handler in your BLoC that will update the state based on the loaded behives:
class HomeBeehivesBloc extends Bloc<HomeBeehivesEvent, HomeBeehivesState> {
HomeBeehivesBloc() : super(BeehivesInitial()) {
<...>
on<HomeBeehivesLoaded>(_onHomeBeehivesLoaded);
}
void _onHomeBeehivesLoaded(HomeBeehivesLoaded event, Emitter<HomeBeehivesState> emit) {
emit(BeehivesLoadedSuccessfully(beehives: event.beehives));
}
<...>
}
Inside the Timer.periodic callback, add this event once you get the response from the repository instead of emitting a state:
class HomeBeehivesBloc extends Bloc<HomeBeehivesEvent, HomeBeehivesState> {
<...>
Future<void> _onBeehivesLoaded(Emitter<HomeBeehivesState> emit) async {
<...>
Timer.periodic(duration, (timer) async {
try {
await repository.getHomeBeehives().then((beehives) async {
add(HomeBeehivesLoaded(beehives: beehives));
});
} catch (exception) {
log(exception.toString());
}
});
}
}
I have custom queuing logic using MediaItem which works properly when the UI is active. I am stopping the audio playback using onTaskRemoved method.
#override
Future<void> onTaskRemoved() async {
//if (!AudioServiceBackground.state.playing) {
logger.v("Task Removed");
await stop();
//}
}
#override
Future<void> stop() async {
await _player.stop();
await playbackState.firstWhere(
(state) => state.processingState == AudioProcessingState.idle);
}
However after swiping the task, notification is still visible and when I click the notification to resume nothing happens and app is stuck in splash screen.
I am using the flutter_sound package to record some audio and as soon as the app starts up I initialise a recorder.
When I hot restart the app another recorder is initialised and the app crashes because on iOS there can only be one recorder.
When I cold restart the app I don't run into this problem, probably because all the resources are freed.
How can I make sure that the code that releases the recorder is called whenever I hot restart the app?
This is the relevant code in the UI.
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<RecorderService>(
create: (_) => RecorderService(),
),
],
child: MaterialApp(
home: ScreenToShow(),
),
);
}
And this is the relevant code in the Recorder Service class:
class RecorderService with ChangeNotifier {
Recording recording;
RecordingStatus status = RecordingStatus.uninitialized;
static const String RECORDING_FORMAT = ".aac";
static const String LISTENING_FORMAT = ".mp3";
static const Duration UPDATE_DURATION_OF_STREAM = Duration(milliseconds: 100);
RecorderService() {
_initialize();
}
/// Private properties
FlutterSoundRecorder _recorder;
Directory _tempDir;
FileConverterService _fileConverterService = FileConverterService();
/// This is the file path in which the [_recorder] writes its data. From the moment it gets assigned in [_initialize()] it stays fixed
String _pathToCurrentRecording;
/// This is the file path to which the [recording] will be saved to. It changes with every call of [_startWithoutReset()]
String _pathToSavedRecording;
/// This function can only be executed once per session else it crashes on iOS (because there is already an initialized recorder)
/// So when we hot restart the app this makes it crash
_initialize() async {
try {
/// The arguments for [openAudioSession] are explained here: https://github.com/dooboolab/flutter_sound/blob/master/doc/player.md#openaudiosession-and-closeaudiosession
_recorder = await FlutterSoundRecorder().openAudioSession(
focus: AudioFocus.requestFocusAndKeepOthers,
category: SessionCategory.playAndRecord,
mode: SessionMode.modeDefault,
audioFlags: outputToSpeaker);
await _recorder.setSubscriptionDuration(UPDATE_DURATION_OF_STREAM);
_tempDir = await getTemporaryDirectory();
_pathToSavedRecording =
"${_tempDir.path}/saved_recording$LISTENING_FORMAT";
status = RecordingStatus.initialized;
notifyListeners();
} catch (e) {
print("Recorder service could not be initialized because of error = $e");
}
}
#override
dispose() async {
try {
await _recorder?.closeAudioSession();
super.dispose();
} catch (e) {
print("Recorder service could not be disposed because of error = $e");
}
}
}
Are you properly closing the session. Read the documentation from here
I realized that this is a month past your initial post but I came across this problem today.
I've found that the fix is to not call the following within the initState() of the page:
_recorder = await FlutterSoundRecorder().openAudioSession(...)
Instead, I created the following:
Future<void> startAudioSession() async {recorderModule.openAudioSession(...);}
And called it at the beginning of the startRecorder function, and then used the closeAudioSession() in the stopRecorder function.
How can you create a scheduled service in Flutter, which will be triggered at a specific time every day, and it will run some code? It needs to work for both Android and IOS and even if the app is terminated.
You could make use of the alarm manager package.
A simple implementation of the same would look like below.
import 'dart:async';
import 'package:android_alarm_manager/android_alarm_manager.dart';
import 'package:flutter/widgets.dart';
void doStuff() {
print("do stuff every minute");
}
Future<void> main() async {
final int periodicID = 0;
// Start the AlarmManager service.
await AndroidAlarmManager.initialize();
runApp(const Center(
child:
Text('See device log for output', textDirection: TextDirection.ltr)));
await AndroidAlarmManager.periodic(
const Duration(minutes: 1), periodicID, doStuff,
wakeup: true);
}