I have a map (Using flutter maps) that presents some layers that can be enabled or disabled by the user. For that I have two buttons to disable and enable these layers, and I'm controlling their state with a getX controller.
RxBool radar = false.obs;
RxBool satelite = false.obs;
I want when the user clicks on one of these buttons the layer related to it activates or deactivates.
So far so good, however, I want that when he activates the radas layer, the other layer remains without re-rendering and vice versa. What is happening is that when the user activates/deactivates a layer, the map component re-renders and ends up causing the other layer to reload its content.
GetX<MenuController>(
init: MenuController(),
builder: (menuController) {
return FlutterMap(
options: MapOptions(),
layers: [
if (menuController.radar.value) //Handle True or false
TileLayerOptions(
wmsOptions: WMSTileLayerOptions(
baseUrl: "MyUrlLocales",
),
),
if (menuController.satelite.value) //Handle True or false
TileLayerOptions(
wmsOptions: WMSTileLayerOptions(
baseUrl: "MyUrlCars",
),
),
],
);
},
),
Obviously this happens because you have given GetX to the whole FlutterMap
create a controller for MenuController
var _controller = Get.put(MenuController());
and
FlutterMap(options: MapOptions(), children: [
Obx(
() => Visibility(
visible: _menuController.radar.value,
child: TileLayerWidget(
options: TileLayerOptions(
wmsOptions: WMSTileLayerOptions(
baseUrl: "MyUrlLocales",
),
),
),
),
),
Obx(
() => Visibility(
visible: menuController.satelite.value,
child: TileLayerWidget(
options: TileLayerOptions(
wmsOptions: WMSTileLayerOptions(
baseUrl: "MyUrlCars",
),
),
),
),
),
]),
You can do something like this :
var _controller = MenuController();
And in your build function let's say you want to update one widget runtime you can do something like this,
FlutterMap(
options: MapOptions(),
layers: [
if (menuController.radar.value) //Handle True or false
Obx(()=> TileLayerOptions(
wmsOptions: WMSTileLayerOptions(
baseUrl: "MyUrlLocales",
),
)),
if (menuController.satelite.value) //Handle True or false
TileLayerOptions(
wmsOptions: WMSTileLayerOptions(
baseUrl: "MyUrlCars",
),
),
],
)
So you need to wrap the widget with Obx() which you want to update runtime.
Related
I'm using index stack in home screen to show different screen at one time.
My Problem is that child widget is rebuilt again and make API call when ever I re-enter to any screen
home screen code:
final _currentPage =
context.select<MenuProvider, int>((provider) => provider.currentPage);
void _onItemTapped(int index) {
Provider.of<MenuProvider>(context, listen: false)
.updateCurrentPage(index);
}
List<MenuItem> mainMenu = [
MenuItem(
AppLocalizations.of(context)!.sd_title,
'Home',
Icons.home,
0,
),
MenuItem(
AppLocalizations.of(context)!.profile_title,
'Profile',
Icons.person,
1,
),
MenuItem(
AppLocalizations.of(context)!.sd_calculator,
'Calculator',
Icons.calculate_rounded,
2,
),
];
var screens = [
mainMenu[_currentPage].index == 0 ? const HomeFragment() : Container(),
mainMenu[_currentPage].index == 1 ? const Profile() : Container(),
mainMenu[_currentPage].index == 2
? LoanCalculatorScreen(isHome: true)
: Container(),
];
var container = Container(
alignment: Alignment.center,
color: Colors.white,
child: FadeIndexedStack(
index: mainMenu[_currentPage].index,
children: screens,
),
);
Container is used in body of Scaffold.
Bottom Navigation:
bottomNavigationBar: BottomNavigationBar(
items: mainMenu
.map(
(item) => BottomNavigationBarItem(
label: item.bottomTitle,
icon: Icon(
item.icon,
),
),
)
.toList(),
currentIndex: _currentPage,
selectedItemColor: Colors.amber[800],
onTap: _onItemTapped,
)
Home screen: As statefull to make a call on init state;
#override
void initState() {
var homeData = Provider.of<HomeProvider>(context, listen: false);
homeData.getOffersAndPartnersSlider();
super.initState();
}
I'm using Provider for state management and API call.
any suggestion will help me to write better code and make good performance.
I've Try Page View also same thing happen the inside child is rebuilt.
I just want to make The API call once.
To fix this I've made changes with Pages.
Declare list of pages above built method with PageStorageKey.
final List<Widget> pages = [
const HomeFragment(
key: PageStorageKey('Page1'),
),
const Profile(
key: PageStorageKey('Page2'),
),
Lo
LoanCalculatorScreen(key: const PageStorageKey('Page'), isHome: true),
];
And Call pages inside Indexed Stack Widgets Children
Container(
alignment: Alignment.center,
color: Colors.white,
child: FadeIndexedStack(
index: mainMenu[_currentPage].index,
children: pages,
),
)
along with greeting you, I wanted to ask you if someone has been able to show an InfoWindow in the flutter map maker, or create a container that is floating so that it appears next to the maker, in google map if possible.
new Marker
(
width: 45.0,
height: 45.0,
point: new LatLng(-25.963678, -51.240657),
builder: (ctx) =>
new Container //here infoWindow or Float Container
(
//child: new FlutterLogo(),
child: IconButton
(
icon: Icon(Icons.location_on),
color: Colors.blue,
iconSize: 45.0,
tooltip: "prueba",
onPressed: ()
{
print("test press");
},
)
),
),
Thank you very much for the help as always.
You cannot set a widget as the marker info-window in any of the flutter map plugins. You can only set the title and text of the info-window in the google maps plugins.
You could turn the map info-window off, center the map on the marker if the user clicks on it, and add some code to show a custom dialog at the approximate center of the screen. You would have to use the flutter Stack widget.
Stack(
children:[
Map(
markers: [Marker(/*no info-window*/, onTap: (){
setState((){ _opacity = 1; });
})],
onMapMove: (){
setState((){ _opacity = 0; });
}
),
Opacity(
opacity: _opacity,
child: /*custom info-window*/,
),
],
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
),
Use custom_info_window package as follows:
Step 1: Initialise CustomInfoWindowController.
CustomInfoWindowController _customInfoWindowController = CustomInfoWindowController();
Step 2: Call CustomInfoWindowController's addInfoWindow from marker's onTap function.
Marker(
markerId: MarkerId("marker_id"),
position: _latLng,
onTap: () {
_customInfoWindowController.addInfoWindow(
<YOUR CUSTOM WIDGET>,
_latLng,
);
},
)
Step 3: Use GoogleMap Widget with Stack.
Stack(
children: <Widget>[
GoogleMap(
onTap: (position) {
_customInfoWindowController.hideInfoWindow();
},
onCameraMove: (position) {
_customInfoWindowController.onCameraMove();
},
onMapCreated: (GoogleMapController controller) async {
_customInfoWindowController.googleMapController = controller;
},
markers: _markers,
initialCameraPosition: CameraPosition(
target: _latLng,
zoom: _zoom,
),
),
CustomInfoWindow(
controller: _customInfoWindowController,
height: 75,
width: 150,
offset: 50,
),
],
)
Call _customInfoWindowController.hideInfoWindow(); inside GoogleMap's onTap to hide CustomInfoWindow when clicking on map but not on the marker.
Call _customInfoWindowController.onCameraMove(); to maintain CustomInfoWindow's position relative to marker. [IMPORTANT]
Assign _customInfoWindowController.googleMapController = controller; inside onMapCreated. [IMPORTANT]
Add CustomInfoWindow as next child to float this on top GoogleMap.
I'm trying to add a map in my flutter app through flutter_map package.
I tried to import this mapbox style :
https://api.mapbox.com/styles/v1/tomjohn/cj5yp5pln0cqb2ruhy6w99j91.html?title=true&access_token=pk.eyJ1IjoidG9tam9obiIsImEiOiJxQ2RydWRNIn0.mYKLvmkrBlBKiQZdhNa31A#10.39/55.8548/-4.1836
by doing this :
FlutterMap(
options: new MapOptions(
center: new LatLng(51.5, -0.09),
zoom: 13.0,
),
layers: [
new TileLayerOptions(
urlTemplate: "https://api.tiles.mapbox.com/v4/"
"{id}/{z}/{x}/{y}#2x.png?access_token={accessToken}",
),
new MarkerLayerOptions(
markers: [
new Marker(
width: 80.0,
height: 80.0,
point: new LatLng(51.5, -0.09),
builder: (ctx) =>
new Container(
child: new FlutterLogo(),
),
),
],
),
],
),
But it is throwing this exception :
Exception: Could not instantiate image codec.
To import mapbox tile:
In your mapbox style sharing option (under developer resources), choose "Third party" and select "CARTO" in dropdown button. Then you can copy the link and paste it as urlTemplate.
After that, add this:
TileLayerOptions(
urlTemplate:<PASTE_URL_HERE>,
additionalOptions: {
'accessToken': <YOUR_ACCESS_TOKEN>,
'id': 'mapbox.mapbox-streets-v8'
}),
MapboxMap(
styleString: "*****",
myLocationEnabled: true,
rotateGesturesEnabled: false,
scrollGesturesEnabled: false,
tiltGesturesEnabled: false,
onMapCreated: (MapboxMapController controller){
_controller = controller;
},
initialCameraPosition: CameraPosition(
target: LatLng(***,***), zoom: 15),
),
I'm just starting with Flutter and very new to State/Stream/BloC concepts. I'm trying to update a FlutterMap center based on the coordinates from a Geolocator()'s Position stream. At the moment I'm just calling setState() updating a variable with the new value, but next I'll be trying using the Bloc pattern instead..
Now, as well as I set the center on the coordinates of the Position coming from the stream I also set a Marker on the same coordinates. I was expecting to see a Steady marker in the center of the screen and a moving map underneath but is the opposite.. the Map is steady and the Icon is moving.. Can you see where I got it wrong?
Also, for the stream: property of the StreamBuilder how would I get to pass the stream from Geolocator() as an event?
Many thanks for your time and help.
This is the Stream:
StreamSubscription positionStream = _geolocator
.getPositionStream(locationOptions)
.listen((Position position) {
setState(() {
_position = position;
});
print(
_position.latitude.toString() + ',' + _position.longitude.toString()); // ok
});
And this is the Map and the Marker:
Container(
height: 670,
width: 370,
child: FlutterMap(
options: MapOptions(
center: LatLng(_position.latitude, _position.longitude),
minZoom: 16.0,
maxZoom: 19.0,
),
layers: [
TileLayerOptions(
// urlTemplate:
// 'https://api.openrouteservice.org/mapsurfer/{z}/{x}/{y}.png?api_key=omitted',
urlTemplate:
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
subdomains: ['a', 'b', 'c'],
keepBuffer: 20),
new MarkerLayerOptions(
markers: [
new Marker(
point: new LatLng(
_position.latitude, _position.longitude),
height: 200,
width: 200,
builder: (context) => IconButton(
icon: Icon(Icons.location_on),
color: Colors.red,
iconSize: 60,
onPressed: () {
print('icon tapped');
},
)),
],
),
],
),
),
Found the problem. I didn't assign any MapController().
So I created one and assign it to the FlutterMap. Inside the setState() I added _controller.move(LatLng(_position.latitude, _position.longitude), 16.0); and now the behaviour is the expected one. Steady Icon in the center of the screen and a moving map underneath.
Hope this will help others.
But still I will try to achieve the same with a BLoC pattern.
Cheers
I have a flutter application and I added the library flutter_map to add an OpenStreetMap map in the application.
This is my code to add the map:
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
FlutterMap(
options: MapOptions(
onPositionChanged: (position, hasGesture) =>
_handleOnPositionChanged(position, hasGesture, context),
center: center,
zoom: zoom,
onTap: (location) {
print(location);
},
),
layers: [
TileLayerOptions(
urlTemplate:
'https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}.png',
subdomains: ['a', 'b', 'c'],
keepBuffer: 6,
backgroundColor: Colors.white,
tileSize: 256,
cachedTiles: true,
),
new MarkerLayerOptions(markers: getListOfMarkers()),
],
)
],
)
);
}
But in the application, when we zoom or move in the map, the loading take some time and so a blank space is displayed on the entire map or just in some places.
Do you know how I can do to avoid these blank spaces ?
Edit
I tested with many tiles servers and it's always the same.
But with google maps and the map_view library, I don't have this problem...
This is a limitation of the library at the moment.
https://github.com/johnpryan/flutter_map/issues/79