Source: https://maps.vietmap.vn/docs/sdk-mobile/vietmap-google-sdk/flutter/ # Integrating VietMap APIs inside a Google Maps Flutter app This guide shows how you can keep using `google_maps_flutter` while leveraging VietMap data via: * Autocomplete / merged location search (API v4) for address & POI suggestions ([Autocomplete v4](https://maps.vietmap.vn/docs/map-api/autocomplete-version/autocomplete-v4/)) * Geocode/Search results (API v4) parsed exactly per spec ([Geocode v4](https://maps.vietmap.vn/docs/map-api/geocode-version/geocode-v4/)) * Place detail (API v4) to obtain coordinates from `ref_id` ([Place v4](https://maps.vietmap.vn/docs/map-api/place-v4/)) * Route between two or more points and render a Polyline ([Route v4](https://maps.vietmap.vn/docs/map-api/route-version/route-v4/)) !!! note "Routing through the plugin" `Vietmap.routing()` in `vietmap_flutter_plugin` (0.5.2) still calls the legacy route endpoint (`api-version=1.1`). The response shape is the same as Route v4, so the polyline code below works with either. For toll costs (`annotations=toll`) or truck and container profiles, call [Route v4](https://maps.vietmap.vn/docs/map-api/route-version/route-v4/) directly over HTTP. * Decode the returned polyline (Google polyline level 5) > Note: If you need full VietMap map styling (vector tiles, traffic layers, advanced navigation), consider migrating to the full VietMap Map or Navigation SDK (Navigation: [Flutter](https://maps.vietmap.vn/docs/sdk-mobile/Flutter/navigation/) · [Android](https://maps.vietmap.vn/docs/sdk-mobile/sdk-android/navigation/) · [iOS](https://maps.vietmap.vn/docs/sdk-mobile/sdk-ios/navigation/)). This document focuses on a hybrid approach where Google Maps is already embedded. ## 1. Prerequisites 1. VietMap API key: register at https://maps.vietmap.vn/ 2. Add core packages: ```bash flutter pub add google_maps_flutter vietmap_flutter_plugin ``` 3. (Optional) Add `http` only if the app will call YOUR backend proxy: ```bash flutter pub add http ``` 4. Network access: HTTPS only (no special Android network security config). 5. (Recommended) Lightweight backend proxy to protect the VietMap key (see section 9). ## 2. Example structure You can either write raw HTTP code or (recommended) use the official `vietmap_flutter_plugin` to call VietMap APIs. Below we show the plugin approach (still rendering everything on a Google Map). ## 3. Autocomplete (Search) API v4 The new merged search uses v4 endpoint. Basic endpoint: ``` GET https://maps.vietmap.vn/api/autocomplete/v4?apikey=YOUR_KEY&text=QUERY&focus=LAT,LON&display_type=1 ``` Key parameters: * `text`: user query (required) * `focus=lat,lng`: optional bias * `display_type`: choose response style (1 = new format; see official docs) V4 Autocomplete & Search responses (per provided docs) are JSON arrays of objects (up to 10) containing fields such as `ref_id`, `distance`, `address`, `name`, `display`, `boundaries[]`, `categories[]`, `entry_points[]`, and optionally `data_old` / `data_new` (variant objects with the same schema) depending on `display_type` (5 or 6). They DO NOT include latitude/longitude. To draw markers or routes you MUST perform a Place v4 lookup using the returned `ref_id` which yields `lat` & `lng` along with address components (`hs_num`, `street`, `city`, `ward`, ...). Using plugin vs custom HTTP | Aspect | Plugin (`vietmap_flutter_plugin`) | Manual HTTP | |--------|-----------------------------------|-------------| | Boilerplate | Minimal | Higher (models, parsing) | | Future API changes | Abstracted | You must maintain | | Error model | Unified `Either` | Exceptions / custom | | Speed to integrate | Faster | Slower | Install plugin: ```bash flutter pub add vietmap_flutter_plugin ``` Initialize once (e.g. `main()` or first screen `initState`): ```dart Vietmap.getInstance('YOUR_API_KEY'); ``` Available calls (see plugin docs): ```dart Vietmap.autocomplete(VietMapAutoCompleteParams(textSearch: 'Ho Chi Minh')); Vietmap.geoCode(VietMapAutoCompleteParams(textSearch: '197 Tran Phu')); Vietmap.place('ref_id'); Vietmap.routing(VietMapRoutingParams(points: [LatLng(10.76,106.7), LatLng(10.78,106.71)])); Vietmap.reverse(LatLng(10.77,106.70)); Vietmap.matrix(VietmapMatrixParams(points: [...], sourcePoints: [...], destinationPoints: [...])) ; ``` Handle result (Either): ```dart final res = await Vietmap.autocomplete(VietMapAutoCompleteParams(textSearch: query)); res.fold( (failure) => debugPrint('Error: ${failure.message}'), (list) => debugPrint('Suggestions: ${list.length}'), ); ``` Example: routing with error handling ```dart final routingEither = await Vietmap.routing( VietMapRoutingParams(points: [LatLng(originLat, originLng), LatLng(destLat, destLng)]) ); routingEither.fold( (failure) => ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Route error: ${failure.message}'))), (data) => debugPrint('Distance: ${data.paths.first.distance} m'), ); ``` ## 4. Display on Google Map Example widget (plugin-based): autocomplete search, pick 2 points (origin/destination) and draw a route. ```dart import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:vietmap_flutter_plugin/vietmap_flutter_plugin.dart' hide LatLng; import 'package:vietmap_flutter_plugin/vietmap_flutter_plugin.dart' as vietmap show LatLng; class VietMapGoogleIntegrationPage extends StatefulWidget { const VietMapGoogleIntegrationPage({super.key}); @override State createState() => _VietMapGoogleIntegrationPageState(); } class _VietMapGoogleIntegrationPageState extends State { GoogleMapController? _mapController; final Set _markers = {}; final Set _polylines = {}; final TextEditingController _searchCtl = TextEditingController(); List _results = []; VietmapAutocompleteModel? _fromAuto; VietmapAutocompleteModel? _toAuto; VietmapPlaceModel? _fromDetail; VietmapPlaceModel? _toDetail; bool _loading = false; @override void initState() { super.initState(); Vietmap.getInstance('YOUR_VIETMAP_API_KEY'); } Future _search() async { final q = _searchCtl.text.trim(); if (q.isEmpty) return; setState(() => _loading = true); final either = await Vietmap.autocomplete( VietMapAutoCompleteParams(textSearch: q), ); either.fold( (f) => ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text('Search error: ${f.toString()}'))), (list) => _results = list, ); setState(() => _loading = false); } Future _select(VietmapAutocompleteModel item) async { setState(() => _loading = true); Future fetch(bool from) async { final refId = item.refId; if (refId == null) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Invalid place reference id')), ); return; } final pe = await Vietmap.place(refId); pe.fold( (f) => ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text('Place error: ${f.toString()}'))), (d) { if (from) { _fromDetail = d; } else { _toDetail = d; } _addMarker( d, from ? 'from' : 'to', from ? BitmapDescriptor.hueAzure : BitmapDescriptor.hueRed, ); }, ); } if (_fromAuto == null) { _fromAuto = item; await fetch(true); } else if (_toAuto == null) { _toAuto = item; await fetch(false); await _buildRoute(); } else { _reset(); _fromAuto = item; await fetch(true); } setState(() => _loading = false); } void _addMarker(VietmapPlaceModel d, String id, double hue) { if (d.lat == null || d.lng == null) return; final m = Marker( markerId: MarkerId(id), position: LatLng(d.lat!.toDouble(), d.lng!.toDouble()), infoWindow: InfoWindow(title: d.display), icon: BitmapDescriptor.defaultMarkerWithHue(hue), ); _markers.removeWhere((x) => x.markerId == m.markerId); _markers.add(m); } Future _buildRoute() async { if (_fromDetail == null || _toDetail == null) return; final either = await Vietmap.routing( VietMapRoutingParams( points: [ vietmap.LatLng( _fromDetail!.lat!.toDouble(), _fromDetail!.lng!.toDouble(), ), vietmap.LatLng( _toDetail!.lat!.toDouble(), _toDetail!.lng!.toDouble(), ), ], ), ); either.fold( (f) => ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text('Route error: ${f.toString()}'))), (r) { if (r.paths?.isEmpty ?? true) return; final path = r.paths!.first; final points = path.points; final pointsDecoded = points != null ? _decodePolyline6(points, 1e5) : []; final poly = Polyline( polylineId: const PolylineId('route'), color: Colors.blue, width: 4, points: pointsDecoded, ); setState( () => _polylines ..clear() ..add(poly), ); _fit(pointsDecoded); }, ); } void _fit(List pts) { if (_mapController == null || pts.isEmpty) return; double minLat = pts.first.latitude, maxLat = pts.first.latitude, minLng = pts.first.longitude, maxLng = pts.first.longitude; for (final p in pts) { if (p.latitude < minLat) minLat = p.latitude; if (p.latitude > maxLat) maxLat = p.latitude; if (p.longitude < minLng) minLng = p.longitude; if (p.longitude > maxLng) maxLng = p.longitude; } _mapController!.animateCamera( CameraUpdate.newLatLngBounds( LatLngBounds( southwest: LatLng(minLat, minLng), northeast: LatLng(maxLat, maxLng), ), 48, ), ); } void _reset() { setState(() { _fromAuto = null; _toAuto = null; _fromDetail = null; _toDetail = null; _markers.clear(); _polylines.clear(); }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('VietMap Plugin + Google Maps'), actions: [ IconButton(onPressed: _reset, icon: const Icon(Icons.refresh)), ], ), body: Column( children: [ Padding( padding: const EdgeInsets.all(8), child: Row( children: [ Expanded( child: TextField( controller: _searchCtl, decoration: const InputDecoration( border: OutlineInputBorder(), hintText: 'Search (v4 via plugin)', ), onSubmitted: (_) => _search(), ), ), const SizedBox(width: 8), ElevatedButton( onPressed: _loading ? null : _search, child: _loading ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2), ) : const Text('Go'), ), ], ), ), if (_results.isNotEmpty) SizedBox( height: 180, child: ListView.separated( itemCount: _results.length, separatorBuilder: (_, __) => const Divider(height: 1), itemBuilder: (_, i) { final r = _results[i]; return ListTile( dense: true, title: Text( r.display?.isNotEmpty == true ? r.display! : (r.name ?? ''), ), subtitle: Text(r.address ?? ''), onTap: () => _select(r), ); }, ), ), if (_fromDetail != null || _toDetail != null) Container( color: Colors.white, padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), child: Row( children: [ Expanded( child: Text( 'From: ${_fromDetail?.display ?? '-'}', maxLines: 1, overflow: TextOverflow.ellipsis, ), ), const SizedBox(width: 4), Expanded( child: Text( 'To: ${_toDetail?.display ?? '-'}', maxLines: 1, overflow: TextOverflow.ellipsis, ), ), ], ), ), Expanded( child: GoogleMap( initialCameraPosition: CameraPosition( target: LatLng(10.776889, 106.700806), zoom: 12, ), onMapCreated: (c) => _mapController = c, markers: _markers, polylines: _polylines, myLocationEnabled: true, compassEnabled: true, ), ), ], ), ); } static List _decodePolyline6(String encoded, double mul) { // precision var inv = 1.0 / mul; var decoded = []; var previous = [0, 0]; var i = 0; // for each byte while (i < encoded.length) { // for each coord (lat, lon) var latLng = [0, 0]; for (var j = 0; j < 2; j++) { var shift = 0; var byte = 0x20; // keep decoding bytes until you have this coord while (byte >= 0x20) { byte = encoded.codeUnitAt(i++) - 63; latLng[j] |= (byte & 0x1f) << shift; shift += 5; } // add previous offset to get the final value and remember for the next one latLng[j] = previous[j] + (latLng[j] & 1 == 1 ? ~(latLng[j] >> 1) : (latLng[j] >> 1)); previous[j] = latLng[j]; } // scale by precision and chop off long coords also flip the positions so // it's the far more standard lon,lat instead of lat,lon var temp = LatLng(latLng[0] * inv, latLng[1] * inv); decoded.add(temp); } // hand back the list of coordinates return decoded; } } ``` ## 5. Notes & best practices * Apply debounce (e.g. 300 ms) + caching to reduce Autocomplete quota usage * For biasing Autocomplete, pass `focus=lat,lng` (approx user location) * Always fetch coordinates via Place v4 (`Vietmap.place(refId)`) because Autocomplete/Search v4 results omit coordinates * Centralize handling of `Either` to reduce duplicated `fold` * Debounce text input (e.g. 300–400 ms) before calling `Vietmap.autocomplete` * Handle errors gracefully; surface limited user-facing messages ## 6. Extensions | Requirement | Approach | |-------------|----------| | Turn-by-turn navigation | VietMap Navigation SDK ([Flutter](https://maps.vietmap.vn/docs/sdk-mobile/Flutter/navigation/)) | | Toll / fee estimation | Route v4 with `annotations=toll` ([Route v4](https://maps.vietmap.vn/docs/map-api/route-version/route-v4/)) | | Multi-stop optimization | [TSP v4](https://maps.vietmap.vn/docs/map-api/tsp-version/tsp-v4/) for one vehicle, [VRP](https://maps.vietmap.vn/docs/map-api/vrp/) for a fleet, or [Matrix v4](https://maps.vietmap.vn/docs/map-api/matrix-version/matrix-v4/) + your own solver | | Truck constraints | Include `vehicle=truck` + required fields (see [Route v4](https://maps.vietmap.vn/docs/map-api/route-version/route-v4/)) | ## 7. Basic error handling Using the plugin you normally handle `Either`: ```dart final result = await Vietmap.routing( VietMapRoutingParams(points:[LatLng(oLat,oLng),LatLng(dLat,dLng)]) ); result.fold( (failure){ ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Route error: ${failure.message}')) ); }, (data){ if(data.paths.isEmpty){ debugPrint('No path'); return; } debugPrint('Distance: ${data.paths.first.distance} m'); } ); ``` ## 8. Secure backend proxy (recommended) Never hardcode sensitive API keys in the client. A minimal backend can: 1. Accept restricted parameters from the mobile app (e.g., `query`, `origin`, `destination`). 2. Append the server-held VietMap API key. 3. Enforce rate limiting, auth (JWT/session), and input validation. 4. Optionally cache popular responses (e.g., Autocomplete for identical query+focus for 30–60s). ## 9. Conclusion You can quickly enrich an existing Google Maps Flutter app using VietMap [Autocomplete v4](https://maps.vietmap.vn/docs/map-api/autocomplete-version/autocomplete-v4/) and [Route v4](https://maps.vietmap.vn/docs/map-api/route-version/route-v4/) without a full migration. As needs grow (vector styling, offline, navigation), consider the full Navigation SDK: [Flutter](https://maps.vietmap.vn/docs/sdk-mobile/Flutter/navigation/) · [Android](https://maps.vietmap.vn/docs/sdk-mobile/sdk-android/navigation/) · [iOS](https://maps.vietmap.vn/docs/sdk-mobile/sdk-ios/navigation/). --- © VietMap