1. Introdução
O Flutter é o SDK de apps para dispositivos móveis do Google. Ele cria experiências nativas de alta qualidade no iOS e no Android em tempo recorde.
Com o plug-in do Google Maps para Flutter, você pode adicionar mapas ao seu aplicativo com base nos dados do Google Maps. O plug-in controla automaticamente o acesso aos servidores do Google Maps, a exibição de mapas e a resposta aos gestos do usuário, como cliques e arrastos. Você também pode adicionar marcadores ao mapa. Esses objetos exibem informações adicionais sobre os locais do mapa e permitem que o usuário interaja com ele.
O que você criará
Neste codelab, você criará um app para dispositivos móveis que traz um mapa do Google Maps usando o SDK do Flutter. Esse app vai:
|
O que é o Flutter?
O Flutter tem três recursos principais.
- Rápido para desenvolver: crie seus aplicativos para Android e iOS em milissegundos com a recarga automática com estado.
- Expressivo e flexível: envie recursos rapidamente com foco nas experiências nativas do usuário final.
- Desempenho nativo no iOS e no Android: os widgets do Flutter incorporam todas as diferenças críticas da plataforma, como rolagem, navegação, ícones e fontes, proporcionando um desempenho nativo completo.
Veja o que o Google Maps oferece:
- 99% de cobertura no mundo: crie com dados confiáveis e abrangentes sobre mais de 200 países e territórios.
- 25 milhões de atualizações diárias: conte com informações de local precisas e em tempo real.
- Um bilhão de usuários ativos por mês: escalone com confiança usando a infraestrutura do Google Maps.
Este codelab mostra como criar uma experiência do Google Maps em um app do Flutter para iOS e Android. Para você se aprofundar no tema, este codelab traz links para repositórios e bibliotecas. A maioria dessas páginas está em inglês.
O que você aprenderá
- Como criar um novo aplicativo do Flutter
- Como configurar um plug-in do Google Maps para Flutter
- Como adicionar marcadores a um mapa usando dados de local de um serviço da Web
O foco deste codelab é adicionar um mapa do Google a um app do Flutter. Conceitos e blocos de códigos não relevantes serão resumidos e apresentados para que você apenas copie e cole.
O que você quer aprender com este codelab?
2. Configurar o ambiente do Flutter
Você precisa de dois softwares para concluir este laboratório: o SDK do Flutter e um editor. Este codelab irá trabalhar com o Android Studio, mas você pode usar o editor que preferir.
É possível completar este codelab usando qualquer um dos seguintes dispositivos:
- Dispositivo físico (Android ou iOS) conectado ao computador e configurado para o modo de desenvolvedor
- Simulador de iOS (requer instalar o Xcode)
- Android Emulator (requer configuração no Android Studio)
3. Primeiros passos
Primeiros passos com o Flutter
A maneira mais fácil de começar com o Flutter é usar a ferramenta de linha de comando dele e criar todo o código necessário para uma primeira experiência.
$ flutter create google_maps_in_flutter Creating project google_maps_in_flutter... [Listing of created files elided] Wrote 127 files. All done! 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.
Adicionar o plug-in do Google Maps ao Flutter como uma dependência
É fácil adicionar outros recursos a um app do Flutter usando os pacotes do Pub. Neste codelab, você insere o plug-in do Flutter do Google Maps executando o seguinte comando a partir do diretório do projeto:
$ cd google_maps_in_flutter $ flutter pub add google_maps_flutter Resolving dependencies... async 2.6.1 (2.8.2 available) charcode 1.2.0 (1.3.1 available) + flutter_plugin_android_lifecycle 2.0.3 + google_maps_flutter 2.0.8 + google_maps_flutter_platform_interface 2.1.1 matcher 0.12.10 (0.12.11 available) meta 1.3.0 (1.7.0 available) + plugin_platform_interface 2.0.1 + stream_transform 2.0.0 test_api 0.3.0 (0.4.3 available) Downloading google_maps_flutter 2.0.8... Downloading flutter_plugin_android_lifecycle 2.0.3... Changed 5 dependencies!
Este codelab também aborda como usar o Google Maps no Flutter para a Web. No entanto, a versão da Web do plug-in ainda não é federada, então você também precisa adicioná-lo ao seu projeto.
$ flutter pub add google_maps_flutter_web Resolving dependencies... async 2.6.1 (2.8.2 available) charcode 1.2.0 (1.3.1 available) + csslib 0.17.0 + flutter_web_plugins 0.0.0 from sdk flutter + google_maps 5.3.0 + google_maps_flutter_web 0.3.0+4 + html 0.15.0 + js 0.6.3 + js_wrapping 0.7.3 matcher 0.12.10 (0.12.11 available) meta 1.3.0 (1.7.0 available) + sanitize_html 2.0.0 test_api 0.3.0 (0.4.3 available) Changed 8 dependencies!
Como configurar o platform
do iOS
Para usar a versão mais recente do SDK do Google Maps no iOS, é necessário ter no mínimo a versão do iOS 11 da plataforma. Modifique o ios/Podfile da seguinte maneira:
ios/Podfile
# Set platform to 11.0 to enable latest Google Maps SDK
platform :ios, '11.0' # Uncomment and set to 11.
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
Como configurar o minSDK
Android
Para usar o SDK do Google Maps no Android, defina minSDK
como 20. Modifique o android/app/build.gradle conforme exibido abaixo.
android/app/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"
minSdkVersion 20 // Update from 16 to 20
targetSdkVersion 30
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
}
4. Adicionar o Google Maps ao app
O que importa são as chaves de API
Para usar o Google Maps no seu app do Flutter, é necessário configurar um projeto de API com a Plataforma Google Maps, seguindo as instruções de como usar chaves de API no SDK do Maps para Android, no SDK do Maps para iOS e no SDK da API Maps JavaScript. Com as chaves de API em mãos, execute as etapas a seguir para configurar os aplicativos Android e iOS.
Adicionar uma chave de API a um app Android
Para adicionar uma chave de API ao app Android, edite o arquivo AndroidManifest.xml
em android/app/src/main
. Adicione uma única entrada meta-data
contendo a chave de API criada na etapa anterior dentro do nó application
.
android/app/src/main/AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.google_maps_in_flutter">
<application
android:label="google_maps_in_flutter"
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:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
Adicionar uma chave de API a um app iOS
Para adicionar uma chave de API ao app iOS, edite o arquivo AppDelegate.swift
em ios/Runner
. Ao contrário do Android, adicionar uma chave de API no iOS exige mudanças no código-fonte do app Runner. O AppDelegate é o Singleton principal que faz parte do processo de inicialização do app.
Faça duas alterações nesse arquivo. Primeiro, adicione uma instrução #import
para extrair os cabeçalhos do Google Maps e, em seguida, chame o método provideAPIKey()
do Singleton GMSServices
. Essa chave de API permite que o Google Maps exiba corretamente os blocos de mapas.
ios/Runner/AppDelegate.swift
import UIKit
import Flutter
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")
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
Adicionar uma chave de API a um app da Web
Para adicionar uma chave de API ao app da Web, edite o arquivo index.html
em web
. Adicione uma referência ao script de JavaScript do Google Maps na seção <head> incluindo sua chave de API.
web/index.html
<head>
<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">
<!-- TODO: 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>
Mostrar um mapa na tela
Agora, é hora de mostrar um mapa na tela. Atualize lib/main.dart
desta maneira:
lib/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({Key? key}) : super(key: key);
@override
_MyAppState 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(
home: Scaffold(
appBar: AppBar(
title: const Text('Maps Sample App'),
backgroundColor: Colors.green[700],
),
body: GoogleMap(
onMapCreated: _onMapCreated,
initialCameraPosition: CameraPosition(
target: _center,
zoom: 11.0,
),
),
),
);
}
}
Executar o app
Execute o app do Flutter no iOS ou Android para ter uma única visualização de mapa, centralizada em Portland. Outra opção é executar um emulador do Android ou um simulador de iOS. Você pode modificar o centro do mapa para representar sua cidade ou algum lugar importante para você.
$ flutter run
5. Colocar o Google no mapa
O Google tem muitos escritórios no mundo todo, desde a América do Norte, América Latina, Europa e Ásia-Pacífico até a África e o Oriente Médio. Se você investigar esses mapas, vai ver que eles têm um endpoint de API fácil de usar para fornecer as informações do local dos escritórios no formato JSON. Nesta etapa, você irá colocar esses escritórios no mapa, além de usar a geração de código para analisar o JSON.
Adicione três novas dependências do Flutter ao projeto da seguinte forma: Primeiro, adicione o pacote http
para facilitar as solicitações HTTP.
$ flutter pub add http Resolving dependencies... async 2.8.1 (2.8.2 available) + http 0.13.3 + http_parser 4.0.0 matcher 0.12.10 (0.12.11 available) + pedantic 1.11.1 test_api 0.4.2 (0.4.3 available) Changed 3 dependencies!
Em seguida, adicione json_serializing para declarar a estrutura de objetos e representar documentos JSON.
$ flutter pub add json_serializable Resolving dependencies... + _fe_analyzer_shared 25.0.0 + analyzer 2.2.0 + args 2.2.0 async 2.8.1 (2.8.2 available) + build 2.1.0 + build_config 1.0.0 + checked_yaml 2.0.1 + cli_util 0.3.3 + convert 3.0.1 + crypto 3.0.1 + dart_style 2.0.3 + file 6.1.2 + glob 2.0.1 + json_annotation 4.1.0 + json_serializable 5.0.0 + logging 1.0.1 matcher 0.12.10 (0.12.11 available) + package_config 2.0.0 + pub_semver 2.0.0 + pubspec_parse 1.0.0 + source_gen 1.1.0 + source_helper 1.2.1 test_api 0.4.2 (0.4.3 available) + watcher 1.0.0 + yaml 3.1.0 Downloading analyzer 2.2.0... Downloading _fe_analyzer_shared 25.0.0... Changed 22 dependencies!
Por fim, adicione build_runner como uma dependência do tempo de desenvolvimento. Ele será usado para a geração de código mais adiante nesta etapa.
$ flutter pub add --dev build_runner Resolving dependencies... async 2.8.1 (2.8.2 available) + build_daemon 3.0.0 + build_resolvers 2.0.4 + build_runner 2.1.1 + build_runner_core 7.1.0 + built_collection 5.1.0 + built_value 8.1.2 + code_builder 4.1.0 + fixnum 1.0.0 + frontend_server_client 2.1.2 + graphs 2.0.0 + http_multi_server 3.0.1 + io 1.0.3 + js 0.6.3 matcher 0.12.10 (0.12.11 available) + mime 1.0.0 + pool 1.5.0 + shelf 1.2.0 + shelf_web_socket 1.0.1 test_api 0.4.2 (0.4.3 available) + timing 1.0.0 + web_socket_channel 2.1.0 Changed 19 dependencies!
Como analisar JSON com a geração de código
Observe que os dados JSON retornados do endpoint de API têm uma estrutura regular. Seria interessante gerar o código para organizar esses dados em objetos que você pode usar no código.
No diretório lib/src
, crie um arquivo locations.dart
e descreva a estrutura dos dados JSON retornados da seguinte forma:
lib/src/locations.dart
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:json_annotation/json_annotation.dart';
import 'package:flutter/services.dart' show rootBundle;
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));
}
} catch (e) {
print(e);
}
// Fallback for when the above HTTP request fails.
return Locations.fromJson(
json.decode(
await rootBundle.loadString('assets/locations.json'),
),
);
}
Depois de adicionar esse código, o ambiente de desenvolvimento integrado (se você estiver usando um) exibirá alguns destaques em vermelho, já que ele faz referência a um arquivo irmão que não existe, o locations.g.dart.
. Esse arquivo gerado é convertido entre estruturas JSON não digitadas e objetos nomeados. Crie-o executando o build_runner
:
$ flutter pub 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)
Agora, seu código será analisado novamente sem erros. Em seguida, precisamos adicionar o arquivo substituto locations.json usado na função getGoogleOffices
. Uma das razões para incluir esse substituto é que os dados estáticos carregados nesta função são veiculados sem cabeçalhos CORS e, portanto, não são carregados em um navegador da Web. Os apps do Flutter para Android e iOS não precisam de cabeçalhos CORS, mas o acesso a dados móveis pode ser complicado.
Acesse https://about.google/static/data/locations.json
no navegador e salve o conteúdo no diretório de assets. Outra opção é usar a linha de comando da seguinte forma:
$ 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
Agora que você já fez o download do arquivo de assets, adicione-o à seção do Flutter do arquivo pubspec.yaml
.
pubspec.yaml
flutter:
uses-material-design: true
assets:
- assets/locations.json
Modifique o arquivo main.dart
para solicitar os dados do mapa e use as informações retornadas para adicionar os escritórios ao mapa:
lib/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({Key? key}) : super(key: key);
@override
_MyAppState 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(
home: Scaffold(
appBar: AppBar(
title: const Text('Google Office Locations'),
backgroundColor: Colors.green[700],
),
body: GoogleMap(
onMapCreated: _onMapCreated,
initialCameraPosition: const CameraPosition(
target: LatLng(0, 0),
zoom: 2,
),
markers: _markers.values.toSet(),
),
),
);
}
}
Esse código executa várias operações:
- Em
_onMapCreated
, ele usa o código de análise JSON da etapa anterior, aguardando (await
) até que ele seja carregado. Em seguida, usará os dados retornados para criar marcadores (Marker
) dentro de um callbacksetState()
. Quando o app recebe novos marcadores, o setState sinaliza o Flutter para repintar a tela, exibindo os locais dos escritórios. - Os marcadores são armazenados em um
Map
associado ao widgetGoogleMap
. Isso vincula os marcadores ao mapa correto. Obviamente, você pode ter vários mapas e exibir marcadores diferentes em cada um.
Veja uma captura de tela do que você fez hoje. Há muitas coisas interessantes que podem ser adicionadas a partir de agora. Por exemplo, você pode adicionar uma visualização dos escritórios em lista, que se move e amplia o mapa quando o usuário clica em um deles, mas o exercício fica para você.
6. Próximas etapas
Parabéns!
Você concluiu o codelab e criou um app do Flutter com um mapa do Google. Você também interagiu com um serviço da Web JSON.
Etapas adicionais
Este codelab criou uma experiência para visualizar uma série de pontos em um mapa. Há diversos aplicativos para dispositivos móveis desenvolvidos com esse recurso a fim de atender a muitas necessidades diferentes dos usuários. Existem outros recursos que podem ajudar você a fazer isso:
- Build Mobile Apps With Flutter and Google Maps (uma palestra do Cloud Next '19)
- O pacote
google_maps_webservice
de Hadrien Lejard, que facilita muito o uso dos Serviços da Web do Google Maps, como a API Directions, a API Distance Matrix e a API Places. - Se você quiser analisar opções diferentes para usar uma API com REST JSON, consulte o artigo de Andrew Brogdon no Medium para ver várias opções para trabalhar com as APIs REST JSON.