Flutter Google Map LatLng, CameraPosition, Geocoding and Geolocator.
We will learn step by step how to use Google Map, Geolocator and Geocoding get user's locatoin and we will see how to get user's location dynamically as user tap on the map and save the user selected location. we will connect to back end.
We will cover the following topic
1. Show Google Map
2. Get address from lattiude and longittude
3. Get locatoin based on user interaction
4. Save the location
Create a project
Let's go ahead and create a project for Google map. You can name it anything. Once the project has been created install these three packages
google_maps_flutter:
geolocator:
geocoding:
Google Map
To show google map we need to use GoogleMap() constructor in the UI code.
GoogleMap(
initialCameraPosition: CameraPosition(target: _initialPosition,
zoom: 10),
onTap: (latLng) {
Get.toNamed(RouteHelper.getPickMapRoute('add-address', false),
arguments: PickMapScreen(
fromAddAddress: true, fromSignUp: false,
googleMapController: locationController.mapController,
route: null, canRoute: false,
));
},
zoomControlsEnabled: false,
compassEnabled: false,
indoorViewEnabled: true,
mapToolbarEnabled: false,
onCameraIdle: () {
locationController.updatePosition(_cameraPosition, true);
},
onCameraMove: ((position) => _cameraPosition = position),
onMapCreated: (GoogleMapController controller) {
locationController.setMapController(controller);
locationController.getCurrentLocation(true, mapController: controller);
},
),
In the above code initialCameraPosition is very important. We must provide a default value using CameraPosition() class.
onTap callback function takes latLng object.
There's onCameraIdle() which gets called when the camera is stopped. This method also gets called when the camera is done initializing first time.
This method is a perfect place for updating different parameters related to google map.
onMapCreated() is called once the map has been created, so it is also get called before onCameraIdle(). Here we may save the GoogleMapController instance locally in our app.
As you move your camera(it means GoogleMap by dragging), onCameraMove callback gets called. It takes position coordinate. So here you can change your intial camera position _cameraPosition. As you move the map, you must change the _cameraPosition varaible using position.
You also need to remmember, that onCameradle() gets called, after onCamera. It means, every time you drag, drop or change your map, onCameraIdle() gets called at the end.
onCameraIdle callback is the best place to update your location controller. it also means updating the actual location or position.
Address from LatLng
Cameraposition iclass s the most important in terms of getting new location as user moves the map or drag the map. We can get exact address as string from google map server if we send the LatLng to it.
CameraPosition position (you can name it anything) object gives you access to target object.
CameraPosition position;
position.target.latitude;
position.target.longitude;
If you wrap the last two lines using LatLng(), then it will return and LatLng object.
LatLng(position.target.latitude,
position.target.longitude))
This object you may send to your server side and return response. Since we are using Getx, the response type would be Response object.
If the reponse.body['status']==ok, then we will return the actual address.
_address = response.body['results'][0]['formatted_address'].toString();
Getting address from LatLng using google server, is also called Geocoding.
After Geocoding, the address in string format could be in our UI.
late Position _myPosition;
Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.best)
.then((Position position) {
_myPosition=position;
print("Position "+_myPosition.toString());
}).catchError((e) {
_myPosition=Position(
latitude: defaultLatLng != null ? defaultLatLng.latitude : double.parse( '0'),
longitude: defaultLatLng != null ? defaultLatLng.longitude : double.parse( '0'),
timestamp: DateTime.now(), accuracy: 1, altitude: 1, heading: 1, speed: 1, speedAccuracy: 1,
);
print("Error "+e);
});
It works.
Here's the complete code
class LocationController extends GetxController implements GetxService {
late LocationRepo locationRepo;
LocationController({required this.locationRepo});
bool _loading = false;
late Position _position;// = Position(longitude: 45.521563, latitude: -122.677433, timestamp: DateTime.now(), accuracy: 1, altitude: 1, heading: 1, speed: 1, speedAccuracy: 1);
late Position _pickPosition;// = Position(longitude: 45.521563, latitude: -122.677433, timestamp: DateTime.now(), accuracy: 1, altitude: 1, heading: 1, speed: 1, speedAccuracy: 1);
Placemark _placeMark = Placemark();
Placemark _pickPlaceMark = Placemark();
List<Marker> _markers = <Marker>[];
List<AddressModel> _addressList=[];
late List<AddressModel> _allAddressList;
int _addressTypeIndex = 0;
List<String> _addressTypeList = ['home', 'office', 'others'];
bool _isLoading = false;
bool _inZone = false;
bool _buttonDisabled = true;
bool _changeAddress = true;
late GoogleMapController _mapController;
bool _updateAddAddressData = true;
bool get isLoading => _isLoading;
bool get loading => _loading;
Position get position => _position;
Position get pickPosition => _pickPosition;
Placemark get placeMark => _placeMark;
Placemark get pickPlaceMark => _pickPlaceMark;
List<Marker> get markers => _markers;
List<AddressModel> get addressList => _addressList;
List<String> get addressTypeList => _addressTypeList;
int get addressTypeIndex => _addressTypeIndex;
bool get inZone => _inZone;
bool get buttonDisabled => _buttonDisabled;
GoogleMapController get mapController => _mapController;
Future<AddressModel> getCurrentLocation(bool fromAddress,
{required GoogleMapController mapController,
LatLng? defaultLatLng, bool notify = true}) async {
_loading = true;
if(notify) {
update();
}
AddressModel _addressModel;
Position _myPosition;
try {
Position newLocalData = await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.best);
_myPosition = newLocalData;
// print("I am from getcurrentPos1 "+defaultLatLng!.latitude.toString());
}catch(e) {
_myPosition = Position(
latitude: defaultLatLng != null ? defaultLatLng.latitude : double.parse( '0'),
longitude: defaultLatLng != null ? defaultLatLng.longitude : double.parse( '0'),
timestamp: DateTime.now(), accuracy: 1, altitude: 1, heading: 1, speed: 1, speedAccuracy: 1,
);
}
if(fromAddress) {
_position = _myPosition;
}else {
_pickPosition = _myPosition;
}
if (mapController != null) {
mapController.animateCamera(CameraUpdate.newCameraPosition(
CameraPosition(target: LatLng(_myPosition.latitude, _myPosition.longitude), zoom: 17),
));
}
Placemark _myPlaceMark;
try {
if(!GetPlatform.isWeb) {
List<Placemark> placeMarks = await placemarkFromCoordinates(_myPosition.latitude, _myPosition.longitude);
_myPlaceMark = placeMarks.first;
}else {
String _address = await getAddressFromGeocode(LatLng(_myPosition.latitude, _myPosition.longitude));
_myPlaceMark = Placemark(name: _address, locality: '', postalCode: '', country: '');
}
}catch (e) {
String _address = await getAddressFromGeocode(LatLng(_myPosition.latitude, _myPosition.longitude));
_myPlaceMark = Placemark(name: _address, locality: '', postalCode: '', country: '');
}
fromAddress ? _placeMark = _myPlaceMark : _pickPlaceMark = _myPlaceMark;
ResponseModel _responseModel = await getZone(_myPosition.latitude.toString(), _myPosition.longitude.toString(), true);
_buttonDisabled = !_responseModel.isSuccess;
_addressModel = AddressModel(
latitude: _myPosition.latitude.toString(), longitude: _myPosition.longitude.toString(), addressType: 'others',
//zoneId: _responseModel.isSuccess ? int.parse(_responseModel.message) : 0,
address: '${_myPlaceMark.name ?? ''}'
' ${_myPlaceMark.locality ?? ''} '
'${_myPlaceMark.postalCode ?? ''} '
'${_myPlaceMark.country ?? ''}',
);
_loading = false;
update();
return _addressModel;
}
void setPickData() {
_pickPosition = _position;
_pickPlaceMark = _placeMark;
}
void disableButton() {
_buttonDisabled = true;
_inZone = true;
update();
}
void setMapController(GoogleMapController mapController) {
_mapController = mapController;
}
Future<ResponseModel> updateAddress(AddressModel addressModel, int addressId) async {
_isLoading = true;
update();
Response response = await locationRepo.updateAddress(addressModel, addressId);
ResponseModel responseModel;
if (response.statusCode == 200) {
getAddressList();
responseModel = ResponseModel(true, response.body["message"]);
} else {
responseModel = ResponseModel(false, response.statusText!);
}
_isLoading = false;
update();
return responseModel;
}
Future<String> getAddressFromGeocode(LatLng latLng) async {
Response response = await locationRepo.getAddressFromGeocode(latLng);
String _address = 'Unknown Location Found';
if( response.body['status'] == 'OK') {
_address = response.body['results'][0]['formatted_address'].toString();
}else {
print("error in the api");
}
return _address;
}
Future<ResponseModel> getZone(String lat, String long, bool markerLoad) async {
if(markerLoad) {
_loading = true;
}else {
_isLoading = true;
}
update();
ResponseModel _responseModel;
Response response = await locationRepo.getZone(lat, long);
if(response.statusCode == 200) {
_inZone = true;
_responseModel = ResponseModel(true, response.body['zone_id'].toString());
}else {
_inZone = false;
_responseModel = ResponseModel(false, response.statusText!);
}
if(markerLoad) {
_loading = false;
}else {
_isLoading = false;
}
update();
return _responseModel;
}
void saveAddressAndNavigate(AddressModel address, bool fromSignUp, String route, bool canRoute) {
if(Get.find<CartController>().getCarts.length > 0) {
Get.dialog(ConfirmationDialog(
icon: "assets/image/warning.png", title: 'are_you_sure_to_reset'.tr, description: 'if_you_change_location'.tr,
onYesPressed: () {
Get.back();
_setZoneData(address, fromSignUp, route, canRoute);
},
onNoPressed: () {
Get.back();
Get.back();
},
));
}else {
_setZoneData(address, fromSignUp, route, canRoute);
}
}
void updatePosition(CameraPosition position, bool fromAddress) async {
if(_updateAddAddressData) {
_loading = true;
update();
try {
if (fromAddress) {
_position = Position(
latitude: position.target.latitude, longitude: position.target.longitude, timestamp: DateTime.now(),
heading: 1, accuracy: 1, altitude: 1, speedAccuracy: 1, speed: 1,
);
} else {
_pickPosition = Position(
latitude: position.target.latitude, longitude: position.target.longitude, timestamp: DateTime.now(),
heading: 1, accuracy: 1, altitude: 1, speedAccuracy: 1, speed: 1,
);
}
ResponseModel _responseModel = await getZone(position.target.latitude.toString(), position.target.longitude.toString(), true);
_buttonDisabled = !_responseModel.isSuccess;
if (_changeAddress) {
/* if (!GetPlatform.isWeb) {
print("update 5");
print("lat is "+position.target.latitude.toString()+" lng is "+position.target.longitude.toString());
List<Placemark> placeMarks = await placemarkFromCoordinates(position.target.latitude, position.target.longitude);
fromAddress ? _placeMark = placeMarks.first : _pickPlaceMark = placeMarks.first;
print("printing place Mark"+_placeMark.name.toString());
print("update 6");*/
//} else {
String _address = await getAddressFromGeocode(LatLng(position.target.latitude, position.target.longitude));
fromAddress ? _placeMark = Placemark(name: _address) : _pickPlaceMark = Placemark(name: _address);
// }
} else {
_changeAddress = true;
}
} catch (e) {
print(e);
}
_loading = false;
update();
}else {
_updateAddAddressData = true;
}
}
void _setZoneData(AddressModel address, bool fromSignUp, String route, bool canRoute) {
Get.find<LocationController>().getZone(address.latitude, address.longitude, false).then((response) async {
if (response.isSuccess) {
Get.find<CartController>().clearCartList();
// address.zoneId = int.parse(response.message);
//autoNavigate(address, fromSignUp, route, canRoute);
Get.toNamed(RouteHelper.getInitialRoute());
} else {
Get.back();
showCustomSnackBar(response.message);
}
});
}
Future<void> getAddressList() async {
Response response = await locationRepo.getAllAddress();
if (response.statusCode == 200) {
_addressList = [];
_allAddressList = [];
response.body.forEach((address) {
_addressList.add(AddressModel.fromJson(address));
_allAddressList.add(AddressModel.fromJson(address));
});
}else{
_addressList=[];
}
update();
}
void clearAddressList(){
_addressList=[];
update();
}
void filterAddresses(String queryText) {
if(_addressList != null) {
_addressList = [];
if (queryText == null || queryText.isEmpty) {
_addressList.addAll(_allAddressList);
} else {
_allAddressList.forEach((address) {
if (address.address.toLowerCase().contains(queryText.toLowerCase())) {
_addressList.add(address);
}
});
}
update();
}
}
Future<ResponseModel> addAddress(AddressModel addressModel) async {
_isLoading = true;
update();
Response response = await locationRepo.addAddress(addressModel);
_isLoading = false;
ResponseModel responseModel;
if (response.statusCode == 200) {
getAddressList();
String message = response.body["message"];
responseModel = ResponseModel(true, message);
await Get.find<LocationController>().saveUserAddress(addressModel);
} else {
responseModel = ResponseModel(false, response.statusText!);
}
update();
return responseModel;
}
Future<bool> saveUserAddress(AddressModel address) async {
String userAddress = jsonEncode(address.toJson());
return await locationRepo.saveUserAddress(userAddress);
}
late Map<String, dynamic> _getAddress;
Map get getAddress=>_getAddress;
AddressModel getUserAddress() {
late AddressModel _addressModel;
_getAddress=jsonDecode(locationRepo.getUserAddress());
try {
_addressModel = AddressModel.fromJson(jsonDecode(locationRepo.getUserAddress()));
}catch(e) {
}
return _addressModel;
}
AddressModel? setUserAddress(AddressModel address) {
AddressModel? _addressModel;
try {
_addressModel = AddressModel.fromJson(jsonDecode(locationRepo.getUserAddress()));
}catch(e) {
}
return _addressModel;
}
void setAddressTypeIndex(int index) {
_addressTypeIndex = index;
update();
}
/*
void saveAddressAndNavigate(AddressModel address, bool fromSignUp, String route, bool canRoute) {
if(Get.find<CartController>().cartList.length > 0) {
Get.dialog(ConfirmationDialog(
icon: Images.warning, title: 'are_you_sure_to_reset'.tr, description: 'if_you_change_location'.tr,
onYesPressed: () {
Get.back();
// _setZoneData(address, fromSignUp, route, canRoute);
},
onNoPressed: () {
Get.back();
Get.back();
},
));
}else {
_setZoneData(address, fromSignUp, route, canRoute);
}
}
*/
void setAddAddressData() {
/*
with this we are able to update the address data from pick map screen
*/
_position = _pickPosition;
_placeMark = _pickPlaceMark;
_updateAddAddressData = false;
update();
}
}
If you want to use the code in your project, you may need to delete some parts or edit the section based on your requirements.
You may keep this tutorial as bookmark, since it would be used for explanation of the code in coming weeks.