Andorid/Flutter的Dictionary也叫Map

使用Flutter版Google Maps 跟 location
(定位還有一套geolocator好像也很厲害, 不過兩套都是Flutter Favorites)
要先去GCP生一把Google Maps API key
選擇Maps SDK for iOS並啟用
然後因為已經啟用iOS了, 所以畫面會變成這樣


iOS Runner的AppDelegate
一樣要import GoogleMaps
然後GMSServices.provideAPIKey
import UIKit
import Flutter
import GoogleMaps
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    GMSServices.provideAPIKey("你鑰匙掉了")
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}
Android這邊請
<manifest ...
  <application ...
    <meta-data android:name="com.google.android.geo.API_KEY"
               android:value="YOUR KEY HERE"/>
如果需要不同平台用不同的key
可以申請兩隻
然後點進去設定
如果切換帳號會看到這個報錯
重新選取專案就好了
iOS:
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs access to location when open.</string>
Android:
如果是Flutter1.12之前才需要特別處理,看這裡
就是一個叫做GoogleMap的Widget
可以設定圖層類型與起始點
根據Fullter慣例
一樣是使用controller去控制widget
不過這邊用了一個沒見過的東西Completer
暫時沒研究
Completer<GoogleMapController> _controller = Completer();
GoogleMap(
    mapType: MapType.terrain,
    initialCameraPosition: _kGooglePlex,
    myLocationEnabled: _enableMyLocation,
    myLocationButtonEnabled: true,
    markers: Set<Marker>.of(_markerMap.values),
    onMapCreated: (GoogleMapController controller) {
      _controller.complete(controller);
    },
  )
可以看到在init裡面有兩個參數都是跟my location有關的
myLocationButtonEnabled就是右下角的定位按鈕(預設為ture)
點了之後的邏輯套件已經處理好
iOS版的Google Maps應該沒有這麼方便⚠️⚠️⚠️
myLocationEnabled就是在地圖上要不要顯示藍點點(預設false)
若為true, 會要求權限
但是要求的時機也太醜了吧...就卡在這邊
用了addPostFrameCallback
好啦是有好一點...
抱歉小弟學藝不精
暫時先用delayed
initState兩秒後再要求(經實測XD, 1秒不要求, 3秒太久)
剛剛配對的controller就可以在這時使用
  static final CameraPosition _kGooglePlex = CameraPosition(
    target: LatLng(37.42796133580664, -122.085749655962),
    zoom: 14.4746,
  );
  final GoogleMapController controller = await _controller.future;
  controller.animateCamera(CameraUpdate.newCameraPosition(_kGooglePlex));
因為是要請聖獸守護在自身四方(這人寫扣寫到頭殼壞掉)
所以要先取得目前位置
  void _getCurrentLocation() async {
    var location = new Location();
    try {
      _currentLocation = await location.getLocation();
      print(_currentLocation);
    } on Exception {
      _currentLocation = null;
    }
    _addMarker(MarkerType.top);
    _addMarker(MarkerType.bottom);
    _addMarker(MarkerType.left);
    _addMarker(MarkerType.right);
  }
然後建立Marker, 加進GoogleMap的markers參數裡
注意兩點:
  enum MarkerType {top, bottom, left, right}
  Map<MarkerId, Marker> _markerMap = <MarkerId, Marker>{};
  
  void _addMarker(MarkerType type) {
    final MarkerId markerId = MarkerId("IDLF_$type");
    final offset = 0.002;
    LatLng latLng;
    String snippet;
    switch (type){
      case MarkerType.top:
        latLng = LatLng(_currentLocation.latitude + 0.001, _currentLocation.longitude);
        snippet = "北~玄武";
        break;
      case MarkerType.bottom:
        latLng = LatLng(_currentLocation.latitude - 0.003, _currentLocation.longitude);
        snippet = "南~朱雀";
        break;
      case MarkerType.left:
        latLng = LatLng(_currentLocation.latitude, _currentLocation.longitude - offset);
        snippet = "左~青龍";
        break;
      case MarkerType.right:
        latLng = LatLng(_currentLocation.latitude, _currentLocation.longitude + offset);
        snippet = "右~白虎";
        break;
    }
    final Marker marker = Marker(
      markerId: markerId,
      infoWindow: InfoWindow(title: "Hello~", snippet: snippet),
      position: latLng,
    );
    if (!mounted) return;
    setState(() {
      _markerMap[markerId] = marker;
    });
  }
  
pub上面的範例就這樣...有點崩潰
後來發現是在這邊
然後套件裡面也有@@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:icofont_flutter/icofont_flutter.dart';
import 'package:location/location.dart';
enum MarkerType {top, bottom, left, right}
class LessonPageMap extends StatefulWidget {
  @override
  _LessonPageMapState createState() => _LessonPageMapState();
}
class _LessonPageMapState extends State<LessonPageMap> {
  static final CameraPosition _kGooglePlex = CameraPosition(
    target: LatLng(37.42796133580664, -122.085749655962),
    zoom: 14.4746,
  );
  Completer<GoogleMapController> _controller = Completer();
  bool _enableMyLocation = false;
  Map<MarkerId, Marker> _markerMap = <MarkerId, Marker>{};
  LocationData _currentLocation;
  void _getCurrentLocation() async {
    var location = new Location();
    try {
      _currentLocation = await location.getLocation();
      print(_currentLocation);
    } on Exception {
      _currentLocation = null;
    }
    _addMarker(MarkerType.top);
    _addMarker(MarkerType.bottom);
    _addMarker(MarkerType.left);
    _addMarker(MarkerType.right);
  }
  void _addMarker(MarkerType type) {
    final MarkerId markerId = MarkerId("IDLF_$type");
    final offset = 0.002;
    LatLng latLng;
    String snippet;
    switch (type){
      case MarkerType.top:
        latLng = LatLng(_currentLocation.latitude + 0.001, _currentLocation.longitude);
        snippet = "北~玄武";
        break;
      case MarkerType.bottom:
        latLng = LatLng(_currentLocation.latitude - 0.003, _currentLocation.longitude);
        snippet = "南~朱雀";
        break;
      case MarkerType.left:
        latLng = LatLng(_currentLocation.latitude, _currentLocation.longitude - offset);
        snippet = "左~青龍";
        break;
      case MarkerType.right:
        latLng = LatLng(_currentLocation.latitude, _currentLocation.longitude + offset);
        snippet = "右~白虎";
        break;
    }
    final Marker marker = Marker(
      markerId: markerId,
      infoWindow: InfoWindow(title: "Hello~", snippet: snippet),
      position: latLng,
    );
    if (!mounted) return;
    setState(() {
      _markerMap[markerId] = marker;
    });
  }
  @override
  void initState() {
    super.initState();
    print("init");
    print(DateTime.now());
      Future.delayed(Duration(seconds: 2)).then((value) {
      print("delayed");
      print(DateTime.now());
      setState(() {
        _enableMyLocation = true;
        _getCurrentLocation();
      });
    });
  }
  Future<void> _goToGoogle() async {
    final GoogleMapController controller = await _controller.future;
    controller.animateCamera(CameraUpdate.newCameraPosition(_kGooglePlex));
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Map")),
      body: GoogleMap(
        mapType: MapType.terrain,
        initialCameraPosition: _kGooglePlex,
        myLocationEnabled: _enableMyLocation,
        myLocationButtonEnabled: true,
        markers: Set<Marker>.of(_markerMap.values),
        onMapCreated: (GoogleMapController controller) {
          _controller.complete(controller);
        },
      ),
      floatingActionButtonLocation: FloatingActionButtonLocation.startFloat,
      floatingActionButton: FloatingActionButton(
        child: Icon(IcoFontIcons.brandGoogle),
        onPressed: _goToGoogle,
      ),
    );
  }
}
本集內容Android版請見:iOS Developer Learning Android. Lesson 22
下集預告:打包上架
