Flutter-geolocator: Reply already submitted crash

Created on 4 Sep 2020  ·  16Comments  ·  Source: Baseflow/flutter-geolocator

🐛 Bug Report

In the sample app LastKnownLocationExampleWidget deny the permission close and reopen the app I get the following exception and the app crashes.

E/AndroidRuntime(12916): Process: com.baseflow.geolocator_example, PID: 12916
E/AndroidRuntime(12916): java.lang.IllegalStateException: Reply already submitted
E/AndroidRuntime(12916):    at io.flutter.embedding.engine.dart.DartMessenger$Reply.reply(DartMessenger.java:139)
E/AndroidRuntime(12916):    at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler$1.success(MethodChannel.java:235)
E/AndroidRuntime(12916):    at com.baseflow.geolocator.MethodCallHandlerImpl.lambda$onGetLastKnownPosition$2(MethodCallHandlerImpl.java:149)
E/AndroidRuntime(12916):    at com.baseflow.geolocator.-$$Lambda$MethodCallHandlerImpl$tyxDngVVF7PcImb4PihgQ0FJ8po.onPositionChanged(Unknown Source:2)
E/AndroidRuntime(12916):    at com.baseflow.geolocator.location.-$$Lambda$FyNSKWRJ6K8pfIKflyK9zilTOk0.onSuccess(Unknown Source:4)
E/AndroidRuntime(12916):    at com.google.android.gms.tasks.zzn.run(Unknown Source:4)
E/AndroidRuntime(12916):    at android.os.Handler.handleCallback(Handler.java:883)
E/AndroidRuntime(12916):    at android.os.Handler.dispatchMessage(Handler.java:100)
E/AndroidRuntime(12916):    at android.os.Looper.loop(Looper.java:214)
E/AndroidRuntime(12916):    at android.app.ActivityThread.main(ActivityThread.java:7356)
E/AndroidRuntime(12916):    at java.lang.reflect.Method.invoke(Native Method)
E/AndroidRuntime(12916):    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
E/AndroidRuntime(12916):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)

Version: 6.0.0+3

Platform:

  • [ ] :iphone: iOS
  • [x] :robot: Android
android in progress needs more info bug

All 16 comments

@humazed thanks for the bug report. I kinda know where the error is coming from (and I can make it go away), but I cannot really reproduce it and according to the stack traces you send me this error should not happen in the first place.

So I am wondering if you can help me figure out what is causing this issue so I can solve it in the correct manner. So to give you some details, here is what I think is happening (based on the stack trace information and the steps you describe):

Step 1:

  1. You start the example App using the debugger (for the first time) and it will ask you for permissions;
  2. You deny permissions and the App shows you the message "User denied permission to access the device's location.";
  3. At this point you stop debugging and the App will close.

So far all is good.

Step 2:

  1. You start debugging the App again and the App will start;
  2. At this point the App should detect that permissions were denied and should show a popup again asking for permission (this is what's happening to me), however for you the App crashes with the above stack trace.

The crash happens because the App somehow enters the Java onRequestPermissionResult(int requestCode, String[] permissions, int[] grantResults) without having showed the permission popup and the permissions and grantResults arrays are empty. In my previous update (6.0.0+3) I detect that these parameters are empty and try to send back that permissions are denied (see code snippet below):

    @Override
    public boolean onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        if (requestCode != PERMISSION_REQUEST_CODE || permissions.length == 0 || grantResults.length == 0) {
            if (this.resultCallback != null) {
                this.resultCallback.onResult(LocationPermission.denied);
            }
            return false;
        }

        ....
  }

As you would guess sending back the permission status in this situation will trigger the exception you listed above, because information has already been send on the channel. This is strange because from the way I understand the steps you take this would be a fresh channel and really should be the first time information is send back on the channel.

What is also strange is that the onRequestPermissionResults is being called with empty permissions and grantResults parameters. I can prevent the exception from occurring by updating the code to:

    @Override
    public boolean onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        if (requestCode != PERMISSION_REQUEST_CODE) {
            if (this.resultCallback != null) {
                this.resultCallback.onResult(LocationPermission.denied);
            }
            return false;
        }

        if (permissions.length == 0 || grantResults.length == 0) {
            return false;
        }

        ....
  }

But I am afraid this will mask the real problem that is occurring. Can you help me finding the real problem by checking the following:

  1. Are these indeed the steps you took?
  2. Did you modify anything on the example code?
  3. Could you provide some information on the Android version and device you are using to run the example application?

Your help would be greatly appreciated.

Step 1:
run the app and deny permission.
Step 2:
Click the back button.
Step 3:
open the app and see the crash.

will send you a screen recording.

recording of the bug
https://drive.google.com/file/d/1dh2dIdiUWcOEysYX1Fh37kuCAc3-gTCl/view?usp=sharing

device:
pixel_2_XL_API_28 emulator

Hi,
I'm experiencing the same problem.

The steps to reproduce mine is simply: Allow location, then allow file and my apps crashed

Device:
Pixel_3a

D/AndroidRuntime(31159): Shutting down VM
E/AndroidRuntime(31159): FATAL EXCEPTION: main
E/AndroidRuntime(31159): Process: rest.clinimetric, PID: 31159
E/AndroidRuntime(31159): java.lang.RuntimeException: Failure delivering result ResultInfo{who=@android:requestPermissions:, request=35569, result=-1, data=Intent { act=android.content.pm.action.REQUEST_PERMISSIONS (has extras) }} to activity {rest.clinimetric/rest.clinimetric.MainActivity}: java.lang.IllegalStateException: Reply already submitted
E/AndroidRuntime(31159):    at android.app.ActivityThread.deliverResults(ActivityThread.java:4845)
E/AndroidRuntime(31159):    at android.app.ActivityThread.handleSendResult(ActivityThread.java:4886)
E/AndroidRuntime(31159):    at android.app.servertransaction.ActivityResultItem.execute(ActivityResultItem.java:51)
E/AndroidRuntime(31159):    at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
E/AndroidRuntime(31159):    at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
E/AndroidRuntime(31159):    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016)
E/AndroidRuntime(31159):    at android.os.Handler.dispatchMessage(Handler.java:107)
E/AndroidRuntime(31159):    at android.os.Looper.loop(Looper.java:214)
E/AndroidRuntime(31159):    at android.app.ActivityThread.main(ActivityThread.java:7356)
E/AndroidRuntime(31159):    at java.lang.reflect.Method.invoke(Native Method)
E/AndroidRuntime(31159):    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
E/AndroidRuntime(31159):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
E/AndroidRuntime(31159): Caused by: java.lang.IllegalStateException: Reply already submitted
E/AndroidRuntime(31159):    at io.flutter.embedding.engine.dart.DartMessenger$Reply.reply(DartMessenger.java:139)
E/AndroidRuntime(31159):    at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler$1.success(MethodChannel.java:235)
E/AndroidRuntime(31159):    at com.baseflow.geolocator.MethodCallHandlerImpl.lambda$onRequestPermission$0(MethodCallHandlerImpl.java:133)
E/AndroidRuntime(31159):    at com.baseflow.geolocator.-$$Lambda$MethodCallHandlerImpl$HgwoSKdIeyav7j8ZZJufnb77Lqo.onResult(Unknown Source:2)
E/AndroidRuntime(31159):    at com.baseflow.geolocator.permission.PermissionManager.onRequestPermissionsResult(PermissionManager.java:103)
E/AndroidRuntime(31159):    at io.flutter.embedding.engine.FlutterEnginePluginRegistry$FlutterEngineActivityPluginBinding.onRequestPermissionsResult(FlutterEnginePluginRegistry.java:664)
E/AndroidRuntime(31159):    at io.flutter.embedding.engine.FlutterEnginePluginRegistry.onRequestPermissionsResult(FlutterEnginePluginRegistry.java:364)
E/AndroidRuntime(31159):    at io.flutter.embedding.android.FlutterActivityAndFragmentDelegate.onRequestPermissionsResult(FlutterActivityAndFragmentDelegate.java:573)
E/AndroidRuntime(31159):    at io.flutter.embedding.android.FlutterActivity.onRequestPermissionsResult(FlutterActivity.java:602)
E/AndroidRuntime(31159):    at android.app.Activity.dispatchRequestPermissionsResult(Activity.java:8289)
E/AndroidRuntime(31159):    at android.app.Activity.dispatchActivityResult(Activity.java:8139)
E/AndroidRuntime(31159):    at android.app.ActivityThread.deliverResults(ActivityThread.java:4838)
E/AndroidRuntime(31159):    ... 11 more
I/Process (31159): Sending signal. PID: 31159 SIG: 9
Lost connection to device.

Hi @humazed, thank you for providing the reproduction steps, I think I have figured out the real problem that is occurring. I believe the error is happening because the permissions are requested multiple times while the first call is still being handled, which triggers a warning on the Android activity "W/Activity(24060): Can request only one set of permissions at a time", and results in the onRequestPermissionsResult being called with the empty permissions and grantResults parameters. This can for example happen when we directly call the getLastKnownPosition from the Flutter build method which can be triggered multiple times. Or as an example, I can reproduce the problem consistently using the following example code:

import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: MyHomePage(title: 'Geolocator #396'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState 
  extends State<MyHomePage>
  with WidgetsBindingObserver {

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void didChangeDependencies() {
    getCurrentPosition(forceAndroidLocationManager: true).then((position) => print('Start: $position'));
    super.didChangeDependencies();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    print(state);
    getCurrentPosition(forceAndroidLocationManager: true).then((position) => print('Resume: $position'));
  }

  @override
  Widget build(BuildContext context) {
   return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),// This trailing comma makes auto-formatting nicer for build methods.
    );
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }
}

The problem is that this only happens when you need to request for permissions. Remember that the getLastKnownPosition, getCurrentPosition and getPositionStream methods will automatically request permissions when applicable. The dilemma is that once you have already requested permission it is just fine to call the getLastKnownPosition (or one of the other position methods) multiple times. So I can handle this in three ways:

  1. Add a check to the onRequestPermissionResults which ignores the call when the permissions and grantResults parameters are empty;
  2. Make sure you can only call the getLastKnownPosition, getCurrentPosition and getPositionStream methods once if their is already an existing request running;
  3. Remove the automatic permission resolution from the getLastKnownPosition, getCurrentPosition and getPositionStream methods and leave it up to the developers to handle permissions.

At the moment I am feeling the most for the first solution, since the others will have other disadvantages or are really introducing huge breaking changes. I will implement the first solution now to make sure we have a workable solution. But I am interested in how you would feel about the different options?

Hi @humazed, @NindoK, I have just released version 6.0.0+4 of the geolocator plugin which should solve this issue using the 1st option I described in the comment above.

Thanks to the information from @humazed I was able to reproduce the problem and confirm that this change should fix the issue. You might however still see the warning "W/Activity(24060): Can request only one set of permissions at a time" which you can ignore.

Let me know if this will solve the issue for you, in the meantime I will keep this issue open.

Thanks it fixed it!

Hi @NindoK, thank you for testing and confirming the problem is fixed. Will close the issue now.

I can also confirm it is fixed thank you

On Tue, Sep 8, 2020 at 7:49 PM Maurits van Beusekom <
[email protected]> wrote:

Closed #513 https://github.com/Baseflow/flutter-geolocator/issues/513.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/Baseflow/flutter-geolocator/issues/513#event-3742130966,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/ACDNYJVA5E6SXFCN6LAIUITSEZVBHANCNFSM4QZMGFKQ
.

I am still experiencing this crash in 6.0.0+4, as the package clashes with a different permission handler's request. This makes sense according to the explanation here. Do you think it'd be possible to add a manual option to disable the automatic permission resolution of the methods?
Thank you!

Hi @xJon, could you maybe provide a stack trace or some logging that shows the problem that is occurring? Can you maybe provide some information on the other plugin you are using that you mentioned is clashing with the geolocator?

We have seen similar issues with other plugins that don't handle the Android onRequestPermissionResult call back correctly, I am thinking this might be the problem here but I would like to investigate and see if I can help out.

Hi @mvanbeusekom, yes this is my log:

    java.lang.RuntimeException: Failure delivering result ResultInfo{who=@android:requestPermissions:, request=109, result=-1, data=Intent { act=android.content.pm.action.REQUEST_PERMISSIONS (has extras) }} to activity {com.gather/com.gather.MainActivity}: java.lang.IllegalStateException: Reply already submitted
        at android.app.ActivityThread.deliverResults(ActivityThread.java:3699)
        at android.app.ActivityThread.handleSendResult(ActivityThread.java:3742)
        at android.app.ActivityThread.-wrap16(ActivityThread.java)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1393)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loop(Looper.java:148)
        at android.app.ActivityThread.main(ActivityThread.java:5417)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
     Caused by: java.lang.IllegalStateException: Reply already submitted
        at io.flutter.embedding.engine.dart.DartMessenger$Reply.reply(DartMessenger.java:139)
        at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler$1.error(MethodChannel.java:240)
        at com.baseflow.geolocator.MethodCallHandlerImpl.lambda$onGetLastKnownPosition$3(MethodCallHandlerImpl.java:150)
        at com.baseflow.geolocator.-$$Lambda$MethodCallHandlerImpl$OCefduhIkqAV59oMDlIQqmk7z8g.onError(lambda)
        at com.baseflow.geolocator.location.GeolocationManager.lambda$handlePermissions$2(GeolocationManager.java:128)
        at com.baseflow.geolocator.location.-$$Lambda$GeolocationManager$boCQNh-1HzLRO8yyNdeq6cg70Yc.onResult(lambda)
        at com.baseflow.geolocator.permission.PermissionManager.onRequestPermissionsResult(PermissionManager.java:161)
        at io.flutter.embedding.engine.FlutterEnginePluginRegistry$FlutterEngineActivityPluginBinding.onRequestPermissionsResult(FlutterEnginePluginRegistry.java:664)
        at io.flutter.embedding.engine.FlutterEnginePluginRegistry.onRequestPermissionsResult(FlutterEnginePluginRegistry.java:364)
        at io.flutter.embedding.android.FlutterActivityAndFragmentDelegate.onRequestPermissionsResult(FlutterActivityAndFragmentDelegate.java:573)
        at io.flutter.embedding.android.FlutterActivity.onRequestPermissionsResult(FlutterActivity.java:602)
        at android.app.Activity.dispatchRequestPermissionsResult(Activity.java:6553)
        at android.app.Activity.dispatchActivityResult(Activity.java:6432)
        at android.app.ActivityThread.deliverResults(ActivityThread.java:3695)
        at android.app.ActivityThread.handleSendResult(ActivityThread.java:3742) 
        at android.app.ActivityThread.-wrap16(ActivityThread.java) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1393) 
        at android.os.Handler.dispatchMessage(Handler.java:102) 
        at android.os.Looper.loop(Looper.java:148) 
        at android.app.ActivityThread.main(ActivityThread.java:5417) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616) 

I made sure I am using the latest version of the package.
The other package I am using is permission_handler.

It is odd though, as I am unable to reproduce this issue continuously; I re-added now the code which seemed to cause the issue, and I don't experience the crash.
I would also like to point out that having a feature to disable the automatic permission resolution would be useful for apps which prefer to request certain permissions when first loading / on the splash screen, or when calling getCurrentPosition() and then getLastKnownPosition() (which would otherwise send 2 separate permission requests).

There should be no need to disable the automatic permission resolution since the geolocator will first check if permissions have been granted / disabled before taking any action and request permission. Meaning if you already took care of handling permissions the geolocator would simply skip the permission resolution.

Could it be that you were running an older version of the geolocator plugin and re-adding the code made sure you updated to the latest version of the geolocator? Maybe you ran the flutter pub upgrade in between?

I could've swore I ran flutter pub upgrade beforehand too, but maybe I didn't. I will update you if the issue arises again.

I don't fully understand what you mean with the automatic permission resolution, as currently if the user denies my first request when the app launches (using the permission_handler package), they will receive another request when getCurrentPosition() runs and another one when getLastKnownPosition() runs (assuming they deny all of them).

@xJon What he means is that if a user has "always allowed" permissions or "denied (dont ask me again)" permission, the plugin will not make a request to Android to display the permissions dialog.

The reason you user gets a request is probably because they allowed or denied their permission temporarily (or "just this once").

@ianendboss I see. Thank you for the explanation!

Was this page helpful?
0 / 5 - 0 ratings