transistorsoft / flutter_background_geolocation_firebase

Firebase proxy for Flutter Background Geolocation SDK
MIT License
21 stars 7 forks source link

[BUG] Firestore is conflicting with `cloud_firestore` after reboot #28

Closed fixatd closed 1 year ago

fixatd commented 1 year ago

Your Environment

Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.7.7, on macOS 13.2.1 22D68 darwin-arm64, locale en-GB)
[✓] Android toolchain - develop for Android devices (Android SDK version 32.1.0-rc1)
[✓] Xcode - develop for iOS and macOS (Xcode 14.2)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2021.2)
[✓] VS Code (version 1.76.1)
[✓] Connected device (3 available)
[✓] HTTP Host Availability

• No issues found!
await BackgroundGeolocationFirebase.configure(
      BackgroundGeolocationFirebaseConfig(
        locationsCollection: '/user/$userId/location/latest',
        updateSingleDocument: true,
      ),
);

pubspec.yaml

  flutter_background_geolocation: ^4.9.0
  background_fetch: ^1.1.5
  background_geolocation_firebase: ^1.0.0
ext {
       ....
        firebaseCoreVersion = "17.4.4"          // Or latest
        firebaseFirestoreVersion = "21.5.0"     // Or latest
    }

To Reproduce Steps to reproduce the behavior:

  1. Create a scheduled "one-off" task using BackgroundFetch library that restarts location tracking after a certain amount of time i.e. 5 mins;
  2. Reboot device;
  3. Wait for BackgroundGeolocation to restart location tracking;
  4. Bring up application UI to forefront, error below is throw and Firestore no longer works;

Debug logs

03-16 16:52:06.296  8875  9370 I flutter : MissingPluginException(No implementation found for method listen on channel plugins.flutter.io/firebase_firestore/document/<Record ID omitted>)
03-16 16:52:06.297  8875  9370 I flutter : #0      MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:313)
03-16 16:52:06.297  8875  9370 I flutter : <asynchronous suspension>
03-16 16:52:06.298  8875  9370 I flutter : #1      EventChannel.receiveBroadcastStream.<anonymous closure> (package:flutter/src/services/platform_channel.dart:662)
03-16 16:52:06.298  8875  9370 I flutter : <asynchronous suspension>

Additional context

We have a feature on our application that allows the user to turn off their location tracking for a certain amount of time, the restart mechanism itself uses BackgroundFetch and works just fine. The issue happens when we reboot the device, just for context most of the users we have will turn off the device after turning off location tracking since they're conscious about being tracked and they will turn on the device once they know they have to be tracked again.

After a bit of debugging, I noticed that we weren't getting the issue when we don't have background_geolocation_firebase installed which points to a conflict with how firestore is being initialised after the reboot and subsequent re-initialisation of the application.

Also as a side note, the location tracking and firestore location sync works just fine when the application UI isn't brought to the foreground.

Let me know if you need anymore logs or info from our end, wasn't sure what you'd need so I've added just the obvious ones.

christocracy commented 1 year ago

Post your entire lib/main.dart

fixatd commented 1 year ago

@christocracy here is the content of lib/main.dart.

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:flutter/material.dart';
import 'package:flutter_native_splash/flutter_native_splash.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:uk_power_mobile/features/init/init.dart';
import 'package:uk_power_mobile/features/routes.dart';
import 'package:uk_power_mobile/firebase_options.dart';
import 'package:uk_power_mobile/locator.dart';
import 'package:uk_power_mobile/logger.dart';
import 'package:uk_power_mobile/navigator_plus.dart';
import 'package:background_fetch/background_fetch.dart';
import 'package:flutter_background_geolocation/flutter_background_geolocation.dart'
    as bg;
import 'package:uk_power_mobile/services/location.dart';
import 'package:uk_power_mobile/services/messaging.dart';
import 'package:uk_power_mobile/ui/colors.dart';
import 'package:uk_power_mobile/ui/widgets/app_builder.dart';

@pragma('vm:entry-point')
void headlessLocationEventHandler(bg.HeadlessEvent headlessEvent) async {
  InternalLogger.debug('[BackgroundGeolocation HeadlessTask]: $headlessEvent');

  switch (headlessEvent.name) {
    case bg.Event.HEARTBEAT:
      bg.HeartbeatEvent event = headlessEvent.event;
      InternalLogger.debug("onHeartbeat detected: $event");
      InternalLogger.debug("Trying to fetch current location");
      bg.Location location = await bg.BackgroundGeolocation.getCurrentPosition(
          timeout: 60, samples: 1, maximumAge: 2000);
      InternalLogger.debug('Returned location: $location');
      break;

    case bg.Event.LOCATION:
      bg.Location location = headlessEvent.event;
      InternalLogger.debug("onLocation detected: $location");
      break;

    case bg.Event.CONNECTIVITYCHANGE:
      bg.ConnectivityChangeEvent event = headlessEvent.event;
      InternalLogger.debug("onConnectivityChange detected: $event");
      if (event.connected) {
        InternalLogger.debug("Device is connected, triggering sync");
      }
      break;
  }
}

@pragma('vm:entry-point')
void backgroundFetchHandler(HeadlessTask task) async {
  String taskId = task.taskId;
  InternalLogger.debug('Background task event received, $taskId');

  switch (taskId) {
    case offDutyStopTaskId:
      InternalLogger.debug("Trying to start BackgroundGeolocation");
      bg.BackgroundGeolocation.state.then((state) {
        InternalLogger.debug("Config change ready: $state");
        if (!state.enabled) {
          InternalLogger.debug(
              "BackgroundGeolocation is not enabled, trying to start");
          bg.BackgroundGeolocation.start();
        }
      });
      break;
    case offDutyStartTaskId:
      InternalLogger.debug("Stopping location tracking");
      bg.BackgroundGeolocation.stop();
      break;
  }

  BackgroundFetch.finish(taskId);
}

Future<void> main() async {
  InternalLogger.debug("Starting application");
  WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();

  InternalLogger.debug("Showing splash screen");
  FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);

  InternalLogger.debug("Initialising firebase");
  FirebaseApp firebaseApp = await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  InternalLogger.debug("Firebase App: ${firebaseApp.name}");

  setupCrashlytics();

  FirebaseFirestore.instance.settings = const Settings(
    persistenceEnabled: true,
    cacheSizeBytes: Settings.CACHE_SIZE_UNLIMITED,
  );

  setupLocator();

  InternalLogger.debug("Setting up messaging service");
  locator<MessagingService>().init();

  InternalLogger.debug("Setting up background geolocation headless tasks");
  bg.BackgroundGeolocation.registerHeadlessTask(headlessLocationEventHandler);
  BackgroundFetch.registerHeadlessTask(backgroundFetchHandler);

  runApp(const ProviderScope(child: UkPowerApp()));
}

void setupCrashlytics() {
  InternalLogger.debug("Setting up crashlytics");
  FlutterError.onError = (errorDetails) {
    FirebaseCrashlytics.instance.recordFlutterFatalError(errorDetails);
  };
}

class UkPowerApp extends StatefulWidget {
  const UkPowerApp({Key? key}) : super(key: key);
  @override
  UkPowerAppState createState() => UkPowerAppState();
}

class UkPowerAppState extends State<UkPowerApp> {
  @override
  Widget build(BuildContext context) {
    return AppBuilder(
      builder: (context) {
        return MaterialApp(
          navigatorKey: navigatorPlus.key,
          debugShowCheckedModeBanner: false,
          routes: routes,
          theme: theme(),
          initialRoute: InitScreen.routeName,
        );
      },
    );
  }
}
christocracy commented 1 year ago

See here

fixatd commented 1 year ago

I'll give it a try, should I just call it directly from main() or would it make sense to also add it to the BackgroundFetch headless task handler (backgroundFetchHandler).

fixatd commented 1 year ago

I'm still getting the error even after adding DartPluginRegistrant.ensureInitialized() at main. I'm wondering if there is a way to call this on boot since it doesn't seem to be getting initialised correctly even at main.

fixatd commented 1 year ago

A bit of a late reply but I've managed to find what was causing the issue. I had included this code snippet as per the firebase documentation:

  FirebaseFirestore.instance.settings = const Settings(
    persistenceEnabled: true,
    cacheSizeBytes: Settings.CACHE_SIZE_UNLIMITED,
  );

And taking it out seems to have fixed everything.