web analytics

Flutter : การใช้ Google Maps ใน Flutter

สวัสดีผู้อ่านครับ มาลองลองเล่น library ต่างๆใน Flutter กันต่อ บล็อกนี้เป็นคิวของ Google Maps ครับ ซึ่งใน Flutter ก็มี Google Maps Library ให้ใช้งานกัน ความสามารถก็ค่อนข้างครอบคลุม แต่ว่าในขณะที่เขียนบล็อกนี้อยู่นี้ google maps flutter เป็นเวอชัน 0.5.7 และยังอยู่ใน Developer Preview อยู่ด้วย นั่นหมายความว่าอาจจะยังมีบัคและอาจการเปลี่ยนแปลงอีกพอสมควรในเวอชันถัดๆไป โอเค มาลองเล่นกันเล้ยย

เปิดใช้งาน Maps SDK

ก่อนอื่นเปิดใช้งาน Google Maps API กันก่อน โดยไปที่ https://console.cloud.google.com/

ซึ่งให้เปิดใช้งาน 2 อัน คือ Maps SDK for Android และ Maps SDK for iOS

กดปุ่ม enable

จากนั้นไปที่ Credential จะมี API key อยู่ ให้ copy เอาไว้

เพิ่ม API key ในโปรเจค

พอได้ api key แล้วก็ไปที่ android/app/src/main/AndroidManifest.xml เอา API key ไปใส่ไว้ในนี้ โดยเพิ่มเป็น <meta-data> และเพิ่ม permission สำหรับใช้งาน location

<manifest ...

   <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

  <application ...
    <meta-data android:name="com.google.android.geo.API_KEY"
               android:value="YOUR KEY HERE"/>

สำหรับ iOS จะเพิ่ม API key ที่ ios/Runner/AppDelegate.swift

import UIKit
import Flutter
import GoogleMaps

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?
  ) -> Bool {
    GMSServices.provideAPIKey("YOUR KEY HERE")
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

เพิ่ม permission ที่ ios/Runner/Info.plist

<plist version="1.0">
<dict>
	...
	<key>NSLocationWhenInUseUsageDescription</key>
	<string>This app needs your location to test the location feature of the Google Maps plugin.</string>
	<key>io.flutter.embedded_views_preview</key>
	<true/>
</dict>
</plist>

เพิ่ม Dependencies ใน Flutter

จากนั้นเพิ่ม dependencies ใน Flutter โดยตัวอย่างนี้ผมจะใช้งาน 3 ตัว คือ google maps อันนี้แน่นอนอยู่แล้ว location สำหรับรับค่าตำแหน่งของเรา ส่วน url launcher เอาไว้เปิดแอป google maps จากแอปของเรา

โดยเพิ่มที่ไฟล์ pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
  ..
  google_maps_flutter: 0.5.7
  location: 2.3.5
  url_launcher: 5.0.2

การใช้งาน Google Maps

มาลองใช้ google maps กันเลย ก่อนอื่น import package google maps

import 'package:google_maps_flutter/google_maps_flutter.dart';

จากนั้นเพิ่ม Widget ที่ชื่อว่า GoogleMap โดยกำหนด mapType และ camera ให้มัน อีกตัวที่สำคัญคือ GoogleMapController ซึ่งในที่นี้เขาจะใช้ Completer class มันก็คือ class ที่เอาไว้สร้าง Future อีกที

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

  @override
  Widget build(BuildContext context) {
    return  Scaffold(
      body: GoogleMap(
        mapType: MapType.normal,
        initialCameraPosition: CameraPosition(
          target: LatLng(13.7650836, 100.5379664),
          zoom: 16,
        ),
        onMapCreated: (GoogleMapController controller) {
          _controller.complete(controller);
        },
      )
    );
  }

ลองรัน จะได้ map แล้วล่ะ

หากใครที่รันใน emulator แล้ว error เป็นไปได้ว่า emulator นั้นไม่มี google play หรือไม่ก็ google play service เวอชันเก่าเกินไป อ่านวิธีอัพเดทได้ที่ลิงค์ข้างล่าง

เกร็ดเล็กๆเพิ่มอีกนิด คือ mapType มีให้เลือก 4 แบบ

  /// Do not display map tiles.
  MapType.none

  /// Normal tiles (traffic and labels, subtle terrain information).
  MapType.normal

  /// Satellite imaging tiles (aerial photos)
  MapType.satellite

  /// Terrain tiles (indicates type and height of terrain)
  MapType.terrain

  /// Hybrid tiles (satellite images with some labels/overlays)
  MapType.hybrid

ตัวอย่าง mapType แต่ละแบบ

ค่าการ zoom ของ camera จะมีตั้งแต่ 0-20 โดยยิ่งเลขมากก็ยิ่งซูมมาก

เราสามารถกำหนดให้มีปุ่ม my location ได้ด้วย แค่กำหนด myLocationEnabled

GoogleMap(
          myLocationEnabled: true,
          ..

การใช้งาน Location

ถ้าเราอยากรู้ตำแหน่งปัจจุบันของเราล่ะ มาลองใช้งาน library location กัน
ให้ import location เข้ามาก่อน

import 'package:location/location.dart';

ประกาศตัวแปร LocationData

class MyMapPageState extends State<MapSample> {
  LocationData currentLocation;
  ..

ดึงตำแหน่ง location โดยใช้ location.getLocation() ซึ่งต้องระวังกรณีไม่มี permission ด้วย

  Future<LocationData> getCurrentLocation() async {
    Location location = Location();
    try {
      return await location.getLocation();
    } on PlatformException catch (e) {
      if (e.code == 'PERMISSION_DENIED') {
        // Permission denied
      }
      return null;
    }
  }

เขียน method สำหรับนำ currentLocation มาใช้กับ Google Maps
วิธีการคือ เอา Lat Lng มาใส่ใน CameraPosition แล้วกำหนด newCameraPosition ผ่าน GoogleMapController

  Future _goToMe() async {
    final GoogleMapController controller = await _controller.future;
    currentLocation = await getCurrentLocation();
    controller.animateCamera(CameraUpdate.newCameraPosition(
        CameraPosition(
          target: LatLng(
              currentLocation.latitude,
              currentLocation.longitude),
          zoom: 16,
        )));
  }

เพิ่มปุ่มให้ กดแล้วเรียกใช้ method ที่เราทำไว้เมื่อกี้

 @override
  Widget build(BuildContext context) {
    return Scaffold(
      ..
      floatingActionButton: FloatingActionButton.extended(
        onPressed: _goToMe,
        label: Text('My location'),
        icon: Icon(Icons.near_me),
      ),
    );
  }

ลองรันก็จะมีปุ่มเพิ่มขึ้นมาแล้ว โดยเริ่มต้นผมกำหนด Lat Lng ที่อนุเสาวรีย์ชัยฯ

จากนั้นกดที่ปุ่ม My Location แอปจะถามหา permission

หากอนุญาต แอปก็จะเปลี่ยนมุมกล้องไปยังตำแหน่งที่เรากำหนดไว้แล้ว

ซึ่งตัว CameraUpdate ก็มีลูกเล่นพอสมควร เช่นหากเราอยากแค่เปลี่ยนตำแหน่ง แต่ระดับซูมเท่าเดิมเราก็ใช้ CameraUpdate.newLatLng ก็ได้ ตัวอย่างนี้ผมจะให้กดแล้วจะแสดงแผนที่สนามบินสุวรรณภูมิ


  Future _goToSuwannabhumiAirport() async {
    final GoogleMapController controller = await _controller.future;
    currentLocation = await getCurrentLocation();
    controller
        .animateCamera(CameraUpdate.newLatLng(LatLng(13.6900043, 100.7479237)));
  }

เพิ่มไอคอนปุ่มไปที่ AppBar สำหรับกดแล้วแสดงแผนที่สนามบิน

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("My Map"), actions: <Widget>[
        IconButton(
            icon: Icon(Icons.airplanemode_active),
            onPressed: _goToSuwannabhumiAirport)
      ]),
      ..
  }

ลองเล่นอีกสักอัน เพิ่มอีกปุ่ม โดยกดแล้วจะขยายออกให้เห็น กรุงเทพทั้งหมด

  Future _zoomOutToBangkok() async {
    final GoogleMapController controller = await _controller.future;
    currentLocation = await getCurrentLocation();
    controller
        .animateCamera(CameraUpdate.newLatLngZoom(LatLng(13.6846021,100.5883304),10));
  }

เพิ่มปุ่มอีกอัน

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("My Map"), actions: <Widget>[
        IconButton(
            icon: Icon(Icons.home),
            onPressed: _zoomOutToBangkok),
    ..
  }

การใส่ Marker

ปกติในแผนที่ การใช้งานจริงมักจะมีจุดแสดงตำแหน่งด้วย จะเป็นรูปหมุด หรือไอคอนต่างๆ ซี่งเราเรียกว่า Marker การใช้งานง่ายมาก โดย GoogleMap Widget มี field ให้ใส่ markers อยู่แล้วโดยกำหนดเป็น Set ซึ่ง marker แต่ละตัวเราต้องกำหนด id และ Lat Lng ให้มัน แถมยังใส่ชื่อ รายละเอียดได้อีกด้วย

GoogleMap(
          ..
          markers: {
            Marker(
                markerId: MarkerId("1"),
                position: LatLng(13.6900043, 100.7479237),
                infoWindow: InfoWindow(title: "สนามบินสุวรรณภูมิ", snippet: "สนามบินนานาชาติของประเทศไทย")),
          },
        )

การส่งตำแหน่งไปเปิดในแอป Google Maps

ในหลายๆครั้งแอปของเราก็มีข้อจำกัด เราสามารถส่งตำแหน่งไปให้ผู้ใช้ ใช้งานใน Google Maps ได้เลย เช่นการนำทาง การคำนวณระยะทาง

เริ่มจาก import url_launcher

import 'package:url_launcher/url_launcher.dart';

การใช้งานก็แค่เรียกผ่าน url ของ Google Map ได้เลย โดยส่ง parameter เป็น query ได้เลย ถ้าใครได้อ่านบล็อกของผมเรื่อง Deep link น่าจะรู้ทันทีได้เลยว่า มันคือการใช้ Universal App Link นั่นเอง

  _openOnGoogleMapApp(double latitude, double longitude) async {
    String googleUrl =
        'https://www.google.com/maps/search/?api=1&query=$latitude,$longitude';
    if (await canLaunch(googleUrl)) {
      await launch(googleUrl);
    } else {
      // Could not open the map.
    }
  }

ตัวอย่างนี้ ผมจะเพิ่ม onTap ให้กับ Marker ทำให้เมื่อกดที่ marker ก็จะเปิดแอป Google Maps ขึ้นมา

GoogleMap(
          ..
          markers: {
            Marker(
                markerId: MarkerId("1"),
                position: LatLng(13.6900043, 100.7479237),
                infoWindow: InfoWindow(
                    title: "สนามบินสุวรรณภูมิ",
                    snippet: "สนามบินนานาชาติของประเทศไทย"),
                onTap: () => _openOnGoogleMapApp(13.6900043, 100.7479237)),
          },
        )

การเพิ่มไอคอนให้ Marker

ไอคอนอันเดิมอาจจะยังไม่เป็นที่ถูกใจเราสามารถเพิ่มไอคอนของเราเป็น marker ได้ โดยกำหนดไอคอนไว้ใน asset folder ก่อน

เพิ่ม path ของ asset ใน pubspec.yaml

flutter:
  assets:
    - assets/images/ic_airport.png

ประกาศตัวแปร BitmapDescriptor

class MyMapPageState extends State<MapSample> {
  BitmapDescriptor _markerIcon;
  ..

เขียน method สำหรับดึง ไอคอนใน asset มาใส่ BitmapDescriptor

  Future _createMarkerImageFromAsset(BuildContext context) async {
    if (_markerIcon == null) {
      ImageConfiguration configuration = ImageConfiguration();
      BitmapDescriptor bmpd = await BitmapDescriptor.fromAssetImage(
          configuration, 'assets/images/ic_airport.png');
      setState(() {
        _markerIcon = bmpd;
      });
    }
  }

เรียกใช้ method นี้ใน build

  @override
  Widget build(BuildContext context) {
    _createMarkerImageFromAsset(context);
    ..

กำหนด BitmapDescriptor ให้กับ icon ของ Marker ได้เลย

GoogleMap(
              ..
              markers: {
              Marker(
                  icon: _markerIcon,
                  ..
              },
            )

จะได้ไอคอนแบบนี้

ในเวอชันนี้ BitmapDescriptor.fromAssetImage จะยังมีบัคเรื่องของขนาดไอคอนอยู่ ทั้งๆที่สามารถใส่ค่า size ได้ แต่ไม่แสดงผลตามขนาด

การใส่ Polyline

ลองเพิ่ม polyline ใน map กันครับ มันคือการใส่เส้นที่เราต้องการลงไปในแผนที่ ซึ่ง GoogleMap Widget ก็เตรียมไว้ให้แล้ว คือกำหนดที่ polylines โดยสามารถกำหนดได้หลายอัน เพราะเป็น Set และใน polyline แต่ละอัน ก็กำหนด Lat Lng ให้มันลากเส้น จากจุดหนึ่งไปจุดหนึ่ง

ในตัวอย่างนี้มจะลากเส้นรอบพื้นที่สนามบินสุวรรณภูมิ

GoogleMap(
          ..
          polylines: {
            Polyline(
                polylineId: PolylineId("p1"),
                color: Colors.red[300],
                points: [
                  LatLng(13.7123167,100.728104),
                  LatLng(13.655067, 100.722697),
                  LatLng(13.648389, 100.753335),
                  LatLng(13.705761, 100.779158),
                  LatLng(13.7123167,100.728104),
                ])},

จะได้แบบนี้

สรุป

Google Maps สำหรับ Flutter ภาพรวมทำได้ดีเลย การใช้งานผ่าน widget ง่ายจริงๆ แม้ว่าจะยังมียัคอยู่บ้างเพราะยังอยู่ใน develoepr preview อยู่ ในอนาคตก็จะเสถียรมากขึ้น เดี๋ยวผมจะมาอัพเดทเรื่อยๆครับ