How to send user location to Firestore even when app is killed in flutter? - firebase

I am trying to send user's location to Firestore even when the app is killed. I'm successfully able to send data to Firestore when the app is in the foreground or background but unable to send data when the app is killed. Can anyone explain what could be the problem?
I'm using
background_locator: ^1.6.4
Flutter
Flutter 2.2.3 • channel stable • https://github.com/flutter/flutter.git
Framework • revision f4abaa0735 (2 weeks ago) • 2021-07-01 12:46:11 -0700
Engine • revision 241c87ad80
Tools • Dart 2.13.4
Dart
Dart SDK version: 2.13.4 (stable) (Wed Jun 23 13:08:41 2021 +0200) on "linux_x64"
This code that I'm trying to execute once data is received
receiverPort.listen(
(dynamic data) async {
if (data != null) {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
final LocationDto position = data as LocationDto;
print('data: $position');
final Member member = await FirebaseService().getCurrentUserProfile();
member.currentPosition = '${position.latitude},${position.longitude}';
await FirebaseService()
.updateData(
FirebaseService.memberRef,
FirebaseService.memberChildId,
FirebaseService.getCurrentUserId(),
member.toJson())
.then((value) {});
} else {
print('data is null');
}
},
);
Please let me know if I need to share anything else. Thanks

I found the problem. I need to add Firestore code in the callback instead of in the ReceivePort.
Here is the working code:
Future<void> _startLocator() async {
final Map<String, dynamic> data = {'countInit': 1};
return BackgroundLocator.registerLocationUpdate(
LocationCallbackHandler.callback, // <-- You need to add your callback code here
initCallback: LocationCallbackHandler.initCallback,
initDataCallback: data,
disposeCallback: LocationCallbackHandler.disposeCallback,
iosSettings: const IOSSettings(
accuracy: location_settings.LocationAccuracy.BALANCED,),
androidSettings: AndroidSettings(
accuracy: location_settings.LocationAccuracy.BALANCED,
interval: 5,
androidNotificationSettings: AndroidNotificationSettings(
notificationChannelName: 'Location tracking',
notificationTitle:
location.isNotEmpty ? location : 'Start Location Tracking',
notificationMsg: 'Track location in background',
notificationBigMsg:
'Background location is on to keep the app up-to-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)));
}
Future<void> callback(LocationDto locationDto) async {
// Your call back code goes here
}

When the app's process is killed, all of its code stops executing. This includes any Firestore listeners. The OS does this in order to prevent an app from unexpectedly burning battery and data when the user isn't using it. This is a feature intended for the benefit of the end user, and your app should respect that.
See also:
Flutter: cross-platform way to keep application running in the background
How to keep my flutter app running in the background when close?
How do I run code in the background, even with the screen off?

Related

Firestore Native Client SDK cold start? (React Native Firebase)

In short: Is there some kind of cold start when connecting to Firestore directly from Client SDK
Hey. I'm using Firestore client sdk in Andoid and IOS application through #react-native-firebase.
Everything works perfectly but I have noticed weird behavior I haven't found explanation.
I have made logging to see how long it takes from user login to retrieve uid corresponding data from Firestore and this time has been ~0.4-0.6s. This is basically the whole onAuthStateChanged workflow.
let userLoggedIn: Date;
let userDataReceived: Date;
auth().onAuthStateChanged(async (user) => {
userLoggedIn = new Date();
const eventsRetrieved = async (data: UserInformation) => {
userDataReceived = new Date();
getDataDuration = `Get data duration: ${(
(userDataReceived.getTime() - userLoggedIn.getTime()) /
1000
).toString()}s`;
console.log(getDataDuration)
// function to check user role and to advance timing logs
onUserDataReceived(data);
};
const errorRetrieved = () => {
signOut();
authStateChanged(false);
};
let unSub: (() => void) | undefined;
if (user && user.uid) {
const userListener = () => {
return firestore()
.collection('Users')
.doc(user.uid)
.onSnapshot((querySnapshot) => {
if (querySnapshot && querySnapshot.exists) {
const data = querySnapshot.data() as UserInformation;
data.id = querySnapshot.id;
eventsRetrieved(data);
} else errorRetrieved();
});
};
unSub = userListener();
} else {
if (typeof unSub === 'function') unSub();
authStateChanged(false);
}
});
Now the problem. When I open the application ~30-50 minutes after last open the time to retrieve uid corresponding data from Firestore will be ~3-9s. What is this time and why does it happen? And after I open the application right after this time will be low again ~0.4-0-6s.
I have been experiencing this behavior for weeks. It is hard to debug as it happens only on build application (not in local environments) and only between +30min interval.
Points to notice
The listener query (which I'm using in this case, I have used also simple getDoc function) is really simple and focused on single document and all project configuration works well. Only in this time interval, which seems just like cold start, the long data retrieval duration occurs.
Firestore Rules should not be slowing the query as subsequent request are fast. Rules for 'Users' collection are as follows in pseudo code:
function checkCustomer(){
let data =
get(/databases/$(database)/documents/Users/$(request.auth.uid)).data;
return (resource.data.customerID == data.customerID);
}
match /Users/{id}{
allow read:if
checkUserRole() // Checks user is logged in and has certain customClaim
&& idComparison(request.auth.uid, id) // Checks user uid is same as document id
&& checkCustomer() // User can read user data only if data is under same customer
}
Device cache doesn't seem to affect the issue as application's cache can be cleaned and the "cold start" still occurs
Firestore can be called from another environment or just another mobile device and this "cold start" will occur to devices individually (meaning that it doesn't help if another device opened the application just before). Unlike if using Cloud Run with min instances, and if fired from any environment the next calls right after will be fast regardless the environment (web or mobile).
EDIT
I have tested this also by changing listener to simple getDoc call. Same behavior still happens on a build application. Replacing listener with:
await firestore()
.collection('Users')
.doc(user.uid)
.get()
.then(async document => {
if (document.exists) {
const data = document.data() as UserInformation;
if (data) data.id = document.id;
eventsRetrieved(data);
}
});
EDIT2
Testing further there has been now 3-15s "cold start" on first Firestore getDoc. Also in some cases the timing between app open has been only 10 minutes so the minimum 30 min benchmark does not apply anymore. I'm going to send dm to Firebase bug report team to see things further.
Since you're using React Native, I assume that the documents in the snapshot are being stored in the local cache by the Firestore SDK (as the local cache is enabled by default on native clients). And since you use an onSnapshot listener it will actually re-retrieve the results from the server if the same listener is still active after 30 minutes. From the documentation on :
If offline persistence is enabled and the listener is disconnected for more than 30 minutes (for example, if the user goes offline), you will be charged for reads as if you had issued a brand-new query.
The wording here is slightly different, but given the 30m mark you mention, I do expect that this is what you're affected by.
In the end I didn't find straight answer why this cold start appeared. I ended up changing native Client SDK to web Client SDK which works correctly first data fetch time being ~0.6s (always 0.5-1s). Package change fixed the issue for me while functions to fetch data are almost completely identical.

AWS Amplify DataStore not syncing with DynamoDB

My DataStore is not syncing with DynamoDB for some reason.
I've ready every issue on stackoverflow to see if I can find a resolution but no dice.
There are no errors.
Hub is showing the events firing.
Here is an example of the issue:
try {
const result = await DataStore.save(
new Employer({
name: 'Test Employer',
rate: 123.45,
}),
)
console.log('Employer saved successfully!')
console.log(result)
// const employer = await DataStore.query(Employer)
// console.log('EMPLOYER = ')
// console.log(employer)
} catch (err) {
console.log('ERROR: An error occurred during getEmployer')
console.log('Error message was ' + JSON.stringify(err))
}
DataStore nevers seems to sync with DynamoDB.
Other than that everything is fine. No issues. DataStore contains the correct data.
The only difference between my project and the code examples if that I have used Amplify Studio to build the data model and performed Amplify pull to update the project.
When I do an "amplify status" the API looks correct.
The aws-exports.js file seems to be correct.
It contains entries for Auth, API, Storage etc.
Auth is working correctly.
What am I missing?

Firebase Messaging 7.0.0 to 9.0.1 conversion

I'm trying to set up FCM for my iOS flutter app and followed the guide by flutter and even the guide from Fireship.io
I was working with an older version of FCM before I updated the dependencies and now there are methods that are deprecated or just plainly left out. I am unsure of which methods to replace these and I was unable to find resources on what exactly changed in between versions.
Below is my code from FCM 7.0.0
Code:
final FirebaseMessaging _fcm = FirebaseMessaging.instance;
StreamSubscription iosSubscription;
#override
void initState() {
super.initState();
if (Platform.isIOS) {
iosSubscription = _fcm.onIosSettingsRegistered.listen((data) {
// save the token OR subscribe to a topic here
});
_fcm.requestNotificationPermissions(IosNotificationSettings);
}
}
_saveDeviceToken() async {
// Get the current user
String uid = FirebaseAuth.instance.currentUser.uid;
// FirebaseUser user = await _auth.currentUser();
// Get the token for this device
String fcmToken = await _fcm.getToken();
// Save it to Firestore
if (fcmToken != null) {
FirebaseFirestore.instance
.collection('users')
.doc(uid)
.collection('tokens')
.add({
'token': fcmToken,
'createdAt': FieldValue.serverTimestamp(), // optional
'platform': Platform.operatingSystem
});
}
}
So my problems lie with the onIosSettingsRegistered line and the requestNotificationPermissions.
For both it says that method is not defined, because it has been deprecated or is no longer a valid method. Obviously, before updating my dependencies it worked fine no errors. I'm looking for resources revolving around Firebase cloud Messaging 9.0.1 that might show what was updated and what are the current methods to use.
The above answer is what lead me to fixing my issues. I'm just outlining below what I pulled from the guide the other user posted
FirebaseMessaging() no longer works and instead use
FirebaseMessaging.instance
IosNotificationSettings which was used with requestNotificationPermissions() can now be done with just RequestPermission()
Setting up a FCM.configure no longer works and you should just use FirebaseMessaging.onMessage(), FirebaseMessaging.onMessageOpenedApp, and FirebaseMessaging.onBackGroundMessage(). The last one can be paired with BkacgroundMessageHandeler in order to set up notifications when the app is closed or suspended.
And lastly, saving tokens still works the same as 7.0.0 as it does in 9.1.0 so no changes there.
9.1.0 brings null safety to the table so (depending on the rest of your dependencies) you can run the newest version of flutter with null safety with no issues.
Simple Google searches yielded the following results, perhaps you can try a bit harder before posting a question next time:
onIosSettingsRegistered is deprecated
Usage of the IosNotificationSettings class is now deprecated (currently used with the now deprecated requestNotificationPermissions() method).
Instead of this class, use named arguments when calling requestPermission() and read the permissions back via NotificationSettings.
requestNotificationPermissions deprecated too
DEPRECATED: requestNotificationPermissions() has been deprecated in favor of requestPermission().

Flutter android_alarm_manager without firebase auth

I'm trying to use the Flutter android_alarm_manager to create some background tasks but can't seem to get it working without the firebase auth. I followed the readme on the plugin, but just adding the android_alarm_manager plugin in the pubspec.yaml causes the app to crash immediately (emulator and real device) on startup. How can I avoid the Firebase integration and still use the alarm manager plugin?
Re: but can't seem to get it working without the firebase auth
Maybe at the time of your attempt back in 2018, there was some unintended entangling or some other issue in your app, but as of now, https://pub.dev/packages/android_alarm_manager does not require any Firebase.
Just need to set up some permissions, a service, and receivers in the manifest, then this sample is runnable:
import 'package:android_alarm_manager/android_alarm_manager.dart';
void printHello() {
final DateTime now = DateTime.now();
final int isolateId = Isolate.current.hashCode;
print("[$now] Hello, world! isolate=${isolateId} function='$printHello'");
}
main() async {
final int helloAlarmID = 0;
await AndroidAlarmManager.initialize();
runApp(...);
await AndroidAlarmManager.periodic(const Duration(minutes: 1), helloAlarmID, printHello);
}
Also see /flutter/android_alarm_manager/example/

Firebase Auth successful, but Database Ref set and update does not happen in electron app version 1.4.2

I'm able to authorize the Firebase app from my existing Electron app using firebase.auth().signInWithCustomToken. The promise for this method resolves and I'm able to obtain the current authorized user with firebase.auth().currentUser.uid.
At this point I must technically be able to write to /users/<currentUser>. However calling the userRef.set() and userRef.update() methods does not update the database reference and fails silently (both the callback and the promise from these methods do not resolve and there is no error thrown).
What is strange is that the exact same code works in a different, newly created Electron app. My code looks like below:
const writeToFirebase = (customToken) => {
syncApp.auth().signInWithCustomToken(customToken).then(user => {
const userId = firebase.auth().currentUser.uid; // this is successfull
const userRef = firebase.database().ref("/users/" + userId);
userRef.set({data: data}, () => { //callback does not trigger });
userRef.update({data: data})
.then(() => {//promise does not resolve})
.catch(err) => {// promise is not rejected either! }
});
}
Any pointers on how to go about debugging this would be helpful.
I discovered the problem. It's unlikely anybody else would have the same issue, but if you do, take a look at the userAgent value in your browserWindow.loadURL in Electron.
Mine was set to an Android mobile device & Firebase was not setting/updating due to this reason. I presume the Firebase server reacts differently when it sees a mobile userAgent and I was using the Firebase JS SDK and not the Android SDK which caused the issue.

Resources