Skip to main content

How to create custom widget based Info Window for Google Maps in Flutter?

Flutter is all about widgets. But in case of Google Maps it is not completely true. There are some components which are not flutter widget yet (e.g. Markers and Info Windows). In case of markers it is still easy to change icon but info windows are not allowed to be customise. Currently info window objects (not widget) can only take "title" and "snippet" and both of these needs to be "String" strictly.

UPDATE 19/01/2021 : I have published a package on pub.dev for this functionality. Please checkout Custom Info Window.

Custom InfoWindow

Currently supported InfoWindow

Today I am going to create a widget based info window which can take any widget and show on the top of marker pin when the marker is tapped. Also this will remain on the top of marker pin even if the user starts to change camera position by swiping unless map or another marker is tapped.

At the time of writing this post "google_maps_flutter 0.5.28+1" is available on "pub.dev".

If you know how to integrate Google Maps in your flutter app then you can proceed otherwise learn about it using following links before moving ahead :
Apart from this you must also have knowledge about flutter state management using ChangeNotifierProvider. If you are not familiar with Provider package in flutter please go through flutter docs.

To make you understand this I am going to develop a sample app with user listing on maps having name, image and ratings on info windows. For this we will need user data to be available as data objects. Let's create "User" class to hold this data.

class User {
final int rating;
final String username;
final String name;
final String image;
final LatLng location;

User(
this.username,
this.name,
this.image,
this.location,
this.rating,
);
}


Now create a class to hold info window state by inheriting "ChangeNotifier" class.


class InfoWindowModel extends ChangeNotifier {
bool _showInfoWindow = false;
bool _tempHidden = false;
User _user;
double _leftMargin;
double _topMargin;

void rebuildInfoWindow() {
notifyListeners();
}

void updateUser(User user) {
_user = user;
}

void updateVisibility(bool visibility) {
_showInfoWindow = visibility;
}

void updateInfoWindow(
BuildContext context,
GoogleMapController controller,
LatLng location,
double infoWindowWidth,
double markerOffset,
) async {
ScreenCoordinate screenCoordinate =
await controller.getScreenCoordinate(location);
double devicePixelRatio =
Platform.isAndroid ? MediaQuery.of(context).devicePixelRatio : 1.0;
double left = (screenCoordinate.x.toDouble() / devicePixelRatio) -
(infoWindowWidth / 2);
double top =
(screenCoordinate.y.toDouble() / devicePixelRatio) - markerOffset;
if (left < 0 || top < 0) {
_tempHidden = true;
} else {
_tempHidden = false;
_leftMargin = left;
_topMargin = top;
}
}

bool get showInfoWindow =>
(_showInfoWindow == true && _tempHidden == false) ? true : false;

double get leftMargin => _leftMargin;

double get topMargin => _topMargin;

User get user => _user;
}
InfoWindowModel is responsible for maintaining info window's state and info window gets rebuild when anything changes which can impact info window's content. InfoWindowModel is notified by accessing "Provider" as follows:

final providerObject = Provider.of<InfoWindowModel>(context, listen: false);
While creating markers we have to define "onTap" as follows:

onTap: () {
providerObject.updateInfoWindow(
context,
mapController,
v.location,
_infoWindowWidth,
_markerOffset,
);
providerObject.updateUser(v);
providerObject.updateVisibility(true);
providerObject.rebuildInfoWindow();
},
Here we are changing user based on which user marker is tapped.


While defining GoogleMap, we need to configure map's "onTap" as follows:

onTap: (position) {
if (providerObject.showInfoWindow) {
providerObject.updateVisibility(false);
providerObject.rebuildInfoWindow();
}
},

For handling camera moves, we have to notify InfoWindowModel using provider as follows:

onCameraMove: (position) {
if (providerObject.user != null) {
providerObject.updateInfoWindow(
context,
mapController,
providerObject.user.location,
_infoWindowWidth,
_markerOffset,
);
providerObject.rebuildInfoWindow();
}
},



Complete Code:

Comments