简体   繁体   中英

Using Google Maps in flutter for both mobile and web

I'm working on an flutter application that should have a common codebase for web and mobile.

My app will have a google map and as far as I've seen there's not a single package to satisfy all platforms.

google_maps_flutter - seems to work only for mobile (IOS / Android)
google_maps_flutter_web - seems to work only for web

So most probably I have to create two separate MapWidgets, one for the web and one for mobile using these separate packages.

For mobile:

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

class MapSample extends StatefulWidget {
  MapSample({Key? key}) : super(key: key);

  @override
  State<MapSample> createState() => MapSampleState();
}

class MapSampleState extends State<MapSample> {
  final Completer<GoogleMapController> _controller = Completer();

  static const CameraPosition _kGooglePlex = CameraPosition(
    target: LatLng(37.42796133580664, -122.085749655962),
    zoom: 14.4746,
  );

  @override
  Widget build(BuildContext context) {
    return GoogleMap(
      mapType: MapType.hybrid,
      initialCameraPosition: _kGooglePlex,
      onMapCreated: (GoogleMapController controller) {
        _controller.complete(controller);
      },
    );
  }
}

For the web, it's a bit more complicated, it seems that google_maps_flutter_web isn't actually an usable version, from what I understand, (correct me if I'm wrong) and it actually uses another package that's not developed by the flutter team google_maps 6.0.0 .

The objective of google_maps_flutter_web probably is to have the same api as google_maps_flutter ( google_maps_flutter_platform_interface ) and use it seamlessly, but I couldn't really find an example of how to use it...

How should I go about this? Any change I'm mistaken about google_maps_flutter_web and it actually works? Or I should just try to use google_maps which actually works for the web and just switch widgets based on kIsWeb ?

Eventually I found a workaround using google_maps and this answer as inspiration:

  1. Abstract MapWidget
    import 'package:client_ojp4danube/map/map_widget_stub.dart'
        if (dart.library.html) 'package:client_ojp4danube/map/map_web_widget.dart'
        if (dart.library.io) 'package:client_ojp4danube/map/map_widget.dart';
    
    import 'package:flutter/material.dart';
    
    abstract class MapWidget extends StatefulWidget {
      factory MapWidget() => getMapWidget();
    }
  1. WebMap widget that uses google_maps:
    import 'dart:html';
    
    import 'package:client_ojp4danube/map/abstract_map_widget.dart';
    import 'package:flutter/cupertino.dart';
    import 'package:google_maps/google_maps.dart';
    import 'dart:ui' as ui;
    
    Widget getMap() {
      String htmlId = "7";
    
      // ignore: undefined_prefixed_name
      ui.platformViewRegistry.registerViewFactory(htmlId, (int viewId) {
        final myLatlng = new LatLng(30.2669444, -97.7427778);
    
        final mapOptions = new MapOptions()
          ..zoom = 8
          ..center = new LatLng(30.2669444, -97.7427778);
    
        final elem = DivElement()
          ..id = htmlId
          ..style.width = "100%"
          ..style.height = "100%"
          ..style.border = 'none';
    
        final map = GMap(elem, mapOptions);
    
        Marker(MarkerOptions()
          ..position = myLatlng
          ..map = map
          ..title = 'Hello World!');
    
        return elem;
      });
    
      return HtmlElementView(viewType: htmlId);
    }
    
    class WebMap extends StatefulWidget implements MapWidget {
      WebMap({Key? key}) : super(key: key);
    
      @override
      State<WebMap> createState() => WebMapState();
    }
    
    class WebMapState extends State<WebMap> {
      @override
      Widget build(BuildContext context) {
        return getMap();
      }
    }
    
    MapWidget getMapWidget() {
      print("Intra in get map web ");
      return WebMap();
    }
  1. Mobile Map Widget
    import 'dart:async';
    
    import 'package:client_ojp4danube/map/abstract_map_widget.dart';
    import 'package:flutter/material.dart';
    import 'package:google_maps_flutter/google_maps_flutter.dart';
    
    class MobileMap extends StatefulWidget implements MapWidget {
      MobileMap({Key? key}) : super(key: key);
    
      @override
      State<MobileMap> createState() => MobileMapState();
    }
    
    class MobileMapState extends State<MobileMap> {
      final Completer<GoogleMapController> _controller = Completer();
    
      static const CameraPosition _kGooglePlex = CameraPosition(
        target: LatLng(37.42796133580664, -122.085749655962),
        zoom: 14.4746,
      );
    
      @override
      Widget build(BuildContext context) {
        return GoogleMap(
          mapType: MapType.hybrid,
          initialCameraPosition: _kGooglePlex,
          onMapCreated: (GoogleMapController controller) {
            _controller.complete(controller);
          },
        );
      }
    }
    
    MapWidget getMapWidget() {
      return MobileMap();
    }
  1. getMapWidget - stub
    import 'package:client_ojp4danube/map/abstract_map_widget.dart';
    
    // Created because importing dart.html on a mobile app breaks the build
    MapWidget getMapWidget() => throw UnsupportedError(
        'Cannot create a map without dart:html or google_maps_flutter');
  1. Actually using the abstract widget that will return the widget suited for the platform
import 'package:client_ojp4danube/map/abstract_map_widget.dart';


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

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Expanded(child: MapWidget()),
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

EDIT: A new official plugin has been released: https://pub.dev/packages/google_maps_flutter_web . It already works with the existing google_maps_flutter plugin, just add your api script in the web/index.html.

As the user exilonX suggested, the current way (Apr '22) to use Google Maps on both Flutter web and mobile, is to load the library dynamically based on the device. However, his answer lacks of some important details. It took me almost 1h to make his work working, therefore I'm sharing here a clearer and more organized solution, hopefully it'll save you some time (I couldn't edit his answer due to long edit queue).


Folder structure:

\widget
   \map_widget.dart
   \web_map_widget.dart
   \mob_map_widget.dart
   \map_widget_stub.dart

MapWidget:

In the file map_widget.dart you'll have the abstract MapWidget:

import 'package:flutter/material.dart';

import 'map_widget_stub.dart'
    if (dart.library.html) 'web_map_widget.dart'
    if (dart.library.io) 'mob_map_widget.dart';

abstract class MapWidget extends StatefulWidget {
  factory MapWidget() => getMapWidget();
}

NOTE: the only semicolumn you need on the conditional import is at the end of the second if.


Web MapWidget:

This file will contain the google map shown on web:

import 'dart:html';
import 'dart:ui' as ui;

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

import 'map_widget.dart';

MapWidget getMapWidget() => WebMap();

class WebMap extends StatefulWidget implements MapWidget {
  WebMap({Key? key}) : super(key: key);

  @override
  State<WebMap> createState() => WebMapState();
}

class WebMapState extends State<WebMap> {
  @override
  Widget build(BuildContext context) {
    final String htmlId = "map";

    // ignore: undefined_prefixed_name
    ui.platformViewRegistry.registerViewFactory(htmlId, (int viewId) {
      final mapOptions = MapOptions()
        ..zoom = 15.0
        ..center = LatLng(35.7560423, 139.7803552);

      final elem = DivElement()..id = htmlId;
      final map = GMap(elem, mapOptions);

      map.onCenterChanged.listen((event) {});
      map.onDragstart.listen((event) {});
      map.onDragend.listen((event) {});

      Marker(MarkerOptions()
        ..position = map.center
        ..map = map);

      return elem;
    });
    return HtmlElementView(viewType: htmlId);
  }
}

Here you can find more details about the web implementation.


Mobile MapWidget:

This file contains the implementation for mobile (android/ios):

import 'dart:async';

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

import 'map_widget.dart';

MapWidget getMapWidget() => MobileMap();

class MobileMap extends StatefulWidget implements MapWidget {
  MobileMap({Key? key}) : super(key: key);

  @override
  State<MobileMap> createState() => MobileMapState();
}

class MobileMapState extends State<MobileMap> {
  final Completer<GoogleMapController> _controller = Completer();

  static const CameraPosition _kFalentexHouse =
      CameraPosition(target: LatLng(44.497858579692135, 11.336362079086408));

  @override
  Widget build(BuildContext context) {
    return GoogleMap(
      mapType: MapType.hybrid,
      initialCameraPosition: _kFalentexHouse,
      onMapCreated: (GoogleMapController controller) {
        _controller.complete(controller);
      },
    );
  }
}

Stub

Finally, you need a stub:

import 'map_widget.dart';

//the error is shown in case of wrong version loaded on wrong platform
MapWidget getMapWidget() => throw UnsupportedError(
    'Cannot create a map without dart:html or google_maps_flutter');

Usage

Now you can use the widget MapWidget as a normal widget:

Scaffold(
      body: Center(
        child: SizedBox(
          height: 300,
          width: 300,
          child: MapWidget(),
        ),
      ),
    );

NOTE: in order to make the map work you need to set it up with the key. See the official library documentation for mobile and web .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM