flutter / flutter

Flutter makes it easy and fast to build beautiful apps for mobile and beyond
https://flutter.dev
BSD 3-Clause "New" or "Revised" License
166.65k stars 27.6k forks source link

[google_maps_flutter] App is lagging when initializing Google Maps #28493

Open ItsPierre opened 5 years ago

ItsPierre commented 5 years ago

I am just building the Google-Maps widget in my Stateful Widget. This will be opened with the Navigator via a Button click. But as soon, as I click, the app is lagging, while initializing the map. When removing the widget, everything is fine.

I tried to set a CircularProgressIndicator and then, setting the map-widget async, but running into the same problem, as soon, as it gets build. I also downloaded and ran the example app from the google-maps-package with the same lagging.

How should the map be initilized, that it is first loaded and then set without lagging?

Flutter Doctor

I am developing with Android Studio

[√] Flutter (Channel dev, v1.2.2, on Microsoft Windows [Version 10.0.17763.316], locale de-DE)
    • Flutter version 1.2.2 at S:\Development\Flutter\Flutter-SDK
    • Framework revision 007a415c2a (5 days ago), 2019-02-21 20:22:47 -0800
    • Engine revision f1f19bba8f
    • Dart version 2.2.0 (build 2.2.0-dev.2.1 c92d5ca288)

[√] Android toolchain - develop for Android devices (Android SDK version 28.0.3)
    • Android SDK at C:\Users\Pierre\AppData\Local\Android\sdk
    • Android NDK location not configured (optional; useful for native profiling support)
    • Platform android-28, build-tools 28.0.3
    • Java binary at: S:\Development\Android Studio\jre\bin\java
    • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1248-b01)
    • All Android licenses accepted.

[√] Android Studio (version 3.3)
    • Android Studio at S:\Development\Android Studio
    • Flutter plugin version 33.3.1
    • Dart plugin version 182.5215
    • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1248-b01)

[!] IntelliJ IDEA Ultimate Edition (version 2018.3)
    • IntelliJ at S:\Development\JetBrains\IntelliJ IDEA 2018.3.2
    X Flutter plugin not installed; this adds Flutter specific functionality.
    X Dart plugin not installed; this adds Dart specific functionality.
    • For information about installing plugins, see
      https://flutter.io/intellij-setup/#installing-the-plugins

[√] Connected device (1 available)
    • Android SDK built for x86 • emulator-5554 • android-x86 • Android 9 (API 28) (emulator)

! Doctor found issues in 1 category.
zoechi commented 5 years ago

Please add the output of flutter doctor -v.

zoechi commented 5 years ago

Have you tried with a release build? Debug builds are never representative for measuring performance.

ItsPierre commented 5 years ago

Please add the output of flutter doctor -v.

I will add the flutter doctor -v later

Have you tried with a release build? Debug builds are never representative for measuring performance.

Yes, I did try release builds, but it is still lagging, when initializing

ItsPierre commented 5 years ago

Please add the output of flutter doctor -v.

I added the flutter doctor. But I think, this is a general problem.

zoechi commented 5 years ago

But I think, this is a general problem.

It's not about possible errors flutter doctor might report. flutter doctor -v output is valuable information about the context where the problem reproduced for you.

realvotum commented 5 years ago

Same for me. It takes a while to build my StatelessWidget with google map inside SizedBox 300x300. Without google maps, the widget builds instantly, otherwise the delay for the StatelessWidget to appear is noticeable. This is my output:

[✓] Flutter (Channel stable, v1.2.1, on Mac OS X 10.14.3 18D109, locale pl-PL) • Flutter version 1.2.1 at /Users/mat/dev/flutter • Framework revision 8661d8aecd (5 weeks ago), 2019-02-14 19:19:53 -0800 • Engine revision 3757390fa4 • Dart version 2.1.2 (build 2.1.2-dev.0.0 0a7dcf17eb)

[✓] Android toolchain - develop for Android devices (Android SDK version 28.0.3) • Android SDK at /Users/mat/Library/Android/sdk • Android NDK location not configured (optional; useful for native profiling support) • Platform android-28, build-tools 28.0.3 • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1248-b01) • All Android licenses accepted.

[✓] iOS toolchain - develop for iOS devices (Xcode 10.1) • Xcode at /Applications/Xcode.app/Contents/Developer • Xcode 10.1, Build version 10B61 • ios-deploy 1.9.4 • CocoaPods version 1.5.3

[✓] Android Studio (version 3.3) • Android Studio at /Applications/Android Studio.app/Contents • Flutter plugin version 33.3.1 • Dart plugin version 182.5215 • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1248-b01)

[✓] Connected device (1 available) • Android SDK built for x86 • emulator-5554 • android-x86 • Android 8.1.0 (API 27) (emulator)

• No issues found!

JRR-OSU commented 5 years ago

Experiencing the same in my release builds. Quite a bit of jank.

I'm not sure how this one can easily be solved as embedding a native platform view as expensive as a Google Map view isn't going to be easy without hindering Flutter's UI performance.

eopeter commented 5 years ago

For now I added a delay in initState to load Google Maps:

Future.delayed(const Duration(milliseconds: 500), () { setState(() { _showGoogleMaps = true; }); });

danielcmm commented 5 years ago

Using this plugin really degrades the performance of the Widget tree for me... i had to put the map on a separate, isolate screen ;/

dshukertjr commented 4 years ago

@danielcmm How do you put a widget on a separate isolate?

devmgs commented 4 years ago

For now I added a delay in initState to load Google Maps:

Future.delayed(const Duration(milliseconds: 500), () { setState(() { _showGoogleMaps = true; }); });

Works nice. Good Workaround utill it is fixed.

Hixie commented 4 years ago

It would be good to study a timeline showing what is actually happening. Can someone who is seeing this run a profile build and collect a timeline of the behaviour?

It would also be good to see a short app that reproduces the problem, that would save us some time when we get around to debugging this.

deckerst commented 4 years ago

I tried to make a simple app to show the issue:

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

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final controller = PageController();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Column(
          children: [
            Expanded(
              child: PageView(
                controller: controller,
                children: [
                  Container(
                    decoration: BoxDecoration(
                      gradient: LinearGradient(stops: [0, 1], colors: [Colors.blue, Colors.transparent]),
                    ),
                  ),
                  MapPage(),
                ],
              ),
            ),
            RaisedButton(
              onPressed: () => controller.animateToPage(
                controller.page == 0 ? 1 : 0,
                duration: Duration(milliseconds: 350),
                curve: Curves.linear,
              ),
              child: Text('Switch page'),
            )
          ],
        ),
      ),
    );
  }
}

class MapPage extends StatefulWidget {
  @override
  _MapPageState createState() => _MapPageState();
}

class _MapPageState extends State<MapPage> with AutomaticKeepAliveClientMixin {
  bool _keepAlive = false;

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return SafeArea(
      child: ListView(
        children: [
          Row(
            children: [
              Checkbox(
                value: _keepAlive,
                onChanged: (v) {
                  _keepAlive = v;
                  updateKeepAlive();
                  setState(() {});
                },
              ),
              Text('Keep alive'),
            ],
          ),
          SizedBox(
            height: 300,
            child: GoogleMap(
              initialCameraPosition: CameraPosition(
                target: LatLng(37.42796133580664, -122.085749655962),
              ),
            ),
          ),
        ],
      ),
    );
  }

  @override
  bool get wantKeepAlive => _keepAlive;
}

The app shows a PageView that can switch to a page with a map. It highlights 3 different cases:

  1. first initialization: the very first time it goes to the map page, the app lags
  2. following initializations without keep alive: the map is reinitialized every time but it does not lag (the map is grey for a few frames during initialization, which is normal)
  3. following initializations with keep alive: actually the map is not reinitialized, so it's always ready and smooth.

Here's a timeline of the first initialization with lag: timeline_2020_1_18-1579319582040000.json.txt

Please note that it's the first time I check and export a timeline, so let me know if it's not what you expected...

kras95 commented 4 years ago

For now I added a delay in initState to load Google Maps: Future.delayed(const Duration(milliseconds: 500), () { setState(() { _showGoogleMaps = true; }); });

Works nice. Good Workaround utill it is fixed.

This didn't help for me, but not sure I'm doing it right.

I have a GoogleMap in a ListView.

If _showGoogleMaps is true, I return the GoogleMap() in the ListView, otherwise I return an empty Container(), when the 500 millisecond timeout is done, the map still lags when initiating.

Does anybody have other solutions? Is there any way to async load the map before it's actually displayed, or maybe load the map before reaching the map in the ListView(), because the moment the ListView() scrolls to the GoogleMap(), the app just freezes until the GoogleMap() is rendered. If the map renders once, scrolling back to it doesn't cause lags.

otopba commented 4 years ago

This can help https://github.com/flutter/plugins/pull/2479

shlima commented 4 years ago

The same issue

ndhbr commented 4 years ago

Any updates?

cohenadair commented 4 years ago

For now I added a delay in initState to load Google Maps:

Future.delayed(const Duration(milliseconds: 500), () { setState(() { _showGoogleMaps = true; }); });

Thanks for the suggestion. This is working for now. I used a FutureBuilder instead, though. For those who are curious:

  final Completer<GoogleMapController> _mapController = Completer();
  Future _mapFuture = Future.delayed(Duration(milliseconds: 250), () => true);

  Widget _buildMap() {
    LatLng currentLocation = LocationMonitor.of(context).currentLocation;

    return FutureBuilder(
      future: _mapFuture,
      builder: (context, snapshot) {
        if (!snapshot.hasData) {
          print("empty");
          return Empty();
        }

        return GoogleMap(
          initialCameraPosition: CameraPosition(
            target: currentLocation == null ? LatLng(0.0, 0.0) : currentLocation,
            zoom: currentLocation == null ? 0 : 15,
          ),
          onMapCreated: (GoogleMapController controller) {
            _mapController.complete(controller);
          },
          myLocationButtonEnabled: false,
          myLocationEnabled: true,
        );
      },
    );
  }
kf6gpe commented 4 years ago

FutureBuilder looks like a reasonable work-around, given the time it takes the map view under the plugin to initialize. I'm going to move this out to Stretch Goals at this point, as we don't currently have resources to work on optimizations here.

0x7061 commented 4 years ago

@cohenadair So basically with the Future builder you are postponing the map draw until the Navigator animation is completed?

cohenadair commented 4 years ago

@cohenadair So basically with the Future builder you are postponing the map draw until the Navigator animation is completed?

@codepushr That's the idea. Technically, it's not waiting for the Navigator to finish, it's just delaying the map draw 250 ms. As long as the delay is longer than the Navigator animation, everything should be fine.

You may be able to listen for the Navigator animation to finish, so the delay becomes irrelevant, but I didn't look into it, and the 250 ms delay works fine for what I'm trying to do.

DjangoOverloard commented 4 years ago

But even when the map is initialized, I still have a lot of lag when moving through the map. This experience is fine in native google maps app on any device, but even if I try to release build my app from flutter, any android device is showing a lot of lag. Not an issue on IOS though.

Xgamefactory commented 4 years ago

@cohenadair So basically with the Future builder you are postponing the map draw until the Navigator animation is completed?

@codepushr That's the idea. Technically, it's not waiting for the Navigator to finish, it's just delaying the map draw 250 ms. As long as the delay is longer than the Navigator animation, everything should be fine.

You may be able to listen for the Navigator animation to finish, so the delay becomes irrelevant, but I didn't look into it, and the 250 ms delay works fine for what I'm trying to do.

yes exactly it would be nice to able to handle route navigation finish like initState methods for example onRoutingFinished and after that initliazing google maps

xnio94 commented 4 years ago

hope this also helps:

 bool showMap = false;
 void initState() {
    super.initState();
    Future.delayed(const Duration(milliseconds: 250), () {
      setState(() {
        showMap= true;
      });
    });
  }

and in the build use :

 Stack(
   children:[
       if (showMap)  GoogleMap,
       Cover,
    ]
)

where GoogleMap is the GoogleMap Widget and Cover is something like this :

            IgnorePointer(
                  child: AnimatedOpacity(
                    curve: Curves.easeInCirc,
                    duration: Duration(milliseconds: 1000),
                    opacity: showMap ? 0 : 1,
                    child: Container(
                      color: Colors.blue,
                      child: Center(
                        child: Icon(
                          FontAwesomeIcons.spinner,
                          size: 60,
                          color: Colors.white.withAlpha(100),
                        ),
                      ),
                    ),
                  ),
                ),

this will add about 250ms before google map start initializing (this will smooth the navigator animation as many suggested) then it adds a one second transition to smooth the appearance of the map it worked well for my use case

0ttik commented 3 years ago
Related message > Thanks for the suggestion. This is working for now. I used a `FutureBuilder` instead, though. For those who are curious: > > ``` > final Completer _mapController = Completer(); > Future _mapFuture = Future.delayed(Duration(milliseconds: 250), () => true); > > Widget _buildMap() { > LatLng currentLocation = LocationMonitor.of(context).currentLocation; > > return FutureBuilder( > future: _mapFuture, > builder: (context, snapshot) { > if (!snapshot.hasData) { > print("empty"); > return Empty(); > } > > return GoogleMap( > initialCameraPosition: CameraPosition( > target: currentLocation == null ? LatLng(0.0, 0.0) : currentLocation, > zoom: currentLocation == null ? 0 : 15, > ), > onMapCreated: (GoogleMapController controller) { > _mapController.complete(controller); > }, > myLocationButtonEnabled: false, > myLocationEnabled: true, > ); > }, > ); > } > ```

I think more clean solution is not to w8 random 250ms but to resolve future at the post frame callback.

Smth like this:

class GodState extends State<God> {
  Completer<void> _initCompleter = Completer();

  @override
  onInitState() {
    WidgetsBinding.instance
        .addPostFrameCallback((_) => _initCompleter.complete());
  }

  FutureBuilder(
      future: _initCompleter.future,
      builder: (...) => ...
  );
}
cohenadair commented 3 years ago

I think more clean solution is not to w8 random 250ms but to resolve future at the post frame callback.

Smth like this:

class GodState extends State<God> {
  Completer<void> _initCompleter = Completer();

  @override
  onInitState() {
    WidgetsBinding.instance
        .addPostFrameCallback((_) => _initCompleter.complete());
  }

  FutureBuilder(
      future: _initCompleter.future,
      builder: (...) => ...
  );
}

Hi @0ttik,

Can you explain how this works a little more? The documentation states that

This callback is run during a frame, just after the persistent frame callbacks (which is when the main rendering pipeline has been flushed)

Does the latter part mean once all animation frames are finished? You wouldn't want the callback invoked part way through the navigation animation.

0ttik commented 3 years ago

Hi, @cohenadair!

You are right, in case of navigation animation it won't work. Although you can track it as well using [ModalRoute] (obtaining it with ModalRoute.of(context)) and then subscribing to animation state like this:

var modal = ModalRoute.of(context);
modal.animation.addStatusListener(/*dont forget to unsubscribe in this callback*/);

And you just trigger map loading after this callback fires with value you need.

Docs:

  1. https://api.flutter.dev/flutter/widgets/ModalRoute-class.html
  2. https://api.flutter.dev/flutter/animation/Animation-class.html

P.S. I found lots of ways to better control application flow connected with navigation when I've actually read [ModalRoute] class docs >_<

mellowcello77 commented 3 years ago

Finding the Google map too laggy to be useful in a list-view, will need to move it to a separate view for now. Above delays not working for me personally.

nikto-b commented 3 years ago

@mellowcello77, did You tried lite mode for a list-view usage?

mellowcello77 commented 3 years ago

@niktob560 I looked into it at the time, I think it was for Android only. I ended up using mapbox.

ItsPierre commented 3 years ago

@niktob560 I tried lite mode, but this did not solve the problem.

At the moment, the best workaround seems to be a FutureBuilder, that waits the 200 ms the page transition animation requires. At this time I just show a shimmer-loading effect. After this I initialize the GoogleMap.

Not perfect, but much better, than a lagging animation.

darshankawar commented 3 years ago

Verified this using a sample app on master version in debug mode and noticed the lag when map tries to open / initialize after directing through a route, as below:

https://user-images.githubusercontent.com/67046386/115868143-9db71c80-a459-11eb-8e1b-d8bd26a46d3a.mov

code sample ``` import 'dart:async'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Google Maps Demo', home: MyHome() ); } } class MyHome extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: Center( child: ElevatedButton( onPressed:() { Navigator.push( context, MaterialPageRoute(builder: (context) => MapSample()), ); }, child: Text('Tap') ) ) ); } } class MapSample extends StatefulWidget { @override State createState() => MapSampleState(); } class MapSampleState extends State { Completer _controller = Completer(); static final CameraPosition _kGooglePlex = CameraPosition( target: LatLng(37.42796133580664, -122.085749655962), zoom: 14.4746, ); static final CameraPosition _kLake = CameraPosition( bearing: 192.8334901395799, target: LatLng(37.43296265331129, -122.08832357078792), tilt: 59.440717697143555, zoom: 19.151926040649414); @override Widget build(BuildContext context) { return new Scaffold( body: GoogleMap( mapType: MapType.hybrid, initialCameraPosition: _kGooglePlex, onMapCreated: (GoogleMapController controller) { _controller.complete(controller); }, ), floatingActionButton: FloatingActionButton.extended( onPressed: _goToTheLake, label: Text('To the lake!'), icon: Icon(Icons.directions_boat), ), ); } Future _goToTheLake() async { final GoogleMapController controller = await _controller.future; controller.animateCamera(CameraUpdate.newCameraPosition(_kLake)); } } ```
flutter doctor -v ``` [✓] Flutter (Channel master, 2.2.0-11.0.pre.187, on Mac OS X 10.15.4 19E2269 darwin-x64, locale en-GB) • Flutter version 2.2.0-11.0.pre.187 at /Users/dhs/documents/fluttersdk/flutter • Upstream repository https://github.com/flutter/flutter.git • Framework revision 5526dcc24b (2 days ago), 2021-04-20 22:34:02 -0700 • Engine revision 610fd039ae • Dart version 2.14.0 (build 2.14.0-18.0.dev) [!] Xcode - develop for iOS and macOS • Xcode at /Applications/Xcode.app/Contents/Developer • Xcode 12.3, Build version 12C33 ! CocoaPods 1.9.3 out of date (1.10.0 is recommended). CocoaPods is used to retrieve the iOS and macOS platform side's plugin code that responds to your plugin usage on the Dart side. Without CocoaPods, plugins will not work on iOS or macOS. For more info, see https://flutter.dev/platform-plugins To upgrade see https://guides.cocoapods.org/using/getting-started.html#installation for instructions. [✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome [✓] VS Code (version 1.55.2) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.21.0 [✓] Connected device (3 available) • iPhone 12 Pro Max (mobile) • A5473606-0213-4FD8-BA16-553433949729 • ios • com.apple.CoreSimulator.SimRuntime.iOS-14-3 (simulator) • macOS (desktop) • macos • darwin-x64 • Mac OS X 10.15.4 19E2269 darwin-x64 • Chrome (web) • chrome • web-javascript • Google Chrome 90.0.4430.85 ! Doctor found issues in 1 category. ```
SergeyShustikov commented 3 years ago

Any updates?

martipello commented 3 years ago

still seeing long wait times for google maps to load

guccisekspir commented 3 years ago

Still there are no changes.

thealmamun commented 3 years ago

still facing the issue..

Adrek commented 2 years ago

Using this plugin really degrades the performance of the Widget tree for me... i had to put the map on a separate, isolate screen ;/

Please @danielcmm, could you share the code to generate an isolate screen?

yacineblr commented 2 years ago

Any news about this ?

Obada2020 commented 1 year ago

any updates ?

aleksandar-radivojevic commented 1 year ago

I also have issues with Google map inside ListView, it's lagging always when I try to scroll, not just when initialising.

kraa1055 commented 1 year ago

I also have issues with Google map inside ListView, it's lagging always when I try to scroll, not just when initialising.

@aleksandar-radivojevic Same here. Do you have any luck with this issue ?

kras95 commented 1 year ago

Perhaps a new issue should be opened for the ListView problem?

kraa1055 commented 1 year ago

Open new issue for scroll lags #135026