Flutter'da Harita Entegrasyonu: Google Maps ve flutter_map
Harita entegrasyonu için iki ana seçenek vardır: google_maps_flutter (Google Maps API, ücretli ama kapsamlı) ve flutter_map (OpenStreetMap tabanlı, tamamen açık kaynak ve ücretsiz). Her ikisini de örneklerle inceleyeceğiz.
google_maps_flutter: Kurulum ve Temel Kullanım
// pubspec:
// google_maps_flutter: ^2.9.0
// geolocator: ^13.0.2
// geocoding: ^3.0.0
// Android: google_services.json ve API key
// android/app/src/main/AndroidManifest.xml
// <meta-data android:name="com.google.android.geo.API_KEY"
// android:value="YOUR_API_KEY"/>
// iOS: AppDelegate.swift
// GMSServices.provideAPIKey("YOUR_API_KEY")
class MapPage extends StatefulWidget {
const MapPage({super.key});
@override State<MapPage> createState() => _MapPageState();
}
class _MapPageState extends State<MapPage> {
GoogleMapController? _mapController;
final Set<Marker> _markers = {};
final Set<Polyline> _polylines = {};
LatLng? _userLocation;
static const _istanbul = LatLng(41.0082, 28.9784);
@override
void initState() {
super.initState();
_loadUserLocation();
}
Future<void> _loadUserLocation() async {
final permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) return;
final pos = await Geolocator.getCurrentPosition(
locationSettings: const LocationSettings(
accuracy: LocationAccuracy.high,
distanceFilter: 10,
),
);
final latLng = LatLng(pos.latitude, pos.longitude);
setState(() {
_userLocation = latLng;
_markers.add(Marker(
markerId: const MarkerId('user'),
position: latLng,
icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueBlue),
infoWindow: const InfoWindow(title: 'Konumunuz'),
));
});
_mapController?.animateCamera(CameraUpdate.newLatLngZoom(latLng, 15));
}
void _addBusinessMarker(Business business) {
_markers.add(Marker(
markerId: MarkerId(business.id),
position: LatLng(business.lat, business.lng),
icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueOrange),
infoWindow: InfoWindow(
title: business.name,
snippet: business.address,
onTap: () => _showBusinessSheet(business),
),
));
setState(() {});
}
void _drawRoute(List<LatLng> points) {
setState(() {
_polylines.add(Polyline(
polylineId: const PolylineId('route'),
points: points,
color: Colors.blue,
width: 5,
patterns: [PatternItem.dash(30), PatternItem.gap(10)],
));
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: GoogleMap(
initialCameraPosition: const CameraPosition(
target: _istanbul,
zoom: 12,
),
markers: _markers,
polylines: _polylines,
myLocationEnabled: true,
myLocationButtonEnabled: true,
mapType: MapType.normal,
trafficEnabled: false,
onMapCreated: (controller) {
_mapController = controller;
// Özel harita stili (JSON)
controller.setMapStyle(_darkMapStyle);
},
onTap: (latLng) {
debugPrint('Tıklanan: ${latLng.latitude}, ${latLng.longitude}');
},
onLongPress: (latLng) => _addCustomMarker(latLng),
),
floatingActionButton: Column(
mainAxisSize: MainAxisSize.min,
children: [
FloatingActionButton.small(
onPressed: _loadUserLocation,
child: const Icon(Icons.my_location),
),
const SizedBox(height: 8),
FloatingActionButton(
onPressed: () => _mapController?.animateCamera(
CameraUpdate.newLatLngZoom(_istanbul, 12)),
child: const Icon(Icons.map),
),
],
),
);
}
void _addCustomMarker(LatLng position) async {
// Özel marker görseli
final icon = await BitmapDescriptor.asset(
const ImageConfiguration(size: Size(48, 48)),
'assets/icons/pin.png',
);
setState(() {
_markers.add(Marker(
markerId: MarkerId(position.toString()),
position: position,
icon: icon,
));
});
}
static const _darkMapStyle = '''[
{"elementType": "geometry", "stylers": [{"color": "#212121"}]},
{"elementType": "labels.text.fill", "stylers": [{"color": "#757575"}]}
]''';
}
flutter_map: Ücretsiz OpenStreetMap Alternatifi
// pubspec:
// flutter_map: ^7.0.2
// latlong2: ^0.9.1
class OpenStreetMapPage extends StatefulWidget {
const OpenStreetMapPage({super.key});
@override State<OpenStreetMapPage> createState() => _OSMPageState();
}
class _OSMPageState extends State<OpenStreetMapPage> {
final _mapController = MapController();
final List<Marker> _markers = [];
final List<Polyline> _routes = [];
@override
Widget build(BuildContext context) {
return Scaffold(
body: FlutterMap(
mapController: _mapController,
options: const MapOptions(
initialCenter: LatLng(41.0082, 28.9784),
initialZoom: 13,
minZoom: 3,
maxZoom: 18,
),
children: [
// Temel harita katmanı
TileLayer(
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
userAgentPackageName: 'com.example.app',
maxZoom: 19,
// Offline tile caching için flutter_map_tile_caching
),
// Rotalar
PolylineLayer(
polylines: _routes,
),
// Marker'lar
MarkerLayer(markers: _markers),
// Kullanıcı konumu halkası
CircleLayer(
circles: [
if (_userPos != null)
CircleMarker(
point: _userPos!,
radius: 80,
useRadiusInMeter: true,
color: Colors.blue.withOpacity(0.2),
borderColor: Colors.blue,
borderStrokeWidth: 2,
),
],
),
// Ölçek çubuğu
const RichAttributionWidget(
attributions: [
TextSourceAttribution('OpenStreetMap contributors'),
],
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () => _mapController.move(LatLng(41.0082, 28.9784), 13),
child: const Icon(Icons.center_focus_strong),
),
);
}
LatLng? _userPos;
void addRoute(List<LatLng> points, {Color color = Colors.blue}) {
setState(() {
_routes.add(Polyline(
points: points,
strokeWidth: 4,
color: color,
));
});
}
void addMarker(LatLng pos, {required Widget child}) {
setState(() {
_markers.add(Marker(
point: pos,
width: 48,
height: 48,
child: child,
));
});
}
}
Konum Takibi: Anlık ve Sürekli
@riverpod
class LocationNotifier extends _$LocationNotifier {
StreamSubscription<Position>? _sub;
@override
LocationState build() {
ref.onDispose(() => _sub?.cancel());
return const LocationState();
}
Future<void> startTracking() async {
final permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
final result = await Geolocator.requestPermission();
if (result == LocationPermission.denied) {
state = state.copyWith(error: 'Konum izni reddedildi');
return;
}
}
_sub = Geolocator.getPositionStream(
locationSettings: const LocationSettings(
accuracy: LocationAccuracy.high,
distanceFilter: 5, // en az 5m değişince bildir
),
).listen((position) {
final latLng = LatLng(position.latitude, position.longitude);
state = state.copyWith(
currentPosition: latLng,
path: [...state.path, latLng],
speed: position.speed, // m/s
heading: position.heading,
);
});
}
void stopTracking() {
_sub?.cancel();
_sub = null;
}
// İki nokta arası mesafe (metre)
double distanceTo(LatLng target) {
if (state.currentPosition == null) return 0;
return Geolocator.distanceBetween(
state.currentPosition!.latitude, state.currentPosition!.longitude,
target.latitude, target.longitude,
);
}
}
Google Maps güçlü ama API maliyeti vardır; flutter_map ücretsiz ve açık kaynak ama bazı özellikler için ek paket gerekir. Konum izinlerini hem Android (ACCESS_FINE_LOCATION) hem iOS (NSLocationWhenInUseUsageDescription) için doğru yapılandırın. Pil tüketimini azaltmak için distanceFilter değerini kullanım senaryosuna göre ayarlayın.