AlarmManager not starting when app is removed from the background - flutter

I am trying to get the location data on some particular time. On every time we want to fetch the location data, we are sending a silent notification to our app. When app sees a silent notification It tries to get the data and upload back to the server. Every thing is working fine when app is running in foreground, but when i close the app and clear from the background, alarm manager is not getting fired to fetch and save the location.
_firebaseMessaging.configure(
onMessage: (
Map<String, dynamic> message,
) async {
final Map data =
message.containsKey('data') ? message['data'] as Map : null;
if (data?.isEmpty ?? false) {
_handleNotification(message);
} else {
_handleSilentEvent(message); // This method will be called when server will make call to fetch location data
}
},
onResume: (Map<String, dynamic> message) async {},
onLaunch: (Map<String, dynamic> message) async {},
onBackgroundMessage: _handleSilentEvent,
);
Future<dynamic> _handleSilentEvent(
final Map<String, dynamic> message,
) async {
final Map data = message.containsKey('data') ? message['data'] as Map : null;
if (data?.isNotEmpty ?? false) {
if (data.containsKey('getLocation')) {
await LocationService.triggerServiceToGetAndSaveLocation();
} else {
_handleWebEngage(data);
}
}
}
abstract class LocationService {
static const int _alarmManagerId = 1001;
static final ILocationFacade _locationFacade = getIt<ILocationFacade>();
LocationService._();
static Future<void> triggerServiceToGetAndSaveLocation() async {
AndroidAlarmManager.oneShotAt(
DateTime.now(),
_alarmManagerId,
LocationService._fetchAndSaveLocation,
wakeup: true,
exact: true,
);
}
static Future<void> _fetchAndSaveLocation() async {
final position = await _getCurrentLocation();
final capturedAt = DateTime.now().millisecondsSinceEpoch.toString();
final gpsEnabled = await Geolocator.isLocationServiceEnabled();
final LocationInfo locationInfo = LocationInfo(
lat: position.latitude,
lng: position.longitude,
capturedAt: capturedAt,
gpsEnabled: gpsEnabled,
);
print(
'onMessage:: Lat: ${position.latitude} Long: ${position.longitude} capturedAt: $capturedAt gpsEnabled: $gpsEnabled'); // This print is getting called when app is in foreground, when i kill the app this print is not event getting called.
// await _locationFacade.saveLocationInfo(locationInfo);
}
static Future<Position> _getCurrentLocation() async {
final _gpsServiceEnabled = await Geolocator.isLocationServiceEnabled();
if (_gpsServiceEnabled) {
return Geolocator.getCurrentPosition();
}
return Geolocator.getLastKnownPosition();
}
}
Can someone point me out where i am making the mistake?

Related

Unhandled Exception: Invalid argument: is a regular instance: Instance of 'LocationDto'

currently I am us this plugin to get the background location of a user, works great with ios but fails on android after recent updates and the plugin progress stalled, I am now faced with the issue below
so, in my main class i have this
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
// onLineStatus = UserSimplePreferences.getUsername() ?? ''
onLineStatus = UserSimplePrefences.getButtonStatus() ?? false;
displayToastMessage(onLineStatus.toString(), context);
osmController = MapController(
initMapWithUserPosition: true,
//initPosition: initPosition,
);
osmController.addObserver(this);
scaffoldKey = GlobalKey<ScaffoldState>();
if (IsolateNameServer.lookupPortByName(
LocationServiceRepository.isolateName) !=
null) {
IsolateNameServer.removePortNameMapping(
LocationServiceRepository.isolateName);
}
IsolateNameServer.registerPortWithName(
port.sendPort, LocationServiceRepository.isolateName);
port.listen(
(dynamic data) async {
if (data != null) await updateUI(data);
},
);
initPlatformState();
}
Future<void> updateUI(LocationDto data) async {
await _updateNotificationText(data);
}
Future<void> _updateNotificationText(LocationDto data) async {
await BackgroundLocator.updateNotificationText(
title: "Your location is updated",
msg: "${DateTime.now()}",
bigMsg: "${data.latitude}, ${data.longitude}");
}
Future<void> initPlatformState() async {
print('Initializing...');
await BackgroundLocator.initialize();
await BackgroundLocator.isServiceRunning();
}
Future<void> _startLocator() async {
Map<String, dynamic> data = {'countInit': 1};
return await BackgroundLocator.registerLocationUpdate(
LocationCallbackHandler.callback,
initCallback: LocationCallbackHandler.initCallback,
initDataCallback: data,
disposeCallback: LocationCallbackHandler.disposeCallback,
iosSettings: IOSSettings(
accuracy: LocationAccuracy.NAVIGATION,
distanceFilter: 0,
stopWithTerminate: true),
autoStop: false,
androidSettings: AndroidSettings(
accuracy: LocationAccuracy.NAVIGATION,
interval: 5,
distanceFilter: 0,
client: LocationClient.google,
androidNotificationSettings: AndroidNotificationSettings(
notificationChannelName: 'Location tracking',
notificationTitle: 'Start Location Tracking',
notificationMsg: 'Track location in background',
notificationBigMsg:
'Background location is on to keep the app up-tp-date with your location. This is required for main features to work properly when the app is not running.',
notificationIconColor: Colors.grey,
notificationTapCallback:
LocationCallbackHandler.notificationCallback)));
}
This is the LocationRepositoryClass
class LocationServiceRepository {
static LocationServiceRepository _instance = LocationServiceRepository._();
LocationServiceRepository._();
factory LocationServiceRepository() {
return _instance;
}
static const String isolateName = 'LocatorIsolate';
int _count = -1;
Future<void> init(Map<dynamic, dynamic> params) async {
//TODO change logs
print("***********Init callback handler");
if (params.containsKey('countInit')) {
dynamic tmpCount = params['countInit'];
if (tmpCount is double) {
_count = tmpCount.toInt();
} else if (tmpCount is String) {
_count = int.parse(tmpCount);
} else if (tmpCount is int) {
_count = tmpCount;
} else {
_count = -2;
}
} else {
_count = 0;
}
print("$_count");
await setLogLabel("start");
final SendPort? send = IsolateNameServer.lookupPortByName(isolateName);
send?.send(null);
}
Future<void> dispose() async {
print("***********Dispose callback handler");
print("$_count");
await setLogLabel("end");
final SendPort? send = IsolateNameServer.lookupPortByName(isolateName);
send?.send(null);
}
#pragma('vm:entry-point')
Future<void> callback(LocationDto locationDto) async {
print('$_count location in dart: ${locationDto.toString()}');
await setLogPosition(_count, locationDto);
final SendPort? send = IsolateNameServer.lookupPortByName(isolateName);
send?.send(locationDto);//error here
_count++;
}
static Future<void> setLogLabel(String label) async {
final date = DateTime.now();
// await FileManager.writeToLogFile(
// '------------\n$label: ${formatDateLog(date)}\n------------\n');
}
static Future<void> setLogPosition(int count, LocationDto data) async {
final date = DateTime.now();
// await FileManager.writeToLogFile(
// '$count : ${formatDateLog(date)} --> ${formatLog(data)} --- isMocked: ${data.isMocked}\n');
}
static double dp(double val, int places) {
num mod = pow(10.0, places);
return ((val * mod).round().toDouble() / mod);
}
static String formatDateLog(DateTime date) {
return date.hour.toString() +
":" +
date.minute.toString() +
":" +
date.second.toString();
}
static String formatLog(LocationDto locationDto) {
return dp(locationDto.latitude, 4).toString() +
" " +
dp(locationDto.longitude, 4).toString();
}
}
and this is the locationCallbackHandler class
class LocationCallbackHandler {
static Future<void> initCallback(Map<dynamic, dynamic> params) async {
LocationServiceRepository myLocationCallbackRepository =
LocationServiceRepository();
await myLocationCallbackRepository.init(params);
}
static Future<void> disposeCallback() async {
LocationServiceRepository myLocationCallbackRepository =
LocationServiceRepository();
await myLocationCallbackRepository.dispose();
}
#pragma('vm:entry-point')
static void callback(LocationDto locationDto) async {
LocationServiceRepository myLocationCallbackRepository =
LocationServiceRepository();
await myLocationCallbackRepository.callback(locationDto);
}
static Future<void> notificationCallback() async {
print('***notificationCallback');
}
}
Everthing was working fine until this update with flutter and kotlin, Now i have the error
[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Invalid argument: is a regular instance: Instance of 'LocationDto'
E/flutter (26006): #0 _SendPort._sendInternal (dart:isolate-patch/isolate_patch.dart:249:43)
E/flutter (26006): #1 _SendPort.send (dart:isolate-patch/isolate_patch.dart:230:5)
E/flutter (26006): #2 LocationServiceRepository.callback
package:drivers_app/tabPages/location_service_reposirtory.dart:59
The error points to this code
#pragma('vm:entry-point')
Future<void> callback(LocationDto locationDto) async {
print('$_count location in dart: ${locationDto.toString()}');
await setLogPosition(_count, locationDto);
final SendPort? send = IsolateNameServer.lookupPortByName(isolateName);
send?.send(locationDto); //error here
_count++;
}
There is no reply from the plugin developer, Any help would be appreciated.
https://github.com/Yukams/background_locator_fixed/pull/53
There were some errors after updating flutter to version 3, If you run into this error then please visit the above website for the fix.

flutter_webrtc: Cannot rejoin to the channel I left earlier

I am developing a streaming application: one application (server) broadcasts a video, and another application (client) displays this video. I used the flutter_webrtc package for real-time communication. I followed the following tutroial:
https://www.youtube.com/watch?v=hAKQzNQmNe0
https://github.com/md-weber/webrtc_tutorial/
Currently, the server application can successfully create a channel and broadcast the video, and the client application can join this channel and watch the video. But when the client leaves the channel and later tries to connect to the same channel, it can't get the video, only a black screen is displayed. There are no errors shown.
I used flutter_riverpod as state management and all codes below are inside StateNotifiers.
Function to create a channel from server application side.
Future<String> startStream(RTCVideoRenderer localVideo) async {
state = ProcessState.loading;
final stream = await navigator.mediaDevices.getUserMedia(<String, dynamic>{
'video': true,
'audio': true,
});
localVideo.srcObject = stream;
localStream = stream;
final roomId = await _createRoom();
await Wakelock.enable();
state = ProcessState.working;
return roomId;
}
Future<String> _createRoom() async {
final db = FirebaseFirestore.instance;
final roomRef = db.collection('rooms').doc();
peerConnection = await createPeerConnection(configuration);
localStream?.getTracks().forEach((track) {
peerConnection?.addTrack(track, localStream!);
});
// Code for collecting ICE candidates below
final callerCandidatesCollection = roomRef.collection('callerCandidates');
peerConnection?.onIceCandidate = (RTCIceCandidate candidate) {
callerCandidatesCollection.add(candidate.toMap() as Map<String, dynamic>);
};
// Add code for creating a room
final offer = await peerConnection!.createOffer();
await peerConnection!.setLocalDescription(offer);
final roomWithOffer = <String, dynamic>{'offer': offer.toMap()};
await roomRef.set(roomWithOffer);
roomId = roomRef.id;
// Listening for remote session description below
roomRef.snapshots().listen((snapshot) async {
final data = snapshot.data();
if (peerConnection?.getRemoteDescription() != null && data != null && data['answer'] != null){
final answer = data['answer'] as Map<String, dynamic>;
final description = RTCSessionDescription(
answer['sdp'] as String?,
answer['type'] as String?,
);
await peerConnection?.setRemoteDescription(description);
}
});
// Listen for remote Ice candidates below
roomRef.collection('calleeCandidates').snapshots().listen((snapshot) {
for (final change in snapshot.docChanges) {
if (change.type == DocumentChangeType.added) {
final data = change.doc.data();
peerConnection!.addCandidate(
RTCIceCandidate(
data?['candidate'] as String?,
data?['sdpMid'] as String?,
data?['sdpMLineIndex'] as int?,
),
);
}
}
});
return roomId!;
}
Function to join a channel from client application side.
Future<bool> startStream(String? roomId, RTCVideoRenderer remoteVideo) async {
if (roomId == null || roomId.isEmpty) {
return false;
}
state = ProcessState.loading;
final result = await _joinRoom(roomId, remoteVideo);
if (result) {
state = ProcessState.working;
await Wakelock.enable();
} else {
state = ProcessState.notInitialized;
}
return result;
}
Future<bool> _joinRoom(String roomId, RTCVideoRenderer remoteVideo) async {
final db = FirebaseFirestore.instance;
final DocumentReference roomRef = db.collection('rooms').doc(roomId);
final roomSnapshot = await roomRef.get();
if (roomSnapshot.exists) {
peerConnection = await createPeerConnection(configuration);
peerConnection?.onAddStream = (MediaStream stream) {
onAddRemoteStream?.call(stream);
remoteStream = stream;
};
// Code for collecting ICE candidates below
final calleeCandidatesCollection = roomRef.collection('calleeCandidates');
peerConnection?.onIceCandidate = (RTCIceCandidate candidate) {
final candidateMap = candidate.toMap() as Map<String, dynamic>;
calleeCandidatesCollection.add(candidateMap);
};
peerConnection?.onTrack = (RTCTrackEvent event) {
event.streams[0].getTracks().forEach((track) {
remoteStream?.addTrack(track);
});
};
// Code for creating SDP answer below
final data = roomSnapshot.data() as Map<String, dynamic>?;
final offer = data?['offer'] as Map<String, dynamic>?;
await peerConnection?.setRemoteDescription(
RTCSessionDescription(
offer?['sdp'] as String?,
offer?['type'] as String?,
),
);
final answer = await peerConnection!.createAnswer();
await peerConnection!.setLocalDescription(answer);
final roomWithAnswer = <String, dynamic>{
'answer': {
'type': answer.type,
'sdp': answer.sdp,
}
};
await roomRef.update(roomWithAnswer);
// Listening for remote ICE candidates below
roomRef.collection('callerCandidates').snapshots().listen((snapshot) {
for (final document in snapshot.docChanges) {
final data = document.doc.data();
peerConnection!.addCandidate(
RTCIceCandidate(
data?['candidate'] as String?,
data?['sdpMid'] as String?,
data?['sdpMLineIndex'] as int?,
),
);
}
});
this.roomId = roomId;
return true;
}
return false;
}
After some research, I found that my problem is the same as in the question Failed to set remote answer sdp: Called in wrong state: stable. This was caused by the fact that one RTCPeerConnection can only establish one peer-to-peer connection. So I could fix this by creating a new RTCPeerConnection on the server side every time a new client wants to join the channel.
Future<String> _createRoom() async {
final db = FirebaseFirestore.instance;
final roomRef = db.collection('rooms').doc();
await newPeerConnection(roomRef);
roomId = roomRef.id;
// Listening for remote session description below
roomRef.snapshots().listen((snapshot) async {
final data = snapshot.data();
if (data != null && data['answer'] != null) {
final answer = data['answer'] as Map<String, dynamic>;
final description = RTCSessionDescription(
answer['sdp'] as String?,
answer['type'] as String?,
);
await peerConnectionList.last.setRemoteDescription(description);
await newPeerConnection(roomRef);
}
});
// Listen for remote Ice candidates below
roomRef.collection('calleeCandidates').snapshots().listen((snapshot) {
for (final change in snapshot.docChanges) {
if (change.type == DocumentChangeType.added) {
final data = change.doc.data();
peerConnectionList.last.addCandidate(
RTCIceCandidate(
data?['candidate'] as String?,
data?['sdpMid'] as String?,
data?['sdpMLineIndex'] as int?,
),
);
}
}
});
return roomId!;
}
Future<void> newPeerConnection(DocumentReference roomRef) async {
final peerConnection = await createPeerConnection(
configuration,
offerSdpConstraints,
);
_registerPeerConnectionListeners(peerConnection);
localStream?.getTracks().forEach((track) {
peerConnection.addTrack(track, localStream!);
});
final offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
final roomWithOffer = <String, dynamic>{'offer': offer.toMap()};
await roomRef.set(roomWithOffer);
peerConnectionList.add(peerConnection);
}

Hide background location notification in failure

I'm using workmanager to retrieve user's location in background every 15 minutes. When the location fetch fails, I receive a notification with the error as you can see in picture. I would like to know how can I prevent the notification to show up in failure cases.
void callbackDispatcher() {
Workmanager.executeTask((taskName, inputData) async {
if (taskName == FETCH_USER_POSITION_IN_BACKGROUND_TASK_NAME) {
// TODO: Find a better way to get user position, maybe with ServiceLocator or even better with BLoC
final dataSource = GeolocatorDataSource();
final remoteDataSource = FirestoreRemoteDataSource(
firebaseFirestore: FirebaseFirestore.instance,
);
final repository = GeolocationRepository(
geolocationDataSource: dataSource,
remoteDataSource: remoteDataSource,
);
final positionEither = await repository.getUserPosition();
positionEither.fold((failure) async {
print('failure: $failure');
}, (position) async {
print('position = $position');
final storePositionEither =
await repository.storeUserPosition(position, inputData['uid']);
storePositionEither.fold((failure) async {
print('failure: $failure');
}, (isStored) async {
print("Position has been successfully stored in background!");
});
});
}
return Future.value(true);
});
}
void _initializeWorkManagerWhenAuthenticated(String userId) {
bool isProduction = bool.fromEnvironment('dart.vm.product');
Workmanager.initialize(
callbackDispatcher,
isInDebugMode: !isProduction,
);
Workmanager.registerPeriodicTask(
FETCH_USER_POSITION_IN_BACKGROUND_TASK_ID,
FETCH_USER_POSITION_IN_BACKGROUND_TASK_NAME,
frequency: Duration(minutes: 15),
existingWorkPolicy: ExistingWorkPolicy.keep,
inputData: {
'userId': userId,
},
);
}
Have you checked if the notifications are appearing when you run with isInDebugMode: false?
See: https://github.com/fluttercommunity/flutter_workmanager/blob/ea274c33b60ef1a4e29bdd392a477f67466dc25d/lib/src/workmanager.dart#L90

Flutter: Is there a way to get quit event in web application?

I want to call an API when the user closes or reloads the webpage(web application).
listen event
html.window.onBeforeUnload.listen(
(event) async {
Map<String, dynamic> _exit = await callApi(CloseRoomAPI);
print(_exit);
},
);
Call API function
Future<Map<String, dynamic>> callApi(String _url) async {
final client = new http.Client();
final connection = await client.get(_url);
if (connection.statusCode == 200) {
return jsonDecode(connection.body.toString());
} else {
return null;
}
}

Save and Retrieve notification with shared preferences list flutter

I have implemented Firebase Cloud Messaging in my flutter application. Everything works well but I want to store all list of messages locally with shared preferences and retrieve them in another screen. All my logic does not work well as I can't save the messages when the onMessage function is called.
PushNotificationService
class PushNotificationService {
final FirebaseMessaging _fcm = FirebaseMessaging();
List<String> titles;
List<String> msgs;
Future initialise() async {
notiList = List<NotiMessage>();
if (Platform.isIOS) {
// request permissions if we're on android
_fcm.requestNotificationPermissions(IosNotificationSettings());
_fcm.configure();
// For testing purposes print the Firebase Messaging token
String token = await _fcm.getToken();
print("FirebaseMessaging token: $token");
} else{
String token = await _fcm.getToken();
print("FirebaseMessaging token: $token");
}
_fcm.configure(
// Called when the app is in the foreground and we receive a push notification
onMessage: (Map<String, dynamic> message) async {
print('onMessage: $message');
//add list of messages to shared preferences
_setMessage(message);
},
// Called when the app has been closed comlpetely and it's opened
// from the push notification.
onLaunch: (Map<String, dynamic> message) async {
print('onLaunch: $message');
_serialiseAndNavigate(message);
//add list of messages to shared preferences
_setMessage(message);
},
// Called when the app is in the background and it's opened
// from the push notification.
onResume: (Map<String, dynamic> message) async {
print('onResume: $message');
_serialiseAndNavigate(message);
//add list of messages to shared preferences
_setMessage(message);
},
);
}
void _serialiseAndNavigate(Map<String, dynamic> message) {
var notificationData = message['data'];
var view = notificationData['view'];
if (view != null) {
// Navigate to desired page
if (view == 'create_post') {
}
}
}
_setMessage(Map<String, dynamic> message) {
//add list of messages to shared preferences
final notification = message['notification'];
final data = message['data'];
final String title = notification['title'];
final String body = notification['body'];
String mMessage = data['message'];
//add to list
titles.add(title);
msgs.add(mMessage);
//save to shared preferences (does not work)
storeTitles(titles);
storeMsgs(msgs);
print("Title: $title, body: $body, message: $mMessage");
}
void storeTitles(List<String> list) async{
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setStringList("notiTitles", list);
//list returns null
}
void storeMsgs(List<String> list) async{
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setStringList("notiMsgs", list);
}
Future<List<String>> getTitles(List<String> list) async{
SharedPreferences prefs = await SharedPreferences.getInstance();
list = prefs.getStringList("notiTitles");
return prefs.getStringList("notiTitles");
}
Future<List<String>> getMsgs(List<String> list) async{
SharedPreferences prefs = await SharedPreferences.getInstance();
list = prefs.getStringList("notiMsgs");
return prefs.getStringList("notiMsgs");
}
}
Whats the best way to achieve this. I want to save the messages persistently and call them in another screen. Please help me.
The code on saving the List on shared_preferences seems to be ok. The issue might be on how the data is fetched. If you're storing critical data, I suggest to better use something like provider instead. The shared_preferences plugin is unable to guarantee that writes will be persisted to disk after returning as mentioned in its docs.