osm_flutter
osm_flutter copied to clipboard
Map rebuilds on UI update
I built an animation, which computes value for opacity and scale of a widget while scrolling pageview, and every frame I scroll it the map rebuilds and it's frustrating, eventually failing to an exception at one moment.
in Android, we have some limitation, if the widget that you try to resized is the map try to avoid that and avoid rebuild parent widget for now until i will find solution for that
Same issue. On ios it's ok but on android when screen update, map rebuild and fails. Please help !
what did you mean by the map fails ?
@thpresident @k0lourful I also had some issues with animation and other flickering and rebuild issues while using fake GPS to create routes. It generally occurs when using API level at or below 29. For emulators, I think API level 30 or above should work fine.
can you provide me small example to see the problem
I am sorry their problem may be different than mine. I have since moved on to tabpageview
. I guess they will have to elaborate their problem more. For me API 29 causes rebuilt and freezing.
hi all! any updates about problem?
share your code to see ewactly your issue you need to separate the map from widget where you manipulate the dara
I using OSMMap inside stateful widget, but when stateful widget is resized map always loading
the some widget is widget that will be shown for specific situation
you make above of the map
try to set this androidHotReloadSupport
to true but take on consideration that the map will restarted
is there any way to avoid reloading the map when resizing it? it's pretty weird.
for now in android side , we don't have a solution for that since our plugin use native platform its weird for us also that our native view lose the context when flutter view rebuilt
@Darkildo can you post your code? I have resized the map before.. in my case I had a container with map on top and another widget at bottom. I would resize the bottom container and simulatiously resize the map. I dont think it reloaded... is this your issue or sth else?
final double width;
final double height;
const OSMMap({super.key, required this.width, required this.height});
@override
State<OSMMap> createState() => _OSMMapState();
}
class _OSMMapState extends State<OSMMap> {
late OSM.MapController controller;
final mapGlobalKey = GlobalKey();
@override
void initState() {
super.initState();
controller = OSM.MapController.withPosition(
initPosition: OSM.GeoPoint(
latitude: 80.45,
longitude: 17.36,
),
);
controller.setZoom(zoomLevel: 14);
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
RepaintBoundary(
child: OSM.OSMFlutter(
key: mapGlobalKey,
controller: controller,
androidHotReloadSupport: true,
mapIsLoading: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: const [
CircularProgressIndicator(),
Text("Map is Loading.."),
],
),
),
onMapIsReady: (isReady) {
if (isReady) {
print("map is ready");
}
},
onGeoPointClicked: (point) => print(point),
// trackMyPosition: true,
),
),
Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: EdgeInsets.only(
right: 18.kH(context),
bottom: 16.kH(context),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: EdgeInsets.only(bottom: 20.kH(context)),
child: MapButton(
isActive: context.watchChargeStationCubit.state.isVisible,
onTap: () {
context.chargeStationCubit.visibleToggled();
DI.metrics.L(
context.chargeStationCubit.state.isVisible
? 'Hide charge stations'
: 'Show charge stations',
);
},
activeIconPath: Assets.icons.selectedChargeButton.path,
inactiveIconPath: Assets.icons.unselectedChargeButton.path,
),
),
CurrentLocationButton(
onPressed: () {
controller.currentLocation();
DI.metrics.L('Show current location');
},
),
],
),
),
),
],
);
}
}
okay i am not entirely sure what is it you are looking for? but to avoid rebuilds try this example and let us knw ,is this something you want ? The same logic can be applied to PageView
as well.. if this is not the issue take this code below and add a minimum of your issue to it as an example, and show where map is reloading.?
import 'package:flutter/material.dart';
import 'package:flutter_osm_plugin/flutter_osm_plugin.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
late MapController controller;
TabController? tabController;
int selectedIndex = 0;
void onItemClicked(int index) {
setState(() {
selectedIndex = index;
tabController!.index = selectedIndex;
});
}
@override
void initState() {
super.initState();
tabController = TabController(length: 2, vsync: this);
}
@override
void dispose() {
super.dispose();
tabController!.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: TabBarView(
physics: NeverScrollableScrollPhysics(),
controller: tabController,
children: const [
HomeTabPage(),
EarningsTabPage(),
],
),
bottomNavigationBar: BottomNavigationBar(
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: "Home"),
BottomNavigationBarItem(
icon: Icon(Icons.credit_card), label: "Another Tab"),
],
type: BottomNavigationBarType.fixed,
selectedLabelStyle: const TextStyle(fontSize: 12.0),
showUnselectedLabels: true,
currentIndex: selectedIndex,
onTap: onItemClicked,
),
);
}
}
class HomeTabPage extends StatefulWidget {
const HomeTabPage({super.key});
@override
State<HomeTabPage> createState() => _HomeTabPageState();
}
class _HomeTabPageState extends State<HomeTabPage>
with
AutomaticKeepAliveClientMixin,
// OSMMixinObserver,
WidgetsBindingObserver {
final GlobalKey _mapKey = GlobalKey();
late MapController osmController;
bool _isMapExpanded = false;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
osmController = MapController(
initMapWithUserPosition: true,
);
// osmController.addObserver(this);
}
void toggleMapSize() {
setState(() {
_isMapExpanded = !_isMapExpanded;
});
}
@override
Widget build(BuildContext context) {
super.build(context);
return Scaffold(
appBar: AppBar(
title: Text("resize"),
),
body: PageStorage(
bucket: PageStorageBucket(),
child: Column(
children: [
Expanded(
flex: _isMapExpanded ? 1 : 9,
child: Container(
height: double.infinity,
child: OSMFlutter(
key: const PageStorageKey('myMap'),
controller: osmController,
trackMyPosition: false,
initZoom: 12,
minZoomLevel: 8,
maxZoomLevel: 14,
stepZoom: 1.0,
userLocationMarker: UserLocationMaker(
personMarker: const MarkerIcon(
icon: Icon(
Icons.location_history_rounded,
color: Colors.red,
size: 48,
),
),
directionArrowMarker: const MarkerIcon(
icon: Icon(
Icons.double_arrow,
size: 48,
),
),
),
),
),
),
Expanded(
flex: 1,
child: Center(
child: ElevatedButton(
onPressed: toggleMapSize,
child: Text(_isMapExpanded ? 'Expand Map' : 'Shrink Map'),
),
),
),
],
),
),
);
}
@override
bool get wantKeepAlive => true;
}
class EarningsTabPage extends StatefulWidget {
const EarningsTabPage({super.key});
@override
State<EarningsTabPage> createState() => _EarningsTabPageState();
}
class _EarningsTabPageState extends State<EarningsTabPage> {
@override
Widget build(BuildContext context) {
return Container(
color: Colors.blue,
child: const Center(
child: Text(
"container",
style: (TextStyle(fontSize: 50, color: Colors.white)),
),
),
);
}
}
you can put OSM.OSMFlutter
in another statelessWidget to prevent it from rebuild
Thank you guys for your help, I removed the androidHotReload flag and everything worked fine. Didn't even have to add RepaintBoundary and PageStorage
oh yes! i remember it causing issues too, never used it much , works fine without it.
any progress? I had a similar problem, once OSMFlutter was built it work fine, but when I called it again (after change of widget/route, etc, and coming back to build it again) the loading of OSMFlutter never was completed. Something that I did that solved it, was give a new value to the controller, maybe you can try?
mapController = MapController(
initPosition: GeoPoint(latitude: 0, longitude: 0)
);
can you share some sample and also on which version you're using ?
can you share some sample and also on which version you're using ?
Explanation at the end (ignore some parts that maybe are not being used, i edited it to a way that I can share), some specifications:
-
Flutter Version: 3.10.5;
-
flutter_osm_interface - version: "0.5.2";
-
flutter_osm_plugin - version: "0.55.3";
-
flutter_osm_web - version: "0.4.2";
-
Android: Tests on Android 9 until Android 11
import 'package:flutter/material.dart';
import 'package:flutter_osm_plugin/flutter_osm_plugin.dart';
class RebuildProvider extends ChangeNotifier {
RebuildProvider._();
static final RebuildProvider _instance = RebuildProvider._();
static RebuildProvider get instance => _instance;
rebuild () {
notifyListeners();
}
}
class AProvider extends ChangeNotifier {
AProvider._internal();
static final AProvider _instance = AProvider._internal();
static AProvider get instance => _instance;
MapController mapController = MapController(
initPosition: GeoPoint(latitude: 0, longitude: 0)
);
GeoPoint? _destination;
GeoPoint? get destination => _destination;
GeoPoint? _userLocation;
GeoPoint? get userLocation => _userLocation;
setDestination(double lat, double long) {
_destination = GeoPoint(latitude: lat, longitude: long);
notifyListeners();
}
setUserLocation(double lat, double long) {
_userLocation = GeoPoint(latitude: lat, longitude: long);
}
bool _mapLoaded = false;
bool get mapLoaded => _mapLoaded;
setMapStatus(bool status) {
_mapLoaded = status;
notifyListeners();
}
bool _loadingData = true;
bool get loadingData => _loadingData;
Future<void> loadData(double lat, double long) async {
_loadingData = true;
notifyListeners();
_userLocation = GeoPoint(latitude: lat, longitude: long);
// SOME REQUEST
var data = {"data": ""} as dynamic;
_destination = GeoPoint(latitude: data.data![0].coordinates.lat, longitude: data.data[0].coordinates.long);
_loadingData = false;
notifyListeners();
RebuildProvider.instance.rebuild();
}
setNewPointDestination(double lat, double long) async {
if (destination?.latitude == lat && destination?.longitude == long) {
return;
}
mapController.removeLastRoad();
mapController.removeMarkers([
userLocation!, destination!
]);
_destination = GeoPoint(latitude: lat, longitude: long);
_setMarker(userLocation!, Icons.person_pin_circle, Colors.blue, 20);
mapController.drawRoad(
userLocation!,
GeoPoint(latitude: lat, longitude: long),
roadType: RoadType.foot,
roadOption: const RoadOption(
roadWidth: 10,
roadColor: Colors.blue,
zoomInto: true,
),
);
_setMarker(GeoPoint(latitude: lat, longitude: long), Icons.place, Colors.red, 48);
notifyListeners();
}
_setMarker(GeoPoint point, IconData icon, Color? color, double width) {
mapController.addMarker(
point,
markerIcon:MarkerIcon(
icon: Icon(
icon,
color: color,
size: width,
),
)
);
}
void reset() {
_destination = null;
_userLocation = null;
if (!mapLoaded) {
mapController = MapController(
initPosition: GeoPoint(latitude: 0, longitude: 0)
);
} else {
mapController.removeLastRoad();
}
_mapLoaded = false;
notifyListeners();
}
}
class StreetMap extends StatefulWidget {
final AProvider _aProvider
= AProvider.instance;
final GeoPoint destination;
StreetMap({Key? key, required this.destination}) : super(key: key);
@override
State<StreetMap> createState() => _StreetMapState();
}
class _StreetMapState extends State<StreetMap> {
@override
void initState() {
super.initState();
widget._aProvider.mapController = MapController(
initPosition: GeoPoint(latitude: 47.4358055, longitude: 8.4737324),
);
}
_setMarker(GeoPoint point, IconData icon, Color? color, double width) {
widget._aProvider.mapController.addMarker(
point,
markerIcon:MarkerIcon(
icon: Icon(
icon,
color: color,
size: width,
),
)
);
}
_setDestination(double lat, double long) {
widget._aProvider.setUserLocation(lat, long);
_setMarker(GeoPoint(latitude: lat, longitude: long), Icons.person_pin_circle, Colors.blue, 40);
widget._aProvider.mapController.drawRoad(
GeoPoint(latitude: lat, longitude: long),
widget.destination,
roadType: RoadType.foot,
roadOption: const RoadOption(
roadWidth: 10,
roadColor: Colors.blue,
zoomInto: true,
),
);
_setMarker(widget.destination, Icons.place, Colors.red, 40);
}
@override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return Container(
height: size.height * .5,
margin: const EdgeInsets.only(left: 25, right: 25),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
border: Border.all(
width: 2,
color: Colors.blueGrey
),
),
child: OSMFlutter(
userTrackingOption: const UserTrackingOption(
enableTracking: true,
unFollowUser: true,
),
controller: widget._aProvider.mapController,
enableRotationByGesture: true,
initZoom: 12,
minZoomLevel: 9,
stepZoom: 1.0,
mapIsLoading: const Text("Map Loading"),
onMapIsReady: (isMapReady) => {
if (isMapReady) widget._aProvider.setMapStatus(isMapReady)
},
onLocationChanged: (geoPosition) => {
_setDestination(geoPosition.latitude, geoPosition.longitude)
},
roadConfiguration: const RoadOption(
roadColor: Colors.lightBlue,
),
markerOption: MarkerOption(
defaultMarker: const MarkerIcon(
icon: Icon(
Icons.person_pin_circle,
color: Colors.blue,
size: 56,
),
)
),
),
);
}
}
class RouteExampleA extends StatelessWidget {
const RouteExampleA({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
children: [
StreetMap(destination: AProvider._instance.destination!),
TextButton(
onPressed: () {
Navigator.push<void>(
context,
MaterialPageRoute<void>(
builder: (BuildContext context) => const RouteExampleB(),
),
);
},
child: const Text("redirect to class B")
)
],
);
}
}
class RouteExampleB extends StatelessWidget {
const RouteExampleB({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
children: [
ListenableBuilder(
listenable: RebuildProvider._instance,
builder: (context, child) {
return StreetMap(destination: AProvider._instance.destination!);
}
),
TextButton(
onPressed: () {
Navigator.push<void>(
context,
MaterialPageRoute<void>(
builder: (BuildContext context) => const RouteExampleA(),
),
);
},
child: const Text("redirect to class A")
)
],
);
}
}
For example, imagine a Route B that redirect to Route A (that route call OSMFlutter), the load of map and destination work fine. Now you click on button that redirect back to Route B > and in the Route B you redirect Back to Route A, now, the map starts to get wrong, the load never stop and map never be completed loaded. To solve this problem, what i did was before redirecting again to the Route A, include a call to reset the map controller, as example:
class RouteExampleB extends StatelessWidget {
const RouteExampleB({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return TextButton(
onPressed: () {
AProvider._instance.reset();
Navigator.push<void>(
context,
MaterialPageRoute<void>(
builder: (BuildContext context) => const RouteExampleA(),
),
);
},
child: const Text("redirect to class A")
);
}
}
you cannot use latest version of flutter and our latest dev version ?
why did you try to use navigator in materialApp directly why to try to add MaterialRoutePage like that ?
why did you try to use navigator in materialApp directly why to try to add MaterialRoutePage like that?
Was just an example to represent route change and illustrate the case. You can focus on the part of the case that I reported, that made this controller reset solve my issue, I hope that works with the other problems reported above...
you cannot use latest version of flutter and our latest dev version ?
I can't at the moment, maybe later if I can I'll post it here again.