0

In my flutter application I have written a background service to get the users location. When the application is in the background this location service still get users location and the application still functions.

I don't want the background location service to run after user terminate the application.

But it seems location service seems to be still running when I terminate my application on Android.

Also when I start the application 2nd time it does not function correctly. I assume this is because the background service is still running.

  • If I stop the application by "Force Stop" all works fine in 2nd time.

  • Also if I manually stop the background service from the application (say from a button click, calling the stop function ) then close the app, again all works fine.

Can someone provide some advice on how to stop the background service when I close the application?

MainActivity.kt is;

class MainActivity: FlutterActivity() {


    override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, LOCATION_CHANNEL).setMethodCallHandler {
                call, result ->

            if (call.method == "startLocationUpdate") {
                var status = startUpdateLocation()
                result.success(status.toString())
            } else if (call.method == "stopLocationUpdate")
            {
                var status = stopUpdateLocation()
                result.success(status.toString())
            } else if (call.method == "isLocationPermissionEnabled")
            {
                var status = checkPermission()
                result.success(status.toString())
            }
            else {
                result.notImplemented()
            }
        }

        EventChannel(flutterEngine.dartExecutor, LOCATION_EVENT_CHANNEL).setStreamHandler(
            object : EventChannel.StreamHandler {
                override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
                    locationUpdateReceiver = receiveLocationUpdate(events)
                }

                override fun onCancel(arguments: Any?) {
                    unregisterReceiver(locationUpdateReceiver)
                    locationUpdateReceiver = null
                    isServiceStarted = false
                }
            }
        )


    }

    override fun onDestroy() {
        try {
            if (locationUpdateReceiver != null )
            {

                unregisterReceiver(locationUpdateReceiver)

            }

        } catch (e: Exception) {
        }
        super.onDestroy()
    }

    private fun stopUpdateLocation() : Int {
        if (isServiceStarted) {
            unregisterReceiver(locationUpdateReceiver)
            stopService(this)
            isServiceStarted = false
            return SUCCESS
        }
        else {
            return SERVICE_NOT_RUNNING
        }
    }

    private fun startUpdateLocation() : Int {
        if (isServiceStarted) {
            return SERVICE_ALREADY_STARTED
        }
        else if (!checkPermission()) {
            //requestPermission()
            return REQUESTING_PERMISSION
        }
        else {
            registerReceiver(locationUpdateReceiver, locationIntentFilter);
            isServiceStarted = true
            startService(this)
            return SUCCESS
        }
    }

    private fun receiveLocationUpdate(events: EventChannel.EventSink): BroadcastReceiver {
        return object : BroadcastReceiver() {
            override fun onReceive(context: Context, intent: Intent) {
                val key = LocationManager.KEY_LOCATION_CHANGED
                val location: Location? = intent.extras!![key] as Location?
                if (location != null) {
                    val runningAppProcessInfo = ActivityManager.RunningAppProcessInfo()
                    ActivityManager.getMyMemoryState(runningAppProcessInfo)
                    var appRunningBackground: Boolean = runningAppProcessInfo.importance != ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
                    if (appRunningBackground) {
                        events.success("0," + location.latitude.toString() + "," + location.longitude.toString())
                    }
                    else {
                        events.success("1," + location.latitude.toString() + "," + location.longitude.toString())
                    }
                }
            }
        }
    }

    private fun checkPermission(): Boolean {
        val result = ContextCompat.checkSelfPermission(applicationContext, Manifest.permission.ACCESS_FINE_LOCATION)
        val result1 = ContextCompat.checkSelfPermission(applicationContext, Manifest.permission.ACCESS_COARSE_LOCATION)
        return result == PackageManager.PERMISSION_GRANTED && result1 == PackageManager.PERMISSION_GRANTED
    }


    companion object {
        private const val LOCATION_CHANNEL = "flutter.io/location"
        private const val LOCATION_EVENT_CHANNEL = "flutter.io/locationEvent"
        private const val LOCATION_UPDATE_INTENT = "FLUTTER_LOCATION"
        private const val PERMISSION_REQUEST_CODE = 1

        private final const val SERVICE_NOT_RUNNING = 0;
        private final const val SUCCESS = 1;
        private final const val REQUESTING_PERMISSION = 100;
        private final const val SERVICE_ALREADY_STARTED = 2;

        var isServiceStarted = false
        var duration = "1" ;
        var distance = "20";
        var locationIntentFilter = IntentFilter(LOCATION_UPDATE_INTENT)
        var locationUpdateReceiver: BroadcastReceiver? = null

        fun startService(context: Context) {
            val startIntent = Intent(context, LocationService::class.java)
            ContextCompat.startForegroundService(context, startIntent)
        }
        fun stopService(context: Context) {
            val stopIntent = Intent(context, LocationService::class.java)
            context.stopService(stopIntent)
        }
    }
}

in LocationSerivice.kt

class LocationService : Service() {
    private val NOTIFICATION_CHANNEL_ID = "notification_location"
    private val duration = 5 // In Seconds
    private val distance = 0  // In Meters

    override fun onCreate() {
        super.onCreate()
        isServiceStarted = true
        val builder: NotificationCompat.Builder =
            NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
                .setOngoing(false)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val notificationManager: NotificationManager =
                getSystemService(NOTIFICATION_SERVICE) as NotificationManager
            val notificationChannel = NotificationChannel(
                NOTIFICATION_CHANNEL_ID,
                NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_LOW
            )
            notificationChannel.description = NOTIFICATION_CHANNEL_ID
            notificationChannel.setSound(null, null)
            notificationManager.createNotificationChannel(notificationChannel)
            startForeground(1, builder.build())
        }
    }

    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        LocationHelper().startListeningLocation(this, duration, distance);
        return START_STICKY
    }

    override fun onBind(intent: Intent): IBinder? {
        return null
    }

    override fun onDestroy() {
        super.onDestroy()
        isServiceStarted = false
    }

    override fun onTaskRemoved(rootIntent: Intent?) {
        super.onTaskRemoved(rootIntent)

        stopSelf()
    }

    companion object {
        var isServiceStarted = false
    }
}

in my AndroidManifest.xml I have

   android:name=".LocationService"
   android:enabled="true"
   android:exported="true"
   android:stopWithTask="true"
   

In my flutter app I call the stop service in

  @override
  void dispose() async {

    if (_locationUpdateEventStarted) {
      await methodChannel.invokeMethod('stopLocationUpdate');
    }

    super.dispose();
  }

I also tried following, but it also did not work

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) async {
    super.didChangeAppLifecycleState(state);

    if (state == AppLifecycleState.detached) {
      
      if (_locationUpdateEventStarted) {
        await methodChannel.invokeMethod('stopLocationUpdate');
      }
    }
  }
Janaka
  • 2,505
  • 4
  • 33
  • 57
  • Does this answer your question? [How to stop Android service when app is closed](https://stackoverflow.com/questions/29323317/how-to-stop-android-service-when-app-is-closed) – mfkw1 Jul 06 '22 at 15:29
  • I think I have implemented all the solutions they talk about in the thread. Sill it is not working – Janaka Jul 06 '22 at 16:05
  • Any help on this guys? – Janaka Jul 08 '22 at 04:30

3 Answers3

2

i had similar or the same problem using flutter_background_service. i'm modify the onTaskRemoved.

flutter_background_service is stop by invokeMethod("stopService"). so, i found the stopService method code.

            if (method.equalsIgnoreCase("stopService")) {
                isManuallyStopped = true;
                WatchdogReceiver.remove(this);

                try {
                    synchronized (listeners) {
                        for (Integer key : listeners.keySet()) {
                            IBackgroundService listener = listeners.get(key);
                            if (listener != null) {
                                listener.stop();
                            }
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }

                stopSelf();
                result.success(true);
                return;
            }

and i was able to solve it by applying the stopService mothod code.

@Override
public void onTaskRemoved(Intent rootIntent) {
    isManuallyStopped = true;
    WatchdogReceiver.remove(this);

    try {
        synchronized (listeners) {
            for (Integer key : listeners.keySet()) {
                IBackgroundService listener = listeners.get(key);
                if (listener != null) {
                    listener.stop();
                }
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }

    stopSelf();
}

i'm not sure this is the correct solution. but i hope this solution help your problem.

I submited an issue, so keep that in mind. https://github.com/ekasetiawans/flutter_background_service/issues/300

jadekim
  • 38
  • 5
0

Actually service is Foreground service, which runs even if the application is closed.

Foreground services show a status bar notification, so that users are actively aware that your app is performing a task in the foreground and is consuming system resources. The notification cannot be dismissed unless the service is either stopped or removed from the foreground.

user18309290
  • 5,777
  • 2
  • 4
  • 22
  • Not very clear what you mean. What should I change to stop the service when the application is closed? – Janaka Jul 06 '22 at 18:51
0

This what i've made and it works

await service.configure(
    androidConfiguration: AndroidConfiguration(
        
        onStart: onStart,
        autoStart: true,
        isForegroundMode: false,

        notificationChannelId: 'my_foreground',
        initialNotificationTitle: 'my service',
        initialNotificationContent: 'the service is started',
        foregroundServiceNotificationId: 888,
        autoStartOnBoot: true
    ),
    iosConfiguration: IosConfiguration(
        autoStart: true,
        onForeground: onStart,
        onBackground: onIosBackground,
    ),
);