Hi guys! I hope everyone is doing great!
I'm working on a custom OSM map widget and I'm encountering an issue with button interactivity. The map displays correctly, but the floating action buttons (zoom in/out, user location, traffic toggle) are not responding to taps. Here's a full code of how I'm implementing the widget:
// Automatic FlutterFlow imports
import '/backend/backend.dart';
import '/backend/schema/structs/index.dart';
import '/backend/supabase/supabase.dart';
import '/flutter_flow/flutter_flow_theme.dart';
import '/flutter_flow/flutter_flow_util.dart';
import '/custom_code/widgets/index.dart'; // Imports other custom widgets
import '/custom_code/actions/index.dart'; // Imports custom actions
import '/flutter_flow/custom_functions.dart'; // Imports custom functions
import 'package:flutter/material.dart';
// Begin custom widget code
// DO NOT REMOVE OR MODIFY THE CODE ABOVE!
import '/custom_code/widgets/index.dart';
import '/custom_code/actions/index.dart';
import '/flutter_flow/custom_functions.dart';
import 'package:flutter_osm_plugin/flutter_osm_plugin.dart' as osm;
import 'package:supabase_flutter/supabase_flutter.dart';
import 'dart:convert';
class OSMMapWidget extends StatefulWidget {
const OSMMapWidget({
Key? key,
this.width,
this.height,
required this.latitudes,
required this.longitudes,
required this.titles,
required this.descriptions,
required this.addresses,
required this.imageUrls,
this.initialZoom = 14.0,
required this.defaultLocation,
required this.isInteractive,
required this.allowZooming,
required this.showZoomButtons,
required this.showUserLocation,
required this.showCompass,
required this.showTraffic,
required this.centerOnMarkerTap,
}) : super(key: key);
final double? width;
final double? height;
final List<double> latitudes;
final List<double> longitudes;
final List<String> titles;
final List<String> descriptions;
final List<String> addresses;
final List<String> imageUrls;
final double initialZoom;
final LatLng defaultLocation;
final bool isInteractive;
final bool allowZooming;
final bool showZoomButtons;
final bool showUserLocation;
final bool showCompass;
final bool showTraffic;
final bool centerOnMarkerTap;
@override
_OSMMapWidgetState createState() => _OSMMapWidgetState();
}
class _OSMMapWidgetState extends State<OSMMapWidget> {
late osm.MapController _mapController;
bool _isBottomSheetVisible = false;
List<MarkerData> markerDataList = [];
bool _trafficEnabled = false;
Map<String, osm.GeoPoint> _markers = {};
@override
void initState() {
super.initState();
_mapController = osm.MapController(
initPosition: osm.GeoPoint(
latitude: widget.defaultLocation.latitude,
longitude: widget.defaultLocation.longitude,
),
);
_createMarkerDataList();
if (widget.showTraffic) {
_trafficEnabled = true;
}
WidgetsBinding.instance.addPostFrameCallback((_) {
addMarkers();
});
}
void _createMarkerDataList() {
for (int i = 0; i < widget.latitudes.length; i++) {
if (i < widget.longitudes.length &&
i < widget.titles.length &&
i < widget.descriptions.length &&
i < widget.addresses.length &&
i < widget.imageUrls.length) {
markerDataList.add(MarkerData(
id: i.toString(),
latitude: widget.latitudes[i],
longitude: widget.longitudes[i],
title: widget.titles[i],
description: widget.descriptions[i],
address: widget.addresses[i],
imageUrl: widget.imageUrls[i],
));
}
}
}
Future<void> addMarkers() async {
try {
for (var markerData in markerDataList) {
final geoPoint = osm.GeoPoint(
latitude: markerData.latitude,
longitude: markerData.longitude,
);
await _mapController.addMarker(
geoPoint,
markerIcon: osm.MarkerIcon(
icon: Icon(
Icons.location_on,
color: Colors.red,
size: 48,
),
),
);
_markers[markerData.id] = geoPoint;
}
if (markerDataList.isNotEmpty) {
await _zoomToFitMarkers();
}
} catch (e) {
print('Error adding markers: $e');
}
}
Future<void> _zoomToFitMarkers() async {
if (_markers.isEmpty) return;
try {
final bounds = osm.BoundingBox.fromGeoPoints(_markers.values.toList());
await _mapController.zoomToBoundingBox(bounds);
} catch (e) {
print('Error zooming to bounds: $e');
}
}
@override
Widget build(BuildContext context) {
return Material(
child: Stack(
children: [
SizedBox(
width: widget.width ?? double.infinity,
height: widget.height ?? double.infinity,
child: osm.OSMFlutter(
controller: _mapController,
onGeoPointClicked: (geoPoint) => _handleMapTap(geoPoint),
onMapIsReady: (isReady) {
if (isReady) addMarkers();
},
osmOption: osm.OSMOption(
userTrackingOption: osm.UserTrackingOption(
enableTracking: widget.showUserLocation,
unFollowUser: true,
),
zoomOption: osm.ZoomOption(
initZoom: widget.initialZoom,
minZoomLevel: 2,
maxZoomLevel: 19,
stepZoom: 1.0,
),
userLocationMarker: widget.showUserLocation
? osm.UserLocationMaker(
personMarker: osm.MarkerIcon(
icon: Icon(
Icons.location_history,
color: Colors.blue,
size: 48,
),
),
directionArrowMarker: osm.MarkerIcon(
icon: Icon(
Icons.navigation,
size: 48,
),
),
)
: null,
roadConfiguration: osm.RoadOption(
roadColor: Colors.blueGrey,
),
markerOption: osm.MarkerOption(
defaultMarker: osm.MarkerIcon(
icon: Icon(
Icons.location_on,
color: Colors.red,
size: 48,
),
),
),
showDefaultInfoWindow: false,
),
),
),
if (!_isBottomSheetVisible && widget.isInteractive)
Positioned(
right: 16,
bottom: 16,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (widget.showZoomButtons && widget.allowZooming) ...[
_buildFloatingButton(
"zoomIn",
Icons.add,
() async => await _mapController.zoomIn(),
),
SizedBox(height: 8),
_buildFloatingButton(
"zoomOut",
Icons.remove,
() async => await _mapController.zoomOut(),
),
SizedBox(height: 8),
],
if (widget.showUserLocation)
_buildFloatingButton(
"location",
Icons.my_location,
_goToUserLocation,
),
if (widget.showTraffic) ...[
SizedBox(height: 8),
_buildFloatingButton(
"traffic",
_trafficEnabled ? Icons.traffic : Icons.traffic_outlined,
_toggleTraffic,
),
],
],
),
),
],
),
);
}
Widget _buildFloatingButton(
String tag, IconData icon, VoidCallback onPressed) {
return FloatingActionButton(
heroTag: tag,
mini: true,
child: Icon(icon),
onPressed: onPressed,
);
}
Future<void> _goToUserLocation() async {
try {
await _mapController.currentLocation();
await _mapController.enableTracking();
} catch (e) {
print('Error getting user location: $e');
}
}
void _handleMapTap(osm.GeoPoint geoPoint) {
final tappedMarker = _findNearestMarker(geoPoint);
if (tappedMarker != null) {
_showBottomSheet(tappedMarker);
if (widget.centerOnMarkerTap) {
_mapController.goToLocation(osm.GeoPoint(
latitude: tappedMarker.latitude,
longitude: tappedMarker.longitude,
));
}
}
}
MarkerData? _findNearestMarker(osm.GeoPoint tappedPoint) {
const double threshold = 0.001; // Approximately 111 meters
for (var marker in markerDataList) {
final distance = _calculateDistance(
tappedPoint.latitude,
tappedPoint.longitude,
marker.latitude,
marker.longitude,
);
if (distance < threshold) {
return marker;
}
}
return null;
}
double _calculateDistance(
double lat1, double lon1, double lat2, double lon2) {
return ((lat1 - lat2).abs() + (lon1 - lon2).abs()) / 2;
}
void _toggleTraffic() {
setState(() {
_trafficEnabled = !_trafficEnabled;
// Note: OSM Flutter plugin doesn't directly support traffic layers
});
}
void _showBottomSheet(MarkerData markerData) {
setState(() {
_isBottomSheetVisible = true;
});
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (context) => BottomSheetContent(
markerData: markerData,
onClose: () {
Navigator.pop(context);
setState(() {
_isBottomSheetVisible = false;
});
},
onViewDetails: _navigateToPostDetails,
),
).then((_) {
setState(() {
_isBottomSheetVisible = false;
});
});
}
Future<void> _navigateToPostDetails(MarkerData markerData) async {
try {
final supabase = Supabase.instance.client;
final likes = await fetchLikes(supabase, markerData.id);
context.pushNamed(
'PostDetails',
queryParameters: {
'postId': markerData.id,
'title': markerData.title,
'description': markerData.description,
'address': markerData.address,
'imageUrl': markerData.imageUrl,
'latitude': markerData.latitude.toString(),
'longitude': markerData.longitude.toString(),
'likes': jsonEncode(likes),
},
);
} catch (e) {
print('Error navigating to PostDetails: $e');
}
}
Future<List<Map<String, dynamic>>> fetchLikes(
SupabaseClient supabase, String postId) async {
try {
final response =
await supabase.from('likes').select().eq('post_id', postId);
return (response as List).cast<Map<String, dynamic>>();
} catch (e) {
print('Error fetching likes: $e');
return [];
}
}
@override
void dispose() {
_mapController.dispose();
super.dispose();
}
}
class MarkerData {
final String id;
final double latitude;
final double longitude;
final String title;
final String description;
final String address;
final String imageUrl;
MarkerData({
required this.id,
required this.latitude,
required this.longitude,
required this.title,
required this.description,
required this.address,
required this.imageUrl,
});
}
class BottomSheetContent extends StatelessWidget {
final MarkerData markerData;
final VoidCallback onClose;
final Function(MarkerData) onViewDetails;
const BottomSheetContent({
Key? key,
required this.markerData,
required this.onClose,
required this.onViewDetails,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return DraggableScrollableSheet(
initialChildSize: 0.6,
minChildSize: 0.2,
maxChildSize: 0.9,
builder: (context, controller) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
child: ListView(
controller: controller,
padding: EdgeInsets.all(16),
children: [
if (markerData.imageUrl.isNotEmpty)
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.network(
markerData.imageUrl,
width: double.infinity,
height: 200,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) => Container(
height: 200,
color: Colors.grey[300],
child: Icon(Icons.error),
),
),
),
SizedBox(height: 16),
Text(
markerData.title,
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
SizedBox(height: 8),
Text(markerData.address),
SizedBox(height: 16),
Text(markerData.description),
SizedBox(height: 24),
ElevatedButton(
onPressed: () {
onClose();
onViewDetails(markerData);
},
child: Text('View Details'),
),
],
),
);
},
);
}
}
The buttons are visible but not responding to taps. Has anyone encountered a similar issue or have suggestions on how to make these buttons interactive within a custom widget in FlutterFlow? Any help or insights would be greatly appreciated.
Thanks in advance!
Project's URL: https://foodshareclub.flutterflow.app/map