2GIS Maps Flutter Integration Guide: Maps for CIS Apps (2026)
2GIS has the most accurate and detailed maps for cities in Russia, Kazakhstan, Kyrgyzstan, and CIS countries - better street-level data, interior maps of malls, more accurate building addresses, and offline map support. For apps targeting these markets, 2GIS is often a better choice than Google Maps.
This guide covers the complete Flutter integration using the official 2GIS Flutter SDK (dgis_maps_flutter).
Why 2GIS Over Google Maps for CIS
| Feature | 2GIS | Google Maps |
|---|---|---|
| CIS street-level accuracy | ✅ Best | Good |
| Courtyard/internal road data | ✅ Excellent | Partial |
| New residential district coverage | ✅ Fast updates | Slower |
| Indoor maps (malls, airports) | ✅ Available | Limited |
| Offline maps | ✅ Available | Limited (paid) |
| Public transit CIS cities | ✅ Excellent | Good |
| Traffic data Russia/CIS | ✅ Real-time | Good |
| Price for CIS traffic | Free tier generous | Pay per request |
| Works without Google Services | ✅ Yes | ❌ No |
The last point matters on some Android devices (Huawei, etc.) where Google Play Services are unavailable.
Setup
1. Get API Key
Register at partner.2gis.com to get your API key (free tier available).
2. Add Dependency
# pubspec.yaml
dependencies:
dgis_maps_flutter: ^1.7.0
3. Android Configuration
<!-- android/app/src/main/AndroidManifest.xml -->
<manifest>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<application>
<!-- 2GIS API Key -->
<meta-data
android:name="DgisMapApiKey"
android:value="YOUR_API_KEY_HERE"/>
<!-- ... -->
</application>
</manifest>
Set minimum SDK version:
// android/app/build.gradle
defaultConfig {
minSdkVersion 21
}
4. iOS Configuration
<!-- ios/Runner/Info.plist -->
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs location access to show you on the map</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>This app needs location access for navigation</string>
Basic Map Display
import 'package:dgis_maps_flutter/dgis_maps_flutter.dart';
import 'package:flutter/material.dart';
class MapScreen extends StatefulWidget {
const MapScreen({super.key});
@override
State<MapScreen> createState() => _MapScreenState();
}
class _MapScreenState extends State<MapScreen> {
late DGisMapController _mapController;
// Bishkek city center
static const CameraPosition _initialPosition = CameraPosition(
target: LatLng(42.8746, 74.5698),
zoom: 13,
);
@override
Widget build(BuildContext context) {
return Scaffold(
body: DGisMap(
initialCameraPosition: _initialPosition,
apiKey: 'YOUR_API_KEY',
onMapCreated: (controller) {
_mapController = controller;
},
myLocationEnabled: true,
myLocationButtonEnabled: true,
trafficEnabled: true, // Show traffic layer
),
);
}
}
Adding Markers
// Add a single marker
Future<void> addMarker(LatLng position, String title) async {
await _mapController.addMarker(
MarkerOptions(
position: position,
infoWindowText: InfoWindowText(title: title),
icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueRed),
),
);
}
// Add multiple markers (e.g., store locations)
Future<void> addStoreMarkers(List<Store> stores) async {
final markers = stores.map((store) => Marker(
markerId: MarkerId(store.id),
position: LatLng(store.lat, store.lng),
infoWindow: InfoWindow(
title: store.name,
snippet: store.address,
onTap: () => _onStoreTap(store),
),
icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueBlue),
)).toSet();
await _mapController.addMarkers(markers);
}
// Custom marker icon
Future<BitmapDescriptor> _createCustomMarker(String text) async {
// Create a custom marker from an asset
return await BitmapDescriptor.fromAssetImage(
const ImageConfiguration(size: Size(48, 48)),
'assets/images/map_pin.png',
);
}
User Location and Geofencing
import 'package:geolocator/geolocator.dart';
class LocationService {
Future<Position> getCurrentLocation() async {
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) throw Exception('Location services disabled');
LocationPermission permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
throw Exception('Location permission denied');
}
}
return await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high,
);
}
// Move camera to user location
Future<void> goToMyLocation(DGisMapController controller) async {
final position = await getCurrentLocation();
await controller.animateCamera(
CameraUpdate.newCameraPosition(
CameraPosition(
target: LatLng(position.latitude, position.longitude),
zoom: 16,
),
),
);
}
// Stream location updates (for tracking)
Stream<Position> trackLocation() {
return Geolocator.getPositionStream(
locationSettings: const LocationSettings(
accuracy: LocationAccuracy.high,
distanceFilter: 10, // Update every 10 meters
),
);
}
}
Drawing Routes
// Draw polyline route between two points
Future<void> drawRoute(LatLng from, LatLng to) async {
// 2GIS routing API
final routePoints = await _getRoutePoints(from, to);
await _mapController.addPolyline(
PolylineOptions(
points: routePoints,
color: Colors.blue,
width: 4,
),
);
}
// Get route via 2GIS Directions API (REST)
Future<List<LatLng>> _getRoutePoints(LatLng from, LatLng to) async {
final response = await http.get(Uri.parse(
'https://routing.api.2gis.com/routing/7.0.0/global'
'?key=YOUR_API_KEY'
'&points=${from.longitude},${from.latitude}'
'|${to.longitude},${to.latitude}'
'&type=pedestrian', // or 'car', 'bicycle'
));
final data = jsonDecode(response.body);
final points = data['result'][0]['paths'][0]['points']['coordinates'] as List;
return points.map((p) => LatLng(p[1] as double, p[0] as double)).toList();
}
Address Search (Geocoding)
// Search for addresses / places
Future<List<SearchResult>> searchAddress(String query, LatLng nearLocation) async {
final response = await http.get(Uri.parse(
'https://catalog.api.2gis.com/3.0/suggest'
'?key=YOUR_API_KEY'
'&q=${Uri.encodeComponent(query)}'
'&location=${nearLocation.longitude},${nearLocation.latitude}'
'&radius=10000' // Search radius in meters
'&locale=ru' // ru, en, kk (Kazakh)
));
final data = jsonDecode(response.body);
return (data['result']['items'] as List)
.map((item) => SearchResult(
name: item['name'],
fullName: item['full_name'],
lat: item['point']?['lat'],
lng: item['point']?['lon'],
))
.where((r) => r.lat != null)
.toList();
}
// Reverse geocoding: coordinates → address
Future<String> reverseGeocode(LatLng position) async {
final response = await http.get(Uri.parse(
'https://catalog.api.2gis.com/3.0/items/geocode'
'?key=YOUR_API_KEY'
'&lat=${position.latitude}'
'&lon=${position.longitude}'
'&fields=items.point,items.address'
));
final data = jsonDecode(response.body);
return data['result']['items'][0]['address_name'] ?? 'Unknown location';
}
Address Input Widget with Autocomplete
class AddressSearchField extends StatefulWidget {
final Function(String address, LatLng position) onSelected;
final LatLng centerLocation;
@override
Widget build(BuildContext context) {
return TextField(
decoration: const InputDecoration(
hintText: 'Enter address',
prefixIcon: Icon(Icons.search),
),
onChanged: (query) async {
if (query.length < 3) return;
final results = await searchAddress(query, widget.centerLocation);
setState(() => _suggestions = results);
},
);
// Show suggestions in dropdown below
}
}
Yandex MapKit Alternative
If you need Yandex-specific features (traffic in Russia), the Yandex MapKit Flutter SDK is also available:
dependencies:
yandex_mapkit: ^3.3.0
Both 2GIS and Yandex MapKit cover CIS well. 2GIS has better interior maps and offline support. Yandex has better real-time traffic data for Russian cities and deeper integration with Yandex services.
API Rate Limits and Pricing (2026)
| API | Free Tier | Paid |
|---|---|---|
| Map tiles | 50,000 loads/month | $0.80 per 1,000 |
| Search / Geocoding | 10,000 requests/month | $0.50 per 1,000 |
| Routing | 10,000 requests/month | $0.60 per 1,000 |
| Traffic | Included with map | - |
For most startup apps, the free tier is sufficient during development and early production.