ekasetiawans / flutter_background_service

269 stars 192 forks source link

`flutter_background_service` stops only in release mode in some Android devices #413

Open hayatshin opened 9 months ago

hayatshin commented 9 months ago

I've keep counting user's steps as using flutter packages flutter_background_service, flutter_background_service_android and flutter_local_notifications.

I write all codes in class StepNotification.


class StepNotification {
  Future<void> initializeService() async {
    final String stepPrefsName = "${todayToStringLine()}_stepCount";

    final service = FlutterBackgroundService();

    const AndroidNotificationChannel channel = AndroidNotificationChannel(
      notificationChannelId,
      notificationChannelName,
      description: "This channel is used for step notification.",
      importance: Importance.low,
      playSound: false,
      enableVibration: false,
    );

    final FlutterLocalNotificationsPlugin flutterLocalNotifiationPlugin =
        FlutterLocalNotificationsPlugin();

    if (Platform.isIOS || Platform.isAndroid) {
      await flutterLocalNotifiationPlugin.initialize(
        const InitializationSettings(
          iOS: DarwinInitializationSettings(),
          android: AndroidInitializationSettings('@mipmap/noti_icon'),
        ),
      );
    }

    SharedPreferences sharedPreferences = await SharedPreferences.getInstance();

    await sharedPreferences.reload();
    int todayStepCount = sharedPreferences.getInt(stepPrefsName) ?? 0;

    await flutterLocalNotifiationPlugin
        .resolvePlatformSpecificImplementation<
            AndroidFlutterLocalNotificationsPlugin>()
        ?.createNotificationChannel(channel);

    await service.configure(
      androidConfiguration: AndroidConfiguration(
        foregroundServiceNotificationId: stepNotificationId,
        onStart: onStart,
        autoStart: true,
        autoStartOnBoot: true,
        notificationChannelId: notificationChannelId,
        initialNotificationTitle: "$todayStepCount 걸음",
        initialNotificationContent: "",
        isForegroundMode: true,
      ),
      iosConfiguration: IosConfiguration(
        autoStart: true,
        onForeground: onStart,
        onBackground: onIosBackground,
      ),
    );

    service.startService();
  }
}

// background
@pragma('vm:entry-point')
Future<void> onStart(ServiceInstance service) async {
  await dotenv.load(fileName: ".env");

  await Firebase.initializeApp();
  await Supabase.initialize(
    url: dotenv.env["SUPABASE_URL"]!,
    anonKey: dotenv.env["SUPABASE_ANONKEY"]!,
  );

  DartPluginRegistrant.ensureInitialized();
  SharedPreferences sharedPreferences = await SharedPreferences.getInstance();

  final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
      FlutterLocalNotificationsPlugin();

  StepEventCount stepEventCount = StepEventCount();
  StepRepository stepRepository = StepRepository();

  if (service is AndroidServiceInstance) {
    service.on('setAsForeground').listen((event) {
      service.setAsForegroundService();
    });
    service.on('setAsBackground').listen((event) {
      service.setAsBackgroundService();
    });

    service.on('stopService').listen((event) {
      service.stopSelf();
    });
  }

  // link step event count
  stepEventCount.initializeStepCount();

  Timer.periodic(
      const Duration(
        seconds: 1,
      ), (timer) async {
    final String stepPrefsName = "${todayToStringLine()}_stepCount";

    await sharedPreferences.reload();
    int todayStepCount = sharedPreferences.getInt(stepPrefsName) ?? 0;

    if (!sharedPreferences.containsKey(stepPrefsName)) {
      DateTime currentDate = currentKoreaDateTime();
      DateTime yesterdayDate = currentDate.subtract(const Duration(days: 1));
      String yesterdayString = dateTimeToStringDateLine(yesterdayDate);
      String yesterdayPrefsName = "${yesterdayString}_stepCount";

      if (sharedPreferences.containsKey(yesterdayPrefsName) &&
          sharedPreferences.containsKey("dummy")) {
        // 어제 값 있는 경우
        int yesterdayStepCount = sharedPreferences.getInt(yesterdayPrefsName) ??
            await stepRepository.fetchUserCertainDateStepCount(yesterdayString);
        await stepRepository.resetStepDatabase(
            yesterdayDate, yesterdayStepCount);

        int dummyPrefs = sharedPreferences.getInt("dummy") ?? 0;
        int updateDummyCount = yesterdayStepCount + dummyPrefs;
        await sharedPreferences.setInt("dummy", updateDummyCount);
        await sharedPreferences.setInt(stepPrefsName, 0);
      }

      // 2일 전 값 삭제
      DateTime dayBeforeYesterdayDate =
          currentDate.subtract(const Duration(days: 2));
      String dayBeforeYesterdayString =
          dateTimeToStringDateLine(dayBeforeYesterdayDate);
      String dayBeforeYesterdayPrefsName =
          "${dayBeforeYesterdayString}_stepCount";

      if (sharedPreferences.containsKey(dayBeforeYesterdayPrefsName)) {
        await sharedPreferences.remove(dayBeforeYesterdayPrefsName);
      }
    }

    service.invoke("step", {
      stepStreamKey: todayStepCount,
    });

    if (service is AndroidServiceInstance) {
      flutterLocalNotificationsPlugin.show(
        stepNotificationId,
        "$todayStepCount 걸음",
        "",
        const NotificationDetails(
          android: AndroidNotificationDetails(
            notificationChannelId,
            notificationChannelName,
            showWhen: false,
            ongoing: true,
            autoCancel: false,
            importance: Importance.high,
            priority: Priority.high,
            playSound: false,
            enableVibration: false,
          ),
          iOS: DarwinNotificationDetails(
            presentAlert: false,
            presentBadge: false,
            presentSound: false,
          ),
        ),
      );
    }
  });

  // 1 hours todatStepCount update
  Timer.periodic(const Duration(hours: 1), (timer) async {
    if (service is AndroidServiceInstance) {
      final String stepPrefsName = "${todayToStringLine()}_stepCount";

      int todayStepCount = sharedPreferences.getInt(stepPrefsName) ?? 0;

      await stepRepository.updateTodayStepCount(todayStepCount);
    }
  });
}

I check activityRecognition and if this permission is given, I call background work.

bool userStepStatus = Platform.isAndroid
        ? await Permission.activityRecognition.status
        : await Permission.sensors.status;

if(userStepStatus) {
   StepNotification().initializeService();
}

I realized that some permission is more needed in AndroidManifest from Android 14 based on document.

So I set below.

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_HEALTH" />

  <service
            android:name="id.flutter.flutter_background_service.BackgroundService"
            android:foregroundServiceType="health"
        />

And it works well.

However after some days later, some Android devices shows not our notification icon, but default icon, which means something go wrong.

enter image description here

Even this counting user step doesn't work at all.

And I know that background work can be killed by device automatically, but after this default icon shows, then even if user open app, which means this app calls StepNotification().initializeService(), Default icon is not changed and counting step also doesn't work at all.

And even sometimes without default icon, just app icon disappears and of course background work is not working even user opens app which means it calls background work even from foreground again.

I can understand device kills background work due to battery. But even user call background work again, why doesn't it work again?

Even I debug with the device having this issue, It works perfect. But only when I download app in release mode, it shows this error so I can't see any issue log.

Please help me whether some code or permission is missing here.

grkemtneri commented 8 months ago

@hayatshin did you find any solution ?

NerdyCrow commented 5 months ago

Did you find any solutions? Does your service start automatically after restarting your phone?

Zer0S2m commented 3 months ago

Good day!

Will there be a solution to this problem or some workarounds?