Lyokone / flutterlocation

A Flutter plugin to easily handle realtime location in iOS and Android. Provides settings for optimizing performance or battery.
MIT License
1.11k stars 824 forks source link

MethodChannelLocation.enableBackgroundMode #961

Open dharambudh1 opened 4 months ago

dharambudh1 commented 4 months ago

MethodChannelLocation.enableBackgroundMode

Stack trace: PlatformException(error, Service.startForeground() not allowed due to mAllowStartForeground false: service com.ahinsaaggregator.vendor/com.lyokone.location.FlutterLocationService, null, android.app.ForegroundServiceStartNotAllowedException: Service.startForeground() not allowed due to mAllowStartForeground false: service com.ahinsaaggregator.vendor/com.lyokone.location.FlutterLocationService at android.app.ForegroundServiceStartNotAllowedException$1.createFromParcel(ForegroundServiceStartNotAllowedException.java:54) at android.app.ForegroundServiceStartNotAllowedException$1.createFromParcel(ForegroundServiceStartNotAllowedException.java:50) at android.os.Parcel.readParcelableInternal(Parcel.java:4882) at android.os.Parcel.readParcelable(Parcel.java:4864) at android.os.Parcel.createExceptionOrNull(Parcel.java:3064) at android.os.Parcel.createException(Parcel.java:3053) at android.os.Parcel.readException(Parcel.java:3036) at android.os.Parcel.readException(Parcel.java:2978) at android.app.IActivityManager$Stub$Proxy.setServiceForeground(IActivityManager.java:7234) at android.app.Service.startForeground(Service.java:775) at com.lyokone.location.FlutterLocationService.d(SourceFile:1) at bb.h.a(SourceFile:1) at bb.h.onMethodCall(SourceFile:1) at xb.j$a.a(SourceFile:1) at ob.c.l(SourceFile:1) at ob.c.m(SourceFile:1) at ob.c.i(SourceFile:1) at ob.b.run(SourceFile:1) at android.os.Handler.handleCallback(Handler.java:958) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loopOnce(Looper.java:230) at android.os.Looper.loop(Looper.java:319) at android.app.ActivityThread.main(ActivityThread.java:8934) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:578) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1103) )

Screenshot from 2024-06-22 10-53-38

Flutter version: Flutter (Channel stable, 3.19.3, on Ubuntu 24.04 LTS 6.8.0-35-generic, locale en_US.UTF-8) Plugin version: location: ^6.0.2 Device information: Samsung Galaxy A23 running Android 14

edwinmacalopu commented 4 months ago

You are missing this permission uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION"

dharambudh1 commented 4 months ago

Thank you for the reply! I am currently utilizing <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />, while <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" /> is not documented anywhere. Let's test it out.

edwinmacalopu commented 4 months ago

android.permission.FOREGROUND_SERVICE_LOCATION

Since Android 14, the permission is required https://developer.android.com/develop/background-work/services/foreground-services?hl=es-419#fgs-prerequisites

PistonShot1 commented 1 month ago

Currently there is a different in requesting permissions for android 11 and above there are certain times , the option for the background location settings doesnt get invoked as expected. For example, when I test on my android 14 , it immediately shows but for my andoid 12 it doesnt.

The background location permission is a bit different for android 11 and above , reference , here

So the solution that may guarantee to work is using native platform channel where you would invoke requestPermission , reference for this , here

I currently inovke the platform channel methods on initstate of the widget screen that expected to use it

Here is the native side of code, ofc will have to be added at MainActivity.kt

package com.cloone.edo

import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import android.widget.Toast

class MainActivity : FlutterActivity() {
  private val CHANNEL = "android.flutter.dev/permission"
  private val REQUEST_CODE_BACKGROUND_LOCATION = 1001
  private var permissionResult: MethodChannel.Result? = null

  override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
    super.configureFlutterEngine(flutterEngine)
    MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
      if (call.method == "requestBackgroundLocationPermission") {
        permissionResult = result
        requestBackgroundLocationPermission()
      } else {
        result.notImplemented()
      }
    }
  }

  private fun requestBackgroundLocationPermission() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
      // Check if permission is already granted
      if (checkSelfPermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION) == PackageManager.PERMISSION_GRANTED) {
        permissionResult?.success("already_granted")
      } else {
        // Request background location permission
        requestPermissions(arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION), REQUEST_CODE_BACKGROUND_LOCATION)
      }
    } else {
      // For android 10 and below , not required
      permissionResult?.success("not_required")
    }
  }

  override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    when (requestCode) {
      REQUEST_CODE_BACKGROUND_LOCATION -> {
        if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
          Toast.makeText(this, "Background Location Permission Granted", Toast.LENGTH_SHORT).show()
          permissionResult?.success("granted")
        } else {
          Toast.makeText(this, "Background Location Permission Denied", Toast.LENGTH_SHORT).show()
          permissionResult?.success("denied")
        }
      }
    }
  }
}

and on the dart side

Future<void> _getBackgroundLocationPermission() async {
    String locationPermission = '';
    try {
      final result = await platform
          .invokeMethod<String>('requestBackgroundLocationPermission');
      locationPermission = 'Permission : $result % .';
    } on PlatformException catch (e) {
      locationPermission = "Failed to get permission: '${e.message}'.";
    }

    setState(() {
      _locationPermission = locationPermission;
    });
  }

this would be the method you would invoke on the initstate

I am quite new to native, so the code quality may not be the best but it currently request permission as expected and would show a native toast . Hope this helps.

And referring to what you issued originally, it is most likely you did not handle what happens when user denies. Usually on testing u should do for denied and then check if app attempts to loop back or redirect user back, and forcing user to allow permission regardless in order to use that particular service.