15

I'm experiencing the following issue: My Flutter app uses a GoogleMap. The map loads just fine initially. However, if I put the app into the background and resume a while later, the map stays blank. The Google logo still shows, like it happens when the API key isn't specified. My polygon overlay doesn't show up, either.

The behavior is not reliably repruducable. Sometimes, the map loads fine after the app had been in the background for hours, sometimes the map is blank after minutes. So far, I have only seen this behavior on Android.

There are no specific log outputs that indicate an error.

Any ideas how to fix/work around this?

I filed an issue with screenshot here: https://github.com/flutter/flutter/issues/40284

EDIT 1: I was able to reproduce this with a GoogleMap as root widget and also without any polygon/feature overlay. Also, I found that wildly zooming in at some point 'reanimates' the map (suddenly the map becomes visible again). Is this maybe a known issue with the underlying Android Google Maps SDK?

EDIT 2: I found that the map is still reacting (e.g. tap/gesture listeners still trigger). Also, the map isn't really empty, it just becomes translucent, so the screen displays whatever widget is behind the map.

jbxbergdev
  • 860
  • 1
  • 10
  • 23
  • This might be a stupid question, but are you able to zoom out the map. From the screenshot seems to be zoomed to the "ground" level. – danypata Dec 17 '19 at 12:31
  • There's no such thing as a stupid question. :-) Yes, It tried zooming, it doesn't work. Also, the base map is a satellite map, so it would show at least some color. – jbxbergdev Dec 17 '19 at 12:34

8 Answers8

25

I discovered that if you tap a marker or change the style the map re-renders

class TheWidgetThatHasTheMap with WidgetsBindingObserver {

   //...your code

    @override
    void didChangeAppLifecycleState(AppLifecycleState state) {
        if (state == AppLifecycleState.resumed) {
            controller.setMapStyle("[]");
        }
    }
}
Mattia Vitturi
  • 251
  • 3
  • 3
  • Ive tried using `GoogleMapController` controller in `onMapCreated` but it doesnt have `setMapStyle` method – kashlo Apr 16 '20 at 17:34
  • 1
    @kashlo If your controller is defined as Completer _controller = Completer(); then you need to retrieve it first with final GoogleMapController controller = await _controller.future;. setMapStyle will show up. – Mario Orozco Apr 30 '20 at 23:49
  • Any idea what to do when the map is inside a Tab? didChangeAppLifecycleState is not called in my Tab, only in my HomeWidget (which contains the Tab) – Thomas Nov 15 '20 at 13:48
  • @Thomas I also had the same issue inside the tab and I solved it by using the below code Try to use this and let me know if it works for you or not. Code: SystemChannels.lifecycle.setMessageHandler((msg){ debugPrint('SystemChannels> $msg'); if(msg==AppLifecycleState.resumed.toString())setState((){ _mapsController.setMapStyle("[]"); }); }); – Vajani Kishan Nov 25 '20 at 07:02
  • @VajaniKishan I will try that. Did you put it in initState() of the Tab itself ? – Thomas Nov 26 '20 at 14:56
  • @Thomas no outside of the initState() block – Vajani Kishan Nov 27 '20 at 06:57
3

Not a solution to the core problem, but I was able to work around this bug by creating a fork of the plugins project and modifying GoogleMapController.java as follows:

@Override
  public void onActivityResumed(Activity activity) {
    if (disposed || activity.hashCode() != registrarActivityHashCode) {
      return;
    }
    mapView.onResume();
    // Workaround for https://github.com/flutter/flutter/issues/40284
    // This apparently forces a re-render of the map.
    if (googleMap != null) {
      googleMap.setMapType(googleMap.getMapType());
    }
  }

Now, on every resume event, the map will be re-rendered.

jbxbergdev
  • 860
  • 1
  • 10
  • 23
  • This Solution Worked for for sometime,then after setting the app in for and back ground for sevral times, the issue re appear – Emam Oct 15 '20 at 17:09
3

I tried something & it seems to be working!

Step 01, Implement WidgetsBindingObserver for related class's State class as follows, i.e:

class MainScreenState extends State<MainScreen> with WidgetsBindingObserver {....

Step 02, Override didChangeAppLifecycleState method i.e:

@override
  Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
    super.didChangeAppLifecycleState(state);
    switch (state) {
      case AppLifecycleState.inactive:
        print('appLifeCycleState inactive');
        break;
      case AppLifecycleState.resumed:
        print('appLifeCycleState resumed');
        break;
      case AppLifecycleState.paused:
        print('appLifeCycleState paused');
        break;
      case AppLifecycleState.detached:
        print('appLifeCycleState detached');
        break;
    }
  }

Step 03 add this for init state

WidgetsBinding.instance!.addObserver(this);

Step 04 Step 4 should be as follows

//onMapCreated method
  void onMapCreated(GoogleMapController controller) {
    controller.setMapStyle(Utils.mapStyles);
    _controller.complete(controller);
  }
// lifecycle
  @override
  Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
    super.didChangeAppLifecycleState(state);
    switch (state) {
      case AppLifecycleState.inactive:
        print('appLifeCycleState inactive');
        break;
      case AppLifecycleState.resumed:
        **//Add These lines**
        final GoogleMapController controller = await _controller.future;
        onMapCreated(controller);
        print('appLifeCycleState resumed');
        break;
      case AppLifecycleState.paused:
        print('appLifeCycleState paused');
        break;
      case AppLifecycleState.detached:
        print('appLifeCycleState detached');
        break;
    }
  }
gsm
  • 2,348
  • 17
  • 16
  • Thanks.... Do you have an example using `Flutter Hooks`? – I AM Sep 07 '21 at 07:09
  • no, but this link has. https://medium.com/flutter-community/flutter-hooks-say-goodbye-to-statefulwidget-and-reduce-boilerplate-code-8573d4720f9a – gsm Sep 07 '21 at 11:10
3

When dealing with a stateful widget, put the code below in your code as shown below

class MainScreenState extends State<MainScreen> with WidgetsBindingObserver 
 {....


  @override
  void initState() {
     super.initState();
     WidgetsBinding.instance!.addObserver(this);
     ...    // Other codes here 
  }


  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
       if (state == AppLifecycleState.resumed) {
         mapController!.setMapStyle("[]");
       }
    }
 }

Then you can add the code below in the state widget

Paul Kumchi
  • 211
  • 4
  • 7
2

Another simpler way to implement solution with setMapStyle within statefull widget of map widget. No need to change anything else: Import flutter services:

import 'package:flutter/services.dart';

Code:

  SystemChannels.lifecycle.setMessageHandler((msg) {
      if (msg == AppLifecycleState.resumed.toString()) {
        mapController.setMapStyle("[]");
      }
    });

"mapController" here is the instance of Google map controller you named somewhere in your code. In my case it is like this:

  GoogleMapController _mapController;
  GoogleMapController get mapController => _mapController;
Elmar
  • 2,235
  • 24
  • 22
1
   if you facing this problem in 2022 also  add this line above your class

class YourClass extends StatefulWidget with WidgetsBindingObserver

Completer<GoogleMapController> controller = Completer();
    
          @override
            void dispose() {
    
              WidgetsBinding.instance!.removeObserver(this);
            
              super.dispose();
            }
              @override
              void didChangeAppLifecycleState(AppLifecycleState state) async {
                  super.didChangeAppLifecycleState(state);
                print('\n\ndidChangeAppLifecycleState');
             
                    if (state == AppLifecycleState.resumed) {
                        final GoogleMapController controller1 = await controller.future;
                        controller1.setMapStyle('[]');
                }
                
              }
        
        
          void initState() {                             
        
            super.initState();
              
            WidgetsBinding.instance!.addObserver(this);
          }
benten
  • 696
  • 7
  • 18
0

Another temporary fix that doesn't required forking the plugins, building, etc.
Add a didChangeAppLifecycleState implemented via WidgetsBindingObserver to your Widget and make the GoogleMap widget rebuild with a state change.

סטנלי גרונן
  • 2,917
  • 23
  • 46
  • 68
MattyBoy
  • 25
  • 5
0

In my case the map threw a black screen when called setState, the below solution solved my problem.

return SingleChildScrollView(
  physics: const NeverScrollableScrollPhysics(),
  child:SizedBox(
    width: MediaQuery.of(context).size.width,
    height: MediaQuery.of(context).size.height,
    child: //Your GoogleMap here,
  ),
);
Deekshith Xetty
  • 224
  • 3
  • 11