1. Introducción
Flutter es un SDK de apps para dispositivos móviles de Google que permite crear experiencias nativas de alta calidad en iOS y Android en tiempo récord.
Con el complemento de Google Maps para Flutter, puedes agregar mapas basados en datos de Google Maps a tu aplicación. El complemento administra automáticamente el acceso a los servidores de Google Maps, la visualización de los mapas y la respuesta a gestos del usuario, como cuando se hace clic o se arrastra algún elemento. También puedes agregar marcadores a tu mapa. Estos objetos proporcionan información adicional de las ubicaciones en el mapa y permiten al usuario interactuar con este.
Qué compilarás
En este codelab, utilizarás el SDK de Flutter a fin de compilar una app para dispositivos móviles que muestre un mapa de Google Maps. Tu app hará lo siguiente:
|
¿Qué es Flutter?
Flutter tiene tres características principales:
- Desarrollo ágil: Compila tus aplicaciones para iOS y Android en cuestión de milisegundos gracias al proceso de recarga en caliente con estado.
- Expresivo y flexible: Incluye funciones rápidamente haciendo hincapié en las experiencias nativas del usuario final.
- Rendimiento nativo en iOS y Android: Los widgets de Flutter incorporan todas las diferencias fundamentales de cada plataforma, como el desplazamiento, la navegación, los íconos y las fuentes, para proporcionar un rendimiento nativo completo.
Google Maps ofrece lo siguiente:
- Cobertura del 99% del mundo: Compila software con datos exhaustivos y confiables de más de 200 países y territorios.
- 25 millones de actualizaciones diarias: Recibe información precisa y en tiempo real acerca de las ubicaciones.
- 1,000 millones de usuarios activos por mes: Aumenta el alcance de tu app con confianza y con el respaldo de la infraestructura de Google Maps.
En este codelab, se explica cómo crear una experiencia de Google Maps en una app creada con Flutter para iOS y Android.
Qué aprenderás
- Cómo crear una nueva aplicación con Flutter
- Cómo configurar el complemento de Google Maps para Flutter
- Cómo agregar marcadores a un mapa con los datos de ubicación de un servicio web
En este codelab, nos centraremos en cómo agregar un mapa de Google Maps a una app creada con Flutter. No ahondaremos en conceptos ni bloques de código que no sean relevantes, los cuales solo se proporcionan para que los copies y pegues.
¿Qué te gustaría aprender en este codelab?
2. Configura tu entorno de Flutter
Para completar este lab, necesitas dos software: el SDK de Flutter y un editor. En este codelab, se presupone que utilizarás Android Studio, pero puedes optar por el editor que desees.
Puedes ejecutar este codelab en cualquiera de los siguientes dispositivos:
- Un dispositivo físico (iOS o Android) conectado a tu computadora y configurado en modo de desarrollador
- El simulador de iOS (requiere instalar herramientas de Xcode)
- El emulador de Android (requiere configuración en Android Studio)
3. Cómo comenzar
Cómo comenzar a utilizar Flutter
La manera más sencilla de comenzar a utilizar Flutter es emplear su herramienta de línea de comandos para crear todo el código necesario a fin de generar una experiencia inicial simple.
$ 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.
Cómo agregar el complemento de Google Maps para Flutter como dependencia
Agregar funciones adicionales a una app creada con Flutter es muy fácil gracias a los paquetes de Pub. En este codelab, incorporarás el complemento de Google Maps para Flutter. Para ello, debes ejecutar el siguiente comando desde el directorio del proyecto:
$ 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!
También veremos cómo utilizar Google Maps en Flutter para la Web. Sin embargo, la versión web del complemento aún no está federada, por lo que también deberás agregarla al proyecto.
$ 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!
Cómo configurar platform
de iOS
Para obtener la versión más reciente del SDK de Google Maps en iOS, se requiere una versión mínima de la plataforma de iOS 11. Modifica el código de ios/Podfile como se indica a continuación.
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'
Cómo configurar minSDK
de Android
Para utilizar el SDK de Google Maps en Android, se requiere configurar minSDK
en 20. Modifica el código de android/app/build.gradle como se indica a continuación.
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. Cómo agregar Google Maps a la app
Las claves de API son fundamentales
Para utilizar Google Maps en tu app creada con Flutter, debes configurar un proyecto de API con Google Maps Platform. Para ello, sigue las instrucciones sobre cómo usar las claves de API del SDK de Maps para Android, del SDK de Maps para iOS y de la API de Maps JavaScript. Con las claves de API a mano, sigue los pasos que se indican a continuación a fin de configurar las aplicaciones para iOS y Android.
Cómo agregar una clave de API a una app para Android
Si deseas agregar una clave de API a una app para Android, edita el archivo AndroidManifest.xml
en android/app/src/main
. Agrega una sola entrada meta-data
que contenga la clave de API creada en el paso anterior dentro del nodo 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>
Cómo agregar una clave de API a una app para iOS
Si deseas agregar una clave de API a una app para iOS, edita el archivo AppDelegate.swift
en ios/Runner
. A diferencia de lo que sucede en Android, para agregar una clave de API en iOS, se deben hacer cambios en el código fuente de la app de Runner. AppDelegate es el singleton principal que forma parte del proceso de inicialización de apps.
Debes hacer dos cambios en este archivo. Primero, agrega una sentencia #import
para extraer los encabezados de Google Maps y, luego, llama al método provideAPIKey()
del singleton GMSServices
. Esta clave de API permite a Google Maps mostrar correctamente los mosaicos del mapa.
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)
}
}
Cómo agregar una clave de API a una app web
Para agregar una clave de API a una app web, edita el archivo index.html
en web
. Agrega una referencia a la secuencia de comandos de Maps JavaScript en la sección <head> con tu clave 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>
Cómo mostrar un mapa en la pantalla
Ahora es el momento de mostrar un mapa en la pantalla. Actualiza lib/main.dart
de la siguiente manera:
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,
),
),
),
);
}
}
Cómo ejecutar la app
Ejecuta la app creada con Flutter en iOS o Android para ver una sola vista de mapa centrada en Portland. También puedes ejecutar un emulador de Android o un simulador de iOS. Puedes modificar el centro del mapa para que sea tu ciudad o el lugar que desees.
$ flutter run
5. Coloca a Google en el mapa
Google tiene muchas oficinas en distintas partes del mundo, desde Norteamérica, Latinoamérica, Europa y Asia-Pacífico hasta África y Oriente Medio. Lo bueno de estos mapas, si los investigas, es que tienen un extremo de API que se puede utilizar fácilmente para proporcionar información de la ubicación de las oficinas en formato JSON. En este paso, colocarás las ubicaciones de esas oficinas en el mapa y generarás un código para analizar datos JSON.
Agrega tres dependencias nuevas de Flutter al proyecto de la siguiente manera. Primero, agrega el paquete http
para realizar solicitudes HTTP con facilidad.
$ 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!
A continuación, agrega json_serializable a fin de declarar la estructura de objetos para representar los 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 último, agrega build_runner como una dependencia de tiempo de desarrollo. Esta se utilizará para generar código más adelante en este paso.
$ 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!
Cómo analizar datos JSON a través de la generación de código
Probablemente adviertas que los datos JSON resultantes provenientes del extremo de API tienen una estructura regular. Por eso, sería conveniente generar el código necesario para organizar esos datos en objetos que puedas utilizar en el código.
En el directorio lib/src
, crea un archivo locations.dart
y describe la estructura de los datos JSON resultantes de la siguiente manera:
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'),
),
);
}
Tras agregar este código, tu IDE (si utilizas uno) debería mostrar partes con un subrayado ondulado de color rojo, ya que hace referencia a un archivo del mismo nivel inexistente, locations.g.dart.
. Este archivo generado realiza conversiones entre estructuras JSON sin tipo y objetos con nombre. Ejecuta build_runner
para crearlo:
$ 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)
Ahora, tu código debería volver a analizarse correctamente. A continuación, debemos agregar el archivo de resguardo locations.json que se utiliza en la función getGoogleOffices
. Una de las razones para incluir este archivo de resguardo es que los datos estáticos cargados en esta función se entregan sin encabezados CORS y, por lo tanto, no pueden cargarse en un navegador web. Las apps creadas con Flutter para iOS y Android no requieren encabezados CORS, pero el acceso a datos móviles puede ser complejo en el mejor de los casos.
Ve a https://about.google/static/data/locations.json
en tu navegador y guarda el contenido en el directorio de recursos. Como alternativa, puedes utilizar la línea de comandos de la siguiente manera:
$ 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
Una vez que descargues el archivo de recursos, agrégalo a la sección flutter de tu archivo pubspec.yaml
.
pubspec.yaml
flutter:
uses-material-design: true
assets:
- assets/locations.json
Modifica el archivo main.dart
para solicitar los datos del mapa y, luego, utiliza la información resultante para agregar las oficinas al 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(),
),
),
);
}
}
Este código realiza varias operaciones:
- En
_onMapCreated
, utiliza el código de análisis de JSON del paso anterior y espera (await
) hasta que se completa la carga. Luego, utiliza los datos resultantes para crear marcadores (Marker
) dentro de una devolución de llamadasetState()
. Una vez que la app recibe marcadores nuevos, setState indica a Flutter que vuelva a procesar la imagen de la pantalla para mostrar las ubicaciones de las oficinas. - Los marcadores se almacenan en un mapa (
Map
) asociado con el widget deGoogleMap
, lo que permite vincularlos al mapa correcto. Sin duda, podrías tener varios mapas y mostrar diferentes marcadores en cada uno.
Esta es una captura de pantalla de lo que lograste. En este punto, se pueden agregar muchas opciones interesantes. Por ejemplo, podrías agregar una vista de lista de las oficinas y que el mapa se mueva y se acerque cuando el usuario haga clic en una de las oficinas. No obstante, este ejercicio queda a cargo del lector.
6. Próximos pasos
¡Felicitaciones!
Completaste el codelab y compilaste una app con Flutter con un mapa de Google Maps. También interactuaste con un servicio web JSON.
Otros próximos pasos
En este codelab, se compiló una experiencia para visualizar una serie de puntos en un mapa. Hay varias apps para dispositivos móviles que expanden esta función a fin de satisfacer diversas necesidades de los usuarios. Existen otros recursos que pueden ayudarte con ello:
- Build Mobile Apps With Flutter and Google Maps (Compila apps para dispositivos móviles con Flutter y Google Maps) (una charla de Cloud Next '19)
- Paquete de
google_maps_webservice
de Hadrien Lejard, que simplifica el uso de los servicios web de Google Maps, como la API de Directions, la API de Distance Matrix y la API de Places - Publicación en Medium de Andrew Brogdon, donde podrás encontrar diversas opciones para trabajar con las API de REST de JSON