Добавление Google Maps в приложение Flutter

1. Введение

Flutter — это SDK мобильных приложений Google, предназначенный для создания высококачественных нативных приложений для iOS и Android в рекордно короткие сроки.

С помощью плагина Google Maps Flutter вы можете добавлять в свое приложение карты на основе данных карт Google. Плагин автоматически обрабатывает доступ к серверам Google Maps, отображение карт и реакцию на жесты пользователя, такие как щелчки и перетаскивания. Вы также можете добавлять маркеры на карту. Эти объекты предоставляют дополнительную информацию о местоположениях на карте и позволяют пользователю взаимодействовать с картой.

Что ты построишь

В этой лаборатории кода вы создадите мобильное приложение с картой Google, используя Flutter SDK. Ваше приложение будет:

  • Отобразить карту Google
  • Получить данные карты из веб-сервиса
  • Отобразите эти данные в виде маркеров на карте.

Снимок экрана приложения Flutter с картой Google Map, работающей в симуляторе iPhone, с выделенным видом на Маунтин-Вью.

Что такое флаттер?

Flutter имеет три основные возможности.

  • Быстрая разработка : создавайте приложения для Android и iOS за миллисекунды с помощью Stateful Hot Reload.
  • Выразительность и гибкость . Быстрая доставка функций с упором на удобство для конечных пользователей.
  • Собственная производительность как на iOS, так и на Android . Виджеты Flutter включают в себя все важные различия платформ, такие как прокрутка, навигация, значки и шрифты, чтобы обеспечить полную производительность.

Карты Google имеют:

  • Охват 99 % по всему миру : используйте надежные и полные данные для более чем 200 стран и территорий.
  • 25 миллионов обновлений ежедневно : рассчитывайте на точную информацию о местоположении в режиме реального времени.
  • 1 миллиард активных пользователей в месяц . Уверенное масштабирование благодаря инфраструктуре Google Maps.

В этой лаборатории кода вы узнаете, как создать возможности Google Maps в приложении Flutter для iOS и Android.

Что вы узнаете

  • Как создать новое приложение Flutter.
  • Как настроить плагин Flutter для Google Maps.
  • Как добавить маркеры на карту, используя данные о местоположении из веб-сервиса.

Эта кодовая лаборатория посвящена добавлению карты Google в приложение Flutter. Нерелевантные концепции и блоки кода замалчиваются и предоставляются для простого копирования и вставки.

Что бы вы хотели узнать из этой кодовой лаборатории?

Я новичок в этой теме, и мне нужен хороший обзор. Я кое-что знаю по этой теме, но хочу освежить знания. Я ищу пример кода для использования в моем проекте. Я ищу объяснение чего-то конкретного.

2. Настройте среду Flutter.

Для выполнения этой лабораторной работы вам понадобятся два программного обеспечения: Flutter SDK и редактор . Эта лаборатория кода предполагает использование Android Studio, но вы можете использовать предпочитаемый вами редактор.

Вы можете запустить эту лабораторию кода, используя любое из следующих устройств:

  • Физическое устройство (Android или iOS), подключенное к вашему компьютеру и переведенное в режим разработчика.
  • Симулятор iOS. (Требуется установка инструментов Xcode .)
  • Эмулятор Андроид. (Требуется настройка в Android Studio .)

3. Начало работы

Начало работы с Flutter

Самый простой способ начать работу с Flutter — использовать инструмент командной строки Flutter, чтобы создать весь необходимый код для простого начала работы.

$ flutter create google_maps_in_flutter --platforms android,ios,web
Creating project google_maps_in_flutter...
Resolving dependencies in `google_maps_in_flutter`... 
Downloading packages... 
Got dependencies in `google_maps_in_flutter`.
Wrote 81 files.

All done!
You can find general documentation for Flutter at: https://docs.flutter.dev/
Detailed API documentation is available at: https://api.flutter.dev/
If you prefer video documentation, consider: https://www.youtube.com/c/flutterdev

In order to run your application, type:

  $ cd google_maps_in_flutter
  $ flutter run

Your application code is in google_maps_in_flutter/lib/main.dart.

Добавление плагина Google Maps Flutter в качестве зависимости

Добавить дополнительные возможности в приложение Flutter легко с помощью пакетов Pub . В этой лаборатории кода вы познакомитесь с плагином Google Maps Flutter, выполнив следующую команду из каталога проекта.

$ cd google_maps_in_flutter
$ flutter pub add google_maps_flutter
Resolving dependencies... 
Downloading packages... 
+ csslib 1.0.0
+ flutter_plugin_android_lifecycle 2.0.19
+ flutter_web_plugins 0.0.0 from sdk flutter
+ google_maps 7.1.0
+ google_maps_flutter 2.6.1
+ google_maps_flutter_android 2.8.0
+ google_maps_flutter_ios 2.6.0
+ google_maps_flutter_platform_interface 2.6.0
+ google_maps_flutter_web 0.5.7
+ html 0.15.4
+ js 0.6.7 (0.7.1 available)
+ js_wrapping 0.7.4
  leak_tracker 10.0.4 (10.0.5 available)
  leak_tracker_flutter_testing 3.0.3 (3.0.5 available)
  material_color_utilities 0.8.0 (0.11.1 available)
  meta 1.12.0 (1.14.0 available)
+ plugin_platform_interface 2.1.8
+ sanitize_html 2.1.0
+ stream_transform 2.1.0
  test_api 0.7.0 (0.7.1 available)
+ web 0.5.1
Changed 16 dependencies!
6 packages have newer versions incompatible with dependency constraints.
Try `flutter pub outdated` for more information.

Настройка platform iOS

Чтобы получить последнюю версию Google Maps SDK для iOS , требуется минимальная версия платформы iOS 14 . Измените верхнюю часть файла конфигурации ios/Podfile следующим образом.

iOS/Подфайл

# Google Maps SDK requires platform version 14
# https://developers.google.com/maps/flutter-package/config#ios
platform :ios, '14.0'


# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

Настройка Android minSDK

Для использования Google Maps SDK на Android необходимо установить для minSdk значение 21 . Измените файл конфигурации android/app/build.gradle следующим образом.

Android/приложение/build.gradle

android {
    defaultConfig {
        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
        applicationId = "com.example.google_maps_in_flutter"
        // Minimum Android version for Google Maps SDK
        // https://developers.google.com/maps/flutter-package/config#android
        minSdk = 21
        targetSdk = flutter.targetSdkVersion
        versionCode = flutterVersionCode.toInteger()
        versionName = flutterVersionName
    }

}

4. Добавление Google Карт в приложение.

Все дело в ключах API

Чтобы использовать Карты Google в своем приложении Flutter, вам необходимо настроить проект API на платформе Google Maps , следуя инструкциям Maps SDK для Android с использованием ключа API , Maps SDK для iOS с использованием ключа API и Maps JavaScript API с использованием ключа API . Имея на руках ключи API, выполните следующие шаги для настройки приложений Android и iOS.

Добавление ключа API для приложения Android

Чтобы добавить ключ API в приложение Android, отредактируйте файл AndroidManifest.xml в android/app/src/main . Добавьте одну запись meta-data содержащую ключ API, созданный на предыдущем шаге, внутри узла application .

Android/приложение/src/main/AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application
        android:label="google_maps_in_flutter"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher">

        <!-- TODO: Add your Google Maps API key here -->
        <meta-data android:name="com.google.android.geo.API_KEY"
               android:value="YOUR-KEY-HERE"/>

        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:launchMode="singleTop"
            android:taskAffinity=""
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize">
            <!-- Specifies an Android theme to apply to this Activity as soon as
                 the Android process has started. This theme is visible to the user
                 while the Flutter UI initializes. After that, this theme continues
                 to determine the Window background behind the Flutter UI. -->
            <meta-data
              android:name="io.flutter.embedding.android.NormalTheme"
              android:resource="@style/NormalTheme"
              />
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <!-- Don't delete the meta-data below.
             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />
    </application>
    <!-- Required to query activities that can process text, see:
         https://developer.android.com/training/package-visibility and
         https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.

         In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
    <queries>
        <intent>
            <action android:name="android.intent.action.PROCESS_TEXT"/>
            <data android:mimeType="text/plain"/>
        </intent>
    </queries>
</manifest>

Добавление ключа API для приложения iOS

Чтобы добавить ключ API в приложение iOS, отредактируйте файл AppDelegate.swift в ios/Runner . В отличие от Android, добавление ключа API в iOS требует внесения изменений в исходный код приложения Runner. AppDelegate — это основной синглтон, который является частью процесса инициализации приложения.

Внесите два изменения в этот файл. Сначала добавьте оператор #import для извлечения заголовков Google Maps, а затем вызовите метод provideAPIKey() синглтона GMSServices . Этот ключ API позволяет Картам Google правильно отображать фрагменты карты.

iOS/Runner/AppDelegate.swift

import Flutter
import UIKit
import GoogleMaps                                          // Add this import

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    GeneratedPluginRegistrant.register(with: self)

    // TODO: Add your Google Maps API key
    GMSServices.provideAPIKey("YOUR-API-KEY")               // Add this line

    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

Добавление ключа API для веб-приложения

Чтобы добавить ключ API в веб-приложение, отредактируйте файл index.html в web . Добавьте ссылку на сценарий JavaScript Карт в разделе заголовка вместе с вашим ключом API.

веб/index.html

<!DOCTYPE html>
<html>
<head>
  <!--
    If you are serving your web app in a path other than the root, change the
    href value below to reflect the base path you are serving from.

    The path provided below has to start and end with a slash "/" in order for
    it to work correctly.

    For more details:
    * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base

    This is a placeholder for base href that will be replaced by the value of
    the `--base-href` argument provided to `flutter build`.
  -->
  <base href="$FLUTTER_BASE_HREF">

  <meta charset="UTF-8">
  <meta content="IE=Edge" http-equiv="X-UA-Compatible">
  <meta name="description" content="A new Flutter project.">

  <!-- iOS meta tags & icons -->
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-status-bar-style" content="black">
  <meta name="apple-mobile-web-app-title" content="google_maps_in_flutter">
  <link rel="apple-touch-icon" href="icons/Icon-192.png">

  <!-- Favicon -->
  <link rel="icon" type="image/png" href="favicon.png"/>

  <!-- Add your Google Maps API key here -->
  <script src="https://maps.googleapis.com/maps/api/js?key=YOUR-KEY-HERE"></script>

  <title>google_maps_in_flutter</title>
  <link rel="manifest" href="manifest.json">
</head>
<body>
  <script src="flutter_bootstrap.js" async></script>
</body>
</html>

Вывод карты на экран

Теперь пришло время вывести карту на экран. Замените содержимое lib/main.dart следующим.

библиотека/main.dart

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

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  late GoogleMapController mapController;

  final LatLng _center = const LatLng(45.521563, -122.677433);

  void _onMapCreated(GoogleMapController controller) {
    mapController = controller;
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        useMaterial3: true,
        colorSchemeSeed: Colors.green[700],
      ),
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Maps Sample App'),
          elevation: 2,
        ),
        body: GoogleMap(
          onMapCreated: _onMapCreated,
          initialCameraPosition: CameraPosition(
            target: _center,
            zoom: 11.0,
          ),
        ),
      ),
    );
  }
}

Запуск приложения

Запустите приложение Flutter на iOS или Android, чтобы увидеть единое представление карты с центром в Портленде. Альтернативно запустите эмулятор Android или симулятор iOS. Не стесняйтесь изменять центр карты, чтобы он представлял ваш родной город или что-то важное для вас.

$ flutter run

Снимок экрана приложения Flutter с картой Google, работающей в симуляторе iPhone.

Снимок экрана приложения Flutter с картой Google Map, работающей в эмуляторе Android.

5. Добавьте Google на карту

У Google есть множество офисов по всему миру: от Северной Америки , Латинской Америки , Европы , Азиатско-Тихоокеанского региона до Африки и Ближнего Востока . Самое приятное в этих картах, если вы их исследуете, то, что они имеют удобную конечную точку API для предоставления информации о местоположении офиса в формате JSON. На этом этапе вы поместите эти офисы на карту. На этом этапе вы будете использовать генерацию кода для анализа JSON.

Добавьте в проект три новые зависимости Flutter следующим образом. Добавьте пакет http для простого выполнения HTTP-запросов, json_serializable и json_annotation для объявления структуры объекта для представления документов JSON, добавьте build_runner для поддержки генерации кода.

$ flutter pub add http json_annotation json_serializable dev:build_runner
Resolving dependencies... 
Downloading packages... 
+ _fe_analyzer_shared 67.0.0 (68.0.0 available)
+ analyzer 6.4.1 (6.5.0 available)
+ args 2.5.0
+ build 2.4.1
+ build_config 1.1.1
+ build_daemon 4.0.1
+ build_resolvers 2.4.2
+ build_runner 2.4.9
+ build_runner_core 7.3.0
+ built_collection 5.1.1
+ built_value 8.9.2
+ checked_yaml 2.0.3
+ code_builder 4.10.0
+ convert 3.1.1
+ crypto 3.0.3
+ dart_style 2.3.6
+ file 7.0.0
+ fixnum 1.1.0
+ frontend_server_client 4.0.0
+ glob 2.1.2
+ graphs 2.3.1
+ http 1.2.1
+ http_multi_server 3.2.1
+ http_parser 4.0.2
+ io 1.0.4
  js 0.6.7 (0.7.1 available)
+ json_annotation 4.9.0
+ json_serializable 6.8.0
  leak_tracker 10.0.4 (10.0.5 available)
  leak_tracker_flutter_testing 3.0.3 (3.0.5 available)
+ logging 1.2.0
  material_color_utilities 0.8.0 (0.11.1 available)
  meta 1.12.0 (1.14.0 available)
+ mime 1.0.5
+ package_config 2.1.0
+ pool 1.5.1
+ pub_semver 2.1.4
+ pubspec_parse 1.2.3
+ shelf 1.4.1
+ shelf_web_socket 1.0.4
+ source_gen 1.5.0
+ source_helper 1.3.4
  test_api 0.7.0 (0.7.1 available)
+ timing 1.0.1
+ typed_data 1.3.2
+ watcher 1.1.0
+ web_socket_channel 2.4.5
+ yaml 3.1.2
Changed 42 dependencies!
8 packages have newer versions incompatible with dependency constraints.
Try `flutter pub outdated` for more information.

Разбор JSON с генерацией кода

Возможно, вы заметили, что данные JSON, возвращаемые из конечной точки API, имеют регулярную структуру. Было бы удобно сгенерировать код для маршалирования этих данных в объекты, которые можно использовать в коде.

В каталоге lib/src создайте файл locations.dart и опишите структуру возвращаемых данных JSON следующим образом:

lib/src/locations.dart

import 'dart:convert';

import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:http/http.dart' as http;
import 'package:json_annotation/json_annotation.dart';

part 'locations.g.dart';

@JsonSerializable()
class LatLng {
  LatLng({
    required this.lat,
    required this.lng,
  });

  factory LatLng.fromJson(Map<String, dynamic> json) => _$LatLngFromJson(json);
  Map<String, dynamic> toJson() => _$LatLngToJson(this);

  final double lat;
  final double lng;
}

@JsonSerializable()
class Region {
  Region({
    required this.coords,
    required this.id,
    required this.name,
    required this.zoom,
  });

  factory Region.fromJson(Map<String, dynamic> json) => _$RegionFromJson(json);
  Map<String, dynamic> toJson() => _$RegionToJson(this);

  final LatLng coords;
  final String id;
  final String name;
  final double zoom;
}

@JsonSerializable()
class Office {
  Office({
    required this.address,
    required this.id,
    required this.image,
    required this.lat,
    required this.lng,
    required this.name,
    required this.phone,
    required this.region,
  });

  factory Office.fromJson(Map<String, dynamic> json) => _$OfficeFromJson(json);
  Map<String, dynamic> toJson() => _$OfficeToJson(this);

  final String address;
  final String id;
  final String image;
  final double lat;
  final double lng;
  final String name;
  final String phone;
  final String region;
}

@JsonSerializable()
class Locations {
  Locations({
    required this.offices,
    required this.regions,
  });

  factory Locations.fromJson(Map<String, dynamic> json) =>
      _$LocationsFromJson(json);
  Map<String, dynamic> toJson() => _$LocationsToJson(this);

  final List<Office> offices;
  final List<Region> regions;
}

Future<Locations> getGoogleOffices() async {
  const googleLocationsURL = 'https://about.google/static/data/locations.json';

  // Retrieve the locations of Google offices
  try {
    final response = await http.get(Uri.parse(googleLocationsURL));
    if (response.statusCode == 200) {
      return Locations.fromJson(
          json.decode(response.body) as Map<String, dynamic>);
    }
  } catch (e) {
    if (kDebugMode) {
      print(e);
    }
  }

  // Fallback for when the above HTTP request fails.
  return Locations.fromJson(
    json.decode(
      await rootBundle.loadString('assets/locations.json'),
    ) as Map<String, dynamic>,
  );
}

После добавления этого кода в вашей среде IDE (если вы ее используете) должны появиться красные закорючки, поскольку они ссылаются на несуществующий родственный файл locations.g.dart. Этот сгенерированный файл преобразует нетипизированные структуры JSON в именованные объекты. Создайте его, запустив build_runner следующим образом:

$ dart run build_runner build --delete-conflicting-outputs
[INFO] Generating build script...
[INFO] Generating build script completed, took 357ms

[INFO] Creating build script snapshot......
[INFO] Creating build script snapshot... completed, took 10.5s

[INFO] There was output on stdout while compiling the build script snapshot, run with `--verbose` to see it (you will need to run a `clean` first to re-snapshot).

[INFO] Initializing inputs
[INFO] Building new asset graph...
[INFO] Building new asset graph completed, took 646ms

[INFO] Checking for unexpected pre-existing outputs....
[INFO] Deleting 1 declared outputs which already existed on disk.
[INFO] Checking for unexpected pre-existing outputs. completed, took 3ms

[INFO] Running build...
[INFO] Generating SDK summary...
[INFO] 3.4s elapsed, 0/3 actions completed.
[INFO] Generating SDK summary completed, took 3.4s

[INFO] 4.7s elapsed, 2/3 actions completed.
[INFO] Running build completed, took 4.7s

[INFO] Caching finalized dependency graph...
[INFO] Caching finalized dependency graph completed, took 36ms

[INFO] Succeeded after 4.8s with 2 outputs (7 actions)

Теперь ваш код снова должен анализироваться чисто. Далее нам следует добавить резервный файл location.json, используемый в функции getGoogleOffices . Одна из причин включения этого резервного варианта заключается в том, что статические данные, загружаемые в эту функцию, обслуживаются без заголовков CORS и, следовательно, не могут загружаться в веб-браузере. Приложениям Flutter для Android и iOS не нужны заголовки CORS, но доступ к мобильным данным и в лучшие времена может быть сложным.

Перейдите по адресу https://about.google/static/data/locations.json в своем браузере и сохраните содержимое в каталог ресурсов. Альтернативно вы можете использовать командную строку следующим образом.

$ mkdir assets
$ cd assets
$ curl -o locations.json https://about.google/static/data/locations.json
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 30348  100 30348    0     0  75492      0 --:--:-- --:--:-- --:--:-- 75492

Теперь, когда у вас есть загруженный файл ресурса, добавьте его в раздел flutter вашего файла pubspec.yaml .

pubspec.yaml

flutter:
  uses-material-design: true

  assets:
    - assets/locations.json

Измените файл main.dart , чтобы запросить данные карты, а затем используйте полученную информацию для добавления офисов на карту:

библиотека/main.dart

import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'src/locations.dart' as locations;

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final Map<String, Marker> _markers = {};
  Future<void> _onMapCreated(GoogleMapController controller) async {
    final googleOffices = await locations.getGoogleOffices();
    setState(() {
      _markers.clear();
      for (final office in googleOffices.offices) {
        final marker = Marker(
          markerId: MarkerId(office.name),
          position: LatLng(office.lat, office.lng),
          infoWindow: InfoWindow(
            title: office.name,
            snippet: office.address,
          ),
        );
        _markers[office.name] = marker;
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        useMaterial3: true,
        colorSchemeSeed: Colors.green[700],
      ),
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Google Office Locations'),
          elevation: 2,
        ),
        body: GoogleMap(
          onMapCreated: _onMapCreated,
          initialCameraPosition: const CameraPosition(
            target: LatLng(0, 0),
            zoom: 2,
          ),
          markers: _markers.values.toSet(),
        ),
      ),
    );
  }
}

Этот код выполняет несколько операций:

  • В _onMapCreated он использует код анализа JSON из предыдущего шага, await его загрузки. Затем он использует возвращенные данные для создания Marker внутри обратного вызова setState() . Как только приложение получит новые маркеры, setState пометит Flutter, чтобы перерисовать экран, в результате чего отобразятся местоположения офисов.
  • Маркеры хранятся на Map , связанной с виджетом GoogleMap . Это свяжет маркеры с правильной картой. Конечно, вы можете иметь несколько карт и отображать на каждой разные маркеры.

Снимок экрана приложения Flutter с картой Google Map, работающей в симуляторе iPhone, с выделенным видом на Маунтин-Вью.

Вот скриншот того, что у вас получилось. На этом этапе можно сделать много интересных дополнений. Например, вы могли бы добавить список офисов, который перемещает и масштабирует карту, когда пользователь щелкает офис, но, как говорится, это упражнение остается на усмотрение читателя!

6. Следующие шаги

Поздравляем!

Вы завершили работу над кодом и создали приложение Flutter с картой Google! Вы также взаимодействовали с веб-службой JSON.

Другие следующие шаги

В этой кодовой лаборатории реализована возможность визуализации ряда точек на карте. Существует ряд мобильных приложений, которые используют эту возможность для удовлетворения множества различных потребностей пользователей. Есть и другие ресурсы, которые могут помочь вам в этом: