liodali / osm_flutter

OpenStreetMap plugin for flutter
https://pub.dev/packages/flutter_osm_plugin
MIT License
240 stars 98 forks source link

LateInitializationError: Field '_osmBaseController@291117859' has not been initialized #493

Open RutananaArnold opened 10 months ago

RutananaArnold commented 10 months ago

[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: LateInitializationError: Field '_osmBaseController@291117859' has not been initialized. E/flutter ( 4318): #0 BaseMapController._osmBaseController (package:flutter_osm_interface/src/map_controller/base_map_controller.dart) E/flutter ( 4318): #1 BaseMapController.osmBaseController (package:flutter_osm_interface/src/map_controller/base_map_controller.dart:21:47) E/flutter ( 4318): #2 MapController.myLocation (package:flutter_osm_plugin/src/controller/map_controller.dart:303:18) E/flutter ( 4318): #3 _MapScreenState.initState. (package:safetec_security/screens/map_screen.dart:34:24) E/flutter ( 4318): #4 SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1325:15) E/flutter ( 4318): #5 SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1264:9) E/flutter ( 4318): #6 SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:1113:5) E/flutter ( 4318): #7 _invoke (dart:ui/hooks.dart:312:13) E/flutter ( 4318): #8 PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:383:5) E/flutter ( 4318): #9 _drawFrame (dart:ui/hooks.dart:283:31) E/flutter ( 4318): 2 E/FrameEvents( 4318): updateAcquireFence: Did not find frame.

flutter_osm_plugin: ^0.70.4 keeps returning the above error in the console

and its causing the map fail to zoom to the user location

here is my code

import 'package:flutter/material.dart';
import 'package:flutter_osm_plugin/flutter_osm_plugin.dart';
import 'package:get/get.dart';
import 'package:safetec_security/controllers/maps_controller.dart';

class MapScreen extends StatefulWidget {
  final double destinationLatitude;
  final double destinationLongtude;
  const MapScreen(
      {super.key,
      required this.destinationLatitude,
      required this.destinationLongtude});

  @override
  State<MapScreen> createState() => _MapScreenState();
}

class _MapScreenState extends State<MapScreen> {
  late MapController controller;

  @override
  void initState() {
    super.initState();
    controller = MapController.withUserPosition(
      trackUserLocation: const UserTrackingOption(
        enableTracking: true,
        unFollowUser: false,
      ),
    );
    mapManager.requestLocationPermisionAndCurrentLocation();
    // Schedule the call to zoomMapToUserLocation after the frame has been built
    WidgetsBinding.instance.addPostFrameCallback((_) async {
      print("starting to load map");
      await controller.myLocation().then((value) async {
        print("lat and long: ${value.latitude} ${value.longitude}");
        await controller.currentLocation();
        await mapManager.zoomMapToUserLocation(
          controller: controller,
          destinationLatitude: widget.destinationLatitude,
          destinationLongitude: widget.destinationLongtude,
        );
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return GetBuilder<MapsController>(
        init: MapsController(),
        builder: (mapsManager) {
          return Scaffold(
              body: OSMFlutter(
            controller: controller,
            onMapIsReady: (bool isMapReady) {
              print("map ready: $isMapReady");
              mapsManager.updateIfMapIsReady(isMapReady);
            },
            mapIsLoading: const Center(
              child: Column(
                mainAxisSize: MainAxisSize.min,
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: [
                  CircularProgressIndicator(),
                  Text("Map is Loading..")
                ],
              ),
            ),
            osmOption: const OSMOption(
              showZoomController: true,
              showDefaultInfoWindow: true,
              roadConfiguration: RoadOption(
                roadColor: Colors.yellowAccent,
              ),
            ),
            onLocationChanged: (GeoPoint locationpoints) {
              print(
                  'Current location: lat:${locationpoints.latitude} long: ${locationpoints.longitude}');
              mapsManager.startLocationUpdates(
                  controller: controller,
                  userLocationPoints: locationpoints,
                  destLat: widget.destinationLatitude,
                  destLong: widget.destinationLongtude);
            },
          ));
        });
  }
}
liodali commented 10 months ago

is mapsManager == MapsController() ?

RutananaArnold commented 10 months ago

this is my controller

import 'dart:async'; import 'dart:math';

import 'package:flutter/material.dart'; import 'package:flutter_osm_plugin/flutter_osm_plugin.dart'; import 'package:get/get.dart'; import 'package:latlng/latlng.dart'; import 'package:location/location.dart';

final mapManager = Get.put(MapsController());

class MapsController extends GetxController { double lat = 0.0; double long = 0.0; storeUserNowLocation(LocationData value) { lat = value.latitude!.toDouble(); long = value.longitude!.toDouble(); update(); }

// Location location = Location(); // late StreamSubscription locationSubscription; // List polylinePoints = [];

// to hold location data GeoPoint currentLocationData = GeoPoint(latitude: 0.0, longitude: 0.0);

updateCurrentLocationData({required GeoPoint value}) { currentLocationData = value; update(); }

GeoPoint getCurrentLocationData() { return currentLocationData; }

// Future getUserNowLocation() async { // LocationData locationData = await location.getLocation(); // return locationData; // }

//to request for location permission requestLocationPermisionAndCurrentLocation() async { Location location = Location();

bool serviceEnabled;
PermissionStatus permissionGranted;

serviceEnabled = await location.serviceEnabled();
if (!serviceEnabled) {
  serviceEnabled = await location.requestService();
  if (!serviceEnabled) {
    return;
  }
}

permissionGranted = await location.hasPermission();
if (permissionGranted == PermissionStatus.denied) {
  permissionGranted = await location.requestPermission();
  if (permissionGranted != PermissionStatus.granted) {
    return;
  }
}

return location.getLocation();

}

//using this boolean to confirm whether the map has loaded successfully bool confirmIfMapIsReady = false;

updateIfMapIsReady(bool value) { confirmIfMapIsReady = value; update(); }

bool isMapLoaded() { return confirmIfMapIsReady; }

zoomMapToUserLocation( {required MapController controller, required double destinationLatitude, required double destinationLongitude}) async { // zoom map to current user location // display current user location marker and destination marker //display polyline betwwen the user location and destination Location location = Location();

LocationData locationData = await location.getLocation();
print(
    "current location: lat- ${locationData.latitude} long- ${locationData.longitude}");

// Register the callback to be executed when the map is ready
if (isMapLoaded() == true) {
  // Now that the map is ready, perform additional actions
  // Display current user location marker
  controller.addMarker(
      GeoPoint(
          latitude: locationData.latitude!.toDouble(),
          longitude: locationData.longitude!.toDouble()),
      markerIcon: const MarkerIcon(
        icon: Icon(
          Icons.person_pin_circle,
          color: Colors.blue,
          size: 56,
        ),
      ));
  update();

  // Display destination marker
  controller.addMarker(
    GeoPoint(
        latitude: destinationLatitude, longitude: destinationLongitude),
    markerIcon: const MarkerIcon(
      icon: Icon(
        Icons.flag,
        color: Colors.red,
        size: 56,
      ),
    ),
  );
  update();

  // Display polyline between user location and destination
  controller.drawRoad(
      GeoPoint(
          latitude: locationData.latitude!.toDouble(),
          longitude: locationData.longitude!.toDouble()),
      GeoPoint(
          latitude: destinationLatitude, longitude: destinationLongitude),
      roadType: RoadType.foot);
  update();

  // Optionally, you can zoom to a bounding box that includes both markers
  // LatLngBounds bounds = LatLngBounds.fromPoints([
  //   LatLng(
  //     getCurrentLocationData().latitude,
  //     getCurrentLocationData().longitude,
  //   ),
  //   LatLng(destinationLatitude, destinationLongitude),
  // ]);

  // controller.zoomToBoundingBox(bounds);
  // update();
}

}

//this method is called while the user is moving closer to the destination as their location changes void startLocationUpdates( {required MapController controller, required GeoPoint userLocationPoints, required double destLat, required double destLong}) { List changingLocation = []; // to hold changing user locations as they are moving if (changingLocation.isEmpty) { changingLocation.add(userLocationPoints); update(); } updateCurrentLocationData( value: userLocationPoints); //to store user current cordinates updateCurrentLocation( geoPoints: changingLocation, controller: controller); //to update markers checkGeofence( controller: controller, destLatitude: destLat, destLongitude: destLong, radius: 5); //to check geofence and draw polyline update(); }

void updateCurrentLocation( {required MapController controller, required List geoPoints}) { controller.setStaticPosition(geoPoints, 'currentUserLocation'); controller.changeLocationMarker( oldLocation: GeoPoint( latitude: getCurrentLocationData().latitude, longitude: getCurrentLocationData().longitude), newLocation: GeoPoint( latitude: getCurrentLocationData().latitude, longitude: getCurrentLocationData().longitude), ); update();

// osmController.setMapState(
//   initPosition: currentLocation,
// );

}

void checkGeofence( {required MapController controller, required double destLatitude, required double destLongitude, required double radius}) { double distance = calculateDistance( currentLocationData.latitude, currentLocationData.longitude, destLatitude, destLongitude, );

if (distance <= radius) {
  print('User has arrived!');
}

// Check if the user is moving close to the destination point
double destinationDistance = calculateDistance(
  getCurrentLocationData().latitude,
  getCurrentLocationData().longitude,
  destLatitude,
  destLongitude,
);

if (destinationDistance <= radius) {
  updatePolyline(destLatitude, destLongitude, controller: controller);
}

}

void updatePolyline(double destinationLatitude, double destinationLongitude, {required MapController controller}) async { controller.drawRoad( GeoPoint( latitude: getCurrentLocationData().latitude, longitude: getCurrentLocationData().longitude), GeoPoint( latitude: destinationLatitude, longitude: destinationLongitude), roadType: RoadType.foot); update(); }

double calculateDistance(double lat1, double lon1, double lat2, double lon2) { const R = 6371000.0; // Radius of the Earth in meters

// Convert latitude and longitude from degrees to radians
double dLat = _toRadians(lat2 - lat1);
double dLon = _toRadians(lon2 - lon1);

// Haversine formula
double a = pow(sin(dLat / 2), 2) +
    cos(_toRadians(lat1)) * cos(_toRadians(lat2)) * pow(sin(dLon / 2), 2);
double c = 2 * atan2(sqrt(a), sqrt(1 - a));

// Distance in meters
double distance = R * c;

return distance;

}

double _toRadians(double degree) { return degree * (pi / 180.0); } }

liodali commented 10 months ago

because each time the builder called you will rebuild the map it's better to put OSMFlutter in child not inside the builder try how to make instance to share mapsManager

RutananaArnold commented 10 months ago

the issue has persisted

[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: LateInitializationError: Field '_osmBaseController@288117859' has not been initialized. E/flutter ( 4706): #0 BaseMapController._osmBaseController (package:flutter_osm_interface/src/map_controller/base_map_controller.dart) E/flutter ( 4706): #1 BaseMapController.osmBaseController (package:flutter_osm_interface/src/map_controller/base_map_controller.dart:21:47) E/flutter ( 4706): #2 MapController.myLocation (package:flutter_osm_plugin/src/controller/map_controller.dart:303:18) E/flutter ( 4706): #3 _MapScreenState.initState. (package:safetec_security/screens/map_screen.dart:38:24) E/flutter ( 4706): #4 SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1325:15) E/flutter ( 4706): #5 SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1264:9) E/flutter ( 4706): #6 SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:1113:5) E/flutter ( 4706): #7 _invoke (dart:ui/hooks.dart:312:13) E/flutter ( 4706): #8 PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:383:5) E/flutter ( 4706): #9 _drawFrame (dart:ui/hooks.dart:283:31) E/flutter ( 4706): 2 E/FrameEvents( 4706): updateAcquireFence: Did not find frame.

this is how i had modified the code


import 'package:flutter/material.dart';
import 'package:flutter_osm_plugin/flutter_osm_plugin.dart';
import 'package:get/get.dart';
import 'package:safetec_security/controllers/maps_controller.dart';

class MapScreen extends StatefulWidget {
  final double destinationLatitude;
  final double destinationLongtude;
  const MapScreen(
      {super.key,
      required this.destinationLatitude,
      required this.destinationLongtude});

  @override
  State<MapScreen> createState() => _MapScreenState();
}

class _MapScreenState extends State<MapScreen> {
  late MapController controller;
  late MapsController mapsManager;

  @override
  void initState() {
    super.initState();
    controller = MapController.withUserPosition(
      trackUserLocation: const UserTrackingOption(
        enableTracking: true,
        unFollowUser: false,
      ),
    );

    mapsManager = Get.find<MapsController>();

    mapManager.requestLocationPermisionAndCurrentLocation();
    // Schedule the call to zoomMapToUserLocation after the frame has been built
    WidgetsBinding.instance.addPostFrameCallback((_) async {
      print("starting to load map");
      await controller.myLocation().then((value) async {
        print("lat and long: ${value.latitude} ${value.longitude}");
        await controller.currentLocation();
        await mapManager.zoomMapToUserLocation(
          controller: controller,
          destinationLatitude: widget.destinationLatitude,
          destinationLongitude: widget.destinationLongtude,
        );
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return
        // GetBuilder<MapsController>(
        //     init: MapsController(),
        //     builder: (mapsManager) {
        //       return

        Scaffold(
            body: OSMFlutter(
      controller: controller,
      onMapIsReady: (bool isMapReady) {
        print("map ready: $isMapReady");
        mapsManager.updateIfMapIsReady(isMapReady);
      },
      mapIsLoading: const Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [CircularProgressIndicator(), Text("Map is Loading..")],
        ),
      ),
      osmOption: const OSMOption(
        showZoomController: true,
        showDefaultInfoWindow: true,
        roadConfiguration: RoadOption(
          roadColor: Colors.yellowAccent,
        ),
      ),
      onLocationChanged: (GeoPoint locationpoints) {
        print(
            'Current location: lat:${locationpoints.latitude} long: ${locationpoints.longitude}');
        mapsManager.startLocationUpdates(
            controller: controller,
            userLocationPoints: locationpoints,
            destLat: widget.destinationLatitude,
            destLong: widget.destinationLongtude);
      },
    ));

    // });
  }
}
RutananaArnold commented 10 months ago

and also to mention

onMapIsReady keeps returning false and keeps displaying the mapIsLoading widget

onMapIsReady: (bool isMapReady) { print("map ready: $isMapReady"); mapsManager.updateIfMapIsReady(isMapReady); },

i really need help for the map to be able to loads and zoom to the current user location

tkb1902 commented 10 months ago

im facing the same issue since the update of OSM


import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:drivers_app/local_notifications.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_osm_plugin/flutter_osm_plugin.dart';
import 'package:latlong2/latlong.dart';

class HomeScreen extends StatefulWidget {
  final User? user; // Pass the user as a parameter

  const HomeScreen({Key? key, required this.user}) : super(key: key);

  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> with AutomaticKeepAliveClientMixin
{
  String? rideType;
  bool isExpanded = false; // Track expansion state
  bool isOnDuty = false; // Track on-Duty state
  late  MapController controller;

  @override
  initState() {
    super.initState();
    LocalNotifications.init();

  controller = MapController.withUserPosition(
  trackUserLocation: const UserTrackingOption(
  enableTracking: true,
  unFollowUser: false,
  ),
  );
    fetchRideType();
  }

  void fetchRideType() async {
  controller.initMapWithUserPosition;
    // Fetch the user document from Firestore
    DocumentSnapshot userDoc = await FirebaseFirestore.instance
        .collection('users')
        .doc(widget.user?.uid)
        .get();

    // Get the 'rideType' field from the user document
    setState(() {
      rideType = (userDoc['rideType']);
    });
  }

  void _handleNotification(
      String action, String dropLocation, String riderName) {
    // Handle the notification action here
    if (action == 'accept') {
      print('Accepted ride request: $riderName to $dropLocation');
      // Implement your logic for accepting the ride request
    } else if (action == 'ignore') {
      print('Ignored ride request: $riderName to $dropLocation');
      // Implement your logic for ignoring the ride request
    }
  }

  @override
  Widget build(BuildContext context) {
    // Check if the user is authenticated
    if (widget.user == null) {
      // If not authenticated, navigate back to the login or signup screen
      return const Scaffold(
        body: Center(
          child: Text('User not authenticated. Please log in or sign up.'),
        ),
      );
    }
    GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
    return Scaffold(
      appBar: AppBar(
        title: const Text('Driver Home'),
      ),
      body: Column(
        children: [
          // Map with adjustable height
          Expanded(
            flex: 3,
            child: OSMFlutter(
              controller: controller,
              osmOption: OSMOption(
                showZoomController: true,
                userTrackingOption: const UserTrackingOption(
                  enableTracking: true,
                  unFollowUser: false,

                ),
                zoomOption: const ZoomOption(
                  initZoom: 16,
                  minZoomLevel: 16,
                  maxZoomLevel: 19,
                  stepZoom: 1.0,
                ),
                userLocationMarker: UserLocationMaker(
                  personMarker: const MarkerIcon(
                    icon: Icon(
                      Icons.location_on,
                      color: Colors.black,
                      size: 60,
                    ),
                  ),
                  directionArrowMarker: const MarkerIcon(
                    icon: Icon(
                      Icons.double_arrow,
                      size: 48,
                    ),
                  ),
                ),
                roadConfiguration: const RoadOption(
                  roadColor: Colors.yellowAccent,
                ),
                markerOption: MarkerOption(
                  defaultMarker: const MarkerIcon(
                    icon: Icon(
                      Icons.person_pin_circle,
                      color: Colors.blue,
                      size: 56,
                    ),
                  ),
                ),
              ),
            ),
          ),
          // Toggle button and list
          Expanded(
            flex: 2,
            child: Padding(
              padding: const EdgeInsets.all(8.0),
              child: Column(
                children: [
                  ElevatedButton(
                    onPressed: () {
                      setState(() {
                        isExpanded = !isExpanded;
                        isOnDuty = !isOnDuty;
                      });
                    },
                    style: ElevatedButton.styleFrom(
                      primary: isOnDuty ? Colors.green : Colors.red,
                      padding: const EdgeInsets.all(16.0),
                    ),
                    child: Text(
                      isOnDuty ? 'On-Duty' : 'Off-Duty',
                      style: const TextStyle(fontSize: 18.0),
                    ),
                  ),
                  if (isExpanded)
                    Expanded(
                      child: StreamBuilder<QuerySnapshot>(
                        stream: FirebaseFirestore.instance
                            .collection(rideType!)
                            .snapshots(),
                        builder: (context, snapshot) {
                          if (!snapshot.hasData) {
                            return const CircularProgressIndicator();
                          }

                          var rideRequests = snapshot.data!.docs;

                          return ListView.builder(
                            itemCount: rideRequests.length,
                            itemBuilder: (context, index) {
                              var rideRequest = rideRequests[index];
                              var dropLocation = rideRequest['dropAddress'];
                              var riderName = rideRequest['name'];

                              LocalNotifications.showNotification(
                                'New Ride Request',
                                '$riderName needs a ride. Drop Location: $dropLocation',
                              );

                              return ListTile(
                                title: Text(
                                    'Drop Location: $dropLocation, Rider: $riderName'),
                                onTap: () {
                                  LocalNotifications.showNotification(
                                    'New Ride Request',
                                    '$riderName needs a ride. Drop Location: $dropLocation',
                                  );
                                },
                              );
                            },
                          );
                        },
                      ),
                    ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
  @override
  bool get wantKeepAlive => true;
}
E/flutter (15803): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: LateInitializationError: Field 'controller' has not been initialized.
E/flutter (15803): #0      _HomeScreenState.controller (package:drivers_app/Allscreens/Homescreen.dart)
E/flutter (15803): #1      _HomeScreenState.fetchRideType (package:drivers_app/Allscreens/Homescreen.dart:38:3)
E/flutter (15803): #2      _HomeScreenState.initState (package:drivers_app/Allscreens/Homescreen.dart:28:5)
E/flutter (15803): #3      StatefulElement._firstBuild (package:flutter/src/widgets/framework.dart:5602:55)
E/flutter (15803): #4      ComponentElement.mount (package:flutter/src/widgets/framework.dart:5447:5)
E/flutter (15803): #5      Element.inflateWidget (package:flutter/src/widgets/framework.dart:4326:16)
E/flutter (15803): #6      Element.updateChild (package:flutter/src/widgets/framework.dart:3837:18)
E/flutter (15803): #7      SingleChildRenderObjectElement.mount (package:flutter/src/widgets/framework.dart:6736:14)
liodali commented 10 months ago

extract OSMFlutter from that widget and put in statelesswidget better

afandiyusuf commented 9 months ago

@RutananaArnold I've experienced this before, and managed to fix the issue. The issue of this problem is you are using controller before the map is ready (adding static geo point via controller, or add marker via controller).

Workaround for this issue, you need to make sure to add a static point after map is ready, to know when then map is ready. you can flag something on onMapIsReady on OSMFlutter.

Or you can add/set static point on onMapIsReady function.

Fintasys commented 6 months ago

Same issue, I tried what @afandiyusuf suggested, but it didn't fix it for me.

liodali commented 6 months ago

can you share your code ?

afandiyusuf commented 6 months ago

@Fintasys @liodali here is my code.

  MapController? controller;

  void initState() {
    //Getting centroid point via some algorithm 
    //Generating city point that I need to render to the maps.
    Future.delayed(Duration.zero, () async {
      var searchRequest = context.read<HotelSearchRequestProvider>();

      //Mapping hotels Result -> cityPoints
      List<HotelInfo> hotelResults = widget.searchResultProvider.hotelResults;
      cityPoints = List<CityPoint>.from(hotelResults.map((e) => CityPoint(
          hotelId: e.id,
          hotelName: e.name,
          price: e.rate,
          longitude: e.longitude,
          latitude: e.latitude)));
      cityPoints.removeWhere(
          (element) => element.longitude == null || element.latitude == null);

      //Generate centroid point based on the all available cityPoints.
      GeoPoint centroidPoint = _centroidPoint(List<GeoPoint>.from(
          hotelResults.map((e) => GeoPoint(
              latitude: e.latitude ?? 0, longitude: e.longitude ?? 0))));

      //##1: update the controller.
      if(mounted){
        setState(() {
          controller = MapController.withPosition(initPosition: centroidPoint);
        });
      }
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.searchRequestProvider.keyword ?? ''),
      ),
      body: SafeArea(
        child: Stack(
          children: [
            Container(
              width: MediaQuery.of(context).size.width,
              height: MediaQuery.of(context).size.height,
              child: (controller == null) //only draw the map only when controller is not null (wait delayed operation on initState ##1)
                  ? SizedBox()
                  : OSMFlutter(
                      onGeoPointClicked: (GeoPoint marker) {
                        //onclick at here
                      },
                      controller: controller!, //already pass null check operation
                      onMapIsReady: (isReady) async {
                        //here I put all the marker, make sure to check if the map is ready
                        if (isReady && needUpdate) {
                          needUpdate = false;
                          for (int i = 0; i < cityPoints.length; i++) {
                            controller!.setStaticPosition(
                                [cityPoints[i].geoPoint!],
                                cityPoints[i].hotelId ?? '');
                            controller!.setMarkerOfStaticPoint(
                                id: cityPoints[i].hotelId ?? '',
                                markerIcon: MarkerIcon(
                                    iconWidget: Column(
                                  mainAxisSize: MainAxisSize.min,
                                  children: [
                                    Icon(Icons.location_on),
                                    Container(
                                      padding: EdgeInsets.symmetric(
                                          horizontal: 5, vertical: 3),
                                      decoration: BoxDecoration(
                                          color: ThemeColors.primaryBlueNavy,
                                          borderRadius:
                                              BorderRadius.circular(100)),
                                      child: Text(
                                        cityPoints[i].hotelName ?? '',
                                        style: TextStyle(
                                            color: Colors.white,
                                            fontSize: 10,
                                            fontWeight: FontWeight.w700),
                                      ),
                                    )
                                  ],
                                )));
                          }
                          //don't forget to update ui 
                          setState(() {});
                        }
                      },
                      osmOption: OSMOption(
                        zoomOption: ZoomOption(initZoom: 13),
                      ),
                    ),
            ),

          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    //only disposed the controller if the controller is initiated.
    if (controller != null) {
      controller!.dispose();
    }
    super.dispose();
  }
}

GeoPoint _centroidPoint(List<GeoPoint> points) {
  int lengthPoint = points.length;
  double sumLat = points.fold(0, (sum, point) => sum + point.latitude);
  double sumLong = points.fold(0, (sum, point) => sum + point.longitude);
  double centroidLat = sumLat / lengthPoint;
  double centridLong = sumLong / lengthPoint;
  return GeoPoint(latitude: centroidLat, longitude: centridLong);
}
liodali commented 6 months ago

replace the code in initState with this


void didChangeDependencies() {
    //Getting centroid point via some algorithm 
    //Generating city point that I need to render to the maps.
    if (searchRequest != null)  {
      var searchRequest = context.read<HotelSearchRequestProvider>();

      //Mapping hotels Result -> cityPoints
      List<HotelInfo> hotelResults = widget.searchResultProvider.hotelResults;
      cityPoints = List<CityPoint>.from(hotelResults.map((e) => CityPoint(
          hotelId: e.id,
          hotelName: e.name,
          price: e.rate,
          longitude: e.longitude,
          latitude: e.latitude)));
      cityPoints.removeWhere(
          (element) => element.longitude == null || element.latitude == null);

      //Generate centroid point based on the all available cityPoints.
      GeoPoint centroidPoint = _centroidPoint(List<GeoPoint>.from(
          hotelResults.map((e) => GeoPoint(
              latitude: e.latitude ?? 0, longitude: e.longitude ?? 0))));

      //##1: update the controller.
      if(mounted){
       controller = MapController.withPosition(initPosition: centroidPoint);
      }
    }
  }

remove this code part where you check controller and keep directly the map

(controller == null)
                  ? SizedBox()
volongithub commented 2 months ago

Hi, I have a pretty simple setup with a stateless view, an AppBar and just the OSMFlutter. I have several problems, which may depend on my setup. I want to see the users actual position but do not reset / center the map after initialization with my GeoPoint. If you scroll out, you should see your location, when it comes in range. It would be great, if I can add an action button, which calculates the zoom level needed to show the initial GeoPoint (with a marker) and the actual user location with another users marker. Right now I have an action button which should center the map at the given GeoPoint and set the zoom level to 17.

Right now the code is not working. The error is: [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: LateInitializationError: Field '_osmBaseController@527117859' has not been initialized.

0 BaseMapController._osmBaseController (package:flutter_osm_interface/src/map_controller/base_map_controller.dart)

1 BaseMapController.osmBaseController (package:flutter_osm_interface/src/map_controller/base_map_controller.dart:21:47)

2 MapController.moveTo (package:flutter_osm_plugin/src/controller/map_controller.dart:166:11)

3 MapsView.build. (package:dbbvapp/maps_view.dart:27:26)

4 _InkResponseState.handleTap (package:flutter/src/material/ink_well.dart:1171:21)

5 GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:344:24)

6 TapGestureRecognizer.handleTapUp (package:flutter/src/gestures/tap.dart:652:11)

7 BaseTapGestureRecognizer._checkUp (package:flutter/src/gestures/tap.dart:309:5)

8 BaseTapGestureRecognizer.handlePrimaryPointer (package:flu<…>

The Map is Ready log is just called once with isReady == false....

Here is the Widget (using osm 1.2.0): import 'package:flutter/material.dart'; import 'package:flutter_osm_plugin/flutter_osm_plugin.dart'; import 'dart:developer' as developer;

class MapsView extends StatelessWidget { const MapsView({super.key});

@override Widget build(BuildContext context) { final controller = MapController.withPosition( initPosition: GeoPoint( latitude: 48.70602758963602, longitude: 9.661015883359068, ), );

return Scaffold(
  appBar: AppBar(
    backgroundColor: Theme.of(context).colorScheme.inversePrimary,
    shadowColor: Theme.of(context).shadowColor,
    centerTitle: true,
    title: const Text('Wo ist was'),
    actions: <Widget>[
      IconButton(
        icon: const Icon(Icons.music_note_sharp),
        onPressed: () {
          controller.moveTo(GeoPoint(
              latitude: 48.70602758963602,
              longitude: 9.661015883359068,
            ),
          );
        },
      ),
    ],
  ),
  body: OSMFlutter(
      onMapIsReady: (isReady) {
        developer.log("Map is Ready! $isReady");
        controller.enableTracking(enableStopFollow: true);
      },
      controller: controller,
      osmOption: const OSMOption(
        userTrackingOption: UserTrackingOption(
          enableTracking: true,
          unFollowUser: true,
        ),
        zoomOption: ZoomOption(
          initZoom: 17,
          minZoomLevel: 5,
          maxZoomLevel: 19,
          stepZoom: 1.0,
        ),
      )),
);

} }

Any help is much appreciated

liodali commented 2 months ago

on which platform you face that issue ?

volongithub commented 2 months ago

I use the iOS Simulator with an iPhone 15 Pro Max and iOs 17.4 and my Hardware iPhone 13 with iOS 17.6.1

I added this key to info.plist, because I want to track users position only when using the app. The location dialog pops up, when the app is installed freshly.

NSLocationWhenInUseUsageDescription my text

volongithub commented 2 months ago

When I set the Simulator Location function to "None", the map stays on the initial GeoPoint, after re-installing the app. When I then set the location to "Apple" the map recenters to the position with the actual zoom. When I use hot code replace, the map always re-centers to the Apple location, although the simulator location is set to "None" That is what I don't want. The location should only be shown if the user zooms out enough or uses the button (not yet there) to calculate the optimal zoom to show both, the user location and the target location (initial GeoPoint). The map seems to be "ready" after re-centering to the users location. Static Points are added after this, too.

liodali commented 2 months ago

your configuration is using our impl for tracking the user that doesnt provide that manuall control of the map. To acheive your behavior , you can use to start get user location await controller.startLocationUpdating(); without control the map this to stop getting the user location await controller.stopLocationUpdating(); to get userLocation use our mixin OSMMixinObserver and call the method onLocationChanged

volongithub commented 2 months ago

Thank you very much for your help. I tried that before setting 'enableTracking: false' and unFollowUser: false' But then the map gets never "ready" and the static Points are not displayed. Also things like showZoomController are not working then. There is nothing showing up when I set 'showZoomController: true'. Or am I missing something more? The AppBar action moving to the given Point is working right now. And the last tests did not throw up that InitializationException 👍

body: OSMFlutter(
          onMapIsReady: (isReady) {
            developer.log("Map is Ready! $isReady");
            controller.enableTracking(enableStopFollow: true);
          },
          controller: controller,
          osmOption: OSMOption(
            userTrackingOption: const UserTrackingOption(
              enableTracking: false,
              unFollowUser: false,
            ),
            zoomOption: const ZoomOption(
              initZoom: 17,
              minZoomLevel: 5,
              maxZoomLevel: 19,
              stepZoom: 1.0,
            ),
            showDefaultInfoWindow: true,
            staticPoints: [
              StaticPositionGeoPoint(
                "Konzertsaal",
                const MarkerIcon(icon: Icon(Icons.music_note_outlined, color: Colors.black87, size: 30,)),
                [GeoPoint(
                  latitude: 48.70558412422597,
                  longitude: 9.661641612106726,)
                ]
              ),
              StaticPositionGeoPoint(
                "Essen",
                const MarkerIcon(icon: Icon(Icons.restaurant, color: Colors.black87, size: 30,)),
                [GeoPoint(
                  latitude: 48.70571439033881,
                  longitude: 9.660920269038554,)
                ]
              ),
            ],
            showZoomController: true,
          ),
        ),
volongithub commented 2 months ago

Oh, my bad... I forgot the controller.enableTracking(enableStopFollow: true);in the onMapIsReady. Removing this and the userTrackingOption keeps the map at the initial point But when I use the startLocationUpdating the map is moving to the user Location. When I move the map by touch it jumps back to the user Location. When I click the action Button a second time, the user location tracking is turned off again and the map does not move automatically.

appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        shadowColor: Theme.of(context).shadowColor,
        centerTitle: true,
        title: const Text('Wo ist was'),
        actions: <Widget>[
          IconButton(
            icon: const Icon(Icons.music_note_sharp),
            onPressed: () async {
              await controller.moveTo(GeoPoint(
                  latitude: 48.70602758963602,
                  longitude: 9.661015883359068,
                ),
              );
            },
          ),
          IconButton(
            icon: const Icon(Icons.home),
            onPressed: () async {
              await controller.startLocationUpdating();
            },
          ),
        ],
      ),
liodali commented 2 months ago

you needi use OSMMixinObserver and call this onLocationChanged, you dont need to use enableTracking will move map automatically doesnt work on zoom out use statefullwidget for better control and just put the map in stateless widget and pass to it controller