1. Введение
Flutter — это SDK мобильных приложений Google, предназначенный для создания высококачественных нативных приложений для iOS и Android в рекордно короткие сроки.
С помощью плагина Google Maps Flutter вы можете добавлять в свое приложение карты на основе данных карт Google. Плагин автоматически обрабатывает доступ к серверам Google Maps, отображение карт и реакцию на жесты пользователя, такие как щелчки и перетаскивания. Вы также можете добавлять маркеры на карту. Эти объекты предоставляют дополнительную информацию о местоположениях на карте и позволяют пользователю взаимодействовать с картой.
Что ты построишь
В этой лаборатории кода вы создадите мобильное приложение с картой Google, используя Flutter SDK. Ваше приложение будет:
|
Что такое флаттер?
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
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
. Это свяжет маркеры с правильной картой. Конечно, вы можете иметь несколько карт и отображать на каждой разные маркеры.
Вот скриншот того, что у вас получилось. На этом этапе можно сделать много интересных дополнений. Например, вы могли бы добавить список офисов, который перемещает и масштабирует карту, когда пользователь щелкает офис, но, как говорится, это упражнение остается на усмотрение читателя!
6. Следующие шаги
Поздравляем!
Вы завершили работу над кодом и создали приложение Flutter с картой Google! Вы также взаимодействовали с веб-службой JSON.
Другие следующие шаги
В этой кодовой лаборатории реализована возможность визуализации ряда точек на карте. Существует ряд мобильных приложений, которые используют эту возможность для удовлетворения множества различных потребностей пользователей. Есть и другие ресурсы, которые могут помочь вам в этом:
- Создавайте мобильные приложения с помощью Flutter и Google Maps (доклад на Cloud Next '19)
- Пакет
google_maps_webservice
Хадриена Лежара, который делает веб-службы Google Maps, такие как Directions API , Distance Matrix API и Places API , действительно простыми в использовании. - Если вы хотите просмотреть различные варианты использования API через JSON REST, ознакомьтесь с публикацией Эндрю Брогдона на Medium, где представлены различные варианты работы с API JSON REST.