Sitemap
Vaishanvi Ingole

Vaishanvi Ingole

Flutter developer | API integration| State Management | Firebase | SQL

Google map Integration

6 min readDec 7, 2024

..

The service in user location is tracking through google map when user opens from in which Google map is loaded then users current location capture user have two options that

  1. User can calculate area by manually taping on map
  2. User can get location by walking at particular location

user can take multiples points to calculate the total area distance

State Management : Bloc

Get Vaishanvi Ingole’s stories in your inbox

Join Medium for free to get updates from this writer.

points Covered in this service:

  1. Google Map Integration
  2. live Tracking Of user location
  3. Area calculation
  4. markers draw on map
  5. polylines on map

Libraries used:

google_maps_flutter               //Used to render the map and show markers.
geolocator // Provides the user's current location.
freezed

freezed_annotation // For generating immutable models and serialization.
json_annotation
json_serializable
form_builder_validators // adds validations in form
flutter_bloc // For state management using Bloc.
injectable

injectable_generator // Dependency injection for better code management.
get_it // Dependency injection for better code management.
dio // Api calls in service
fluttertoast // to Notify user show messages in service

Add Form where Google map is rendered and markers and polygoan and drawn on map events are called from bloc

class EmpTrackingForm extends StatelessWidget {
final _key = GlobalKey<FormState>();
final siteCon = TextEditingController();
final dateCon =
TextEditingController(text: Utils.getDisplayFormatDate(DateTime.now()));
final areaCon = TextEditingController();
final gpsCoordinates = TextEditingController();
final remarkCon = TextEditingController();
GIS_EmployeeTracking empTracking = GIS_EmployeeTracking();
@override
Widget build(BuildContext context) {
string = Localizations.of<LocaleBase>(context, LocaleBase)!.main;
return Scaffold(
appBar: AppBar(
title: Text("Employee Tracking"),
),
body: Form(
key: _key,
child: ListView(
padding: EdgeInsets.all(15),
children: [
BlocBuilder<EmpTrackingBloc, EmpTrackingState>(
builder: (context, state) => state.maybeWhen(
getMapDataState: (currentPosition, latLongList, totalArea,
markers, polyLine) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CommonTextField(
hintText: "Get Coordinates",
headerLabel: "Gps Coordinates",
con: TextEditingController(
text: gpsCoordinates.text
.replaceAll("),", "\n")
.replaceAll(")", "")),
readOnly: true,
validator: (String? v) {
FormBuilderValidators.required();
if (v?.isEmpty ?? false) {
return "Coordinates can't be empty";
}
List<String> coordinates =
v?.split('\n').map((e) => e.trim()).toList() ??
[];
if (coordinates.length < 3) {
print("rgsskj${coordinates.length}");
return "Please select at least 3 coordinates";
}
return null;
},
maxLine: latLongList.length,
),
SizedBox(height: 20),
CommonText(
text: "Total Area - ${totalArea} meter",
),
],
);
},
orElse: () => SizedBox(),
),
),
SizedBox(height: 20),
Container(
height: 320,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30),
),
child: BlocBuilder<EmpTrackingBloc, EmpTrackingState>(
builder: (context, state) => state.maybeWhen(
loading: () => Center(child: CircularProgressIndicator()),
getMapDataState: (currentPosition, latLongList, totalArea,
markers, polyLine) {
areaCon.text = totalArea.toString();
gpsCoordinates.text = latLongList
.join(",")
.toString()
.replaceAll("LatLng(", "")
.toString();
return Column(
children: [
Expanded(
child: GoogleMap(
myLocationEnabled: true,
zoomControlsEnabled: true,
zoomGesturesEnabled: true,
myLocationButtonEnabled: true,
mapType: MapType.hybrid,
polylines: polyLine,
gestureRecognizers: Set()
..add(Factory<EagerGestureRecognizer>(
() => EagerGestureRecognizer())),
initialCameraPosition: CameraPosition(
target: new LatLng(latLongList.first.latitude,
latLongList.first.longitude),
zoom: 14.4746,
),
markers: Set<Marker>.of(markers.values),
onTap: (LatLng location) {
if (!kReleaseMode)
BlocProvider.of<EmpTrackingBloc>(context).add(
EmpTrackingEvent.setMarker(
location: location));
},
),
),
SizedBox(height: 20),
Row(
children: [
Expanded(
child: CommonButton(
label: "Get",
onPressed: () {
BlocProvider.of<EmpTrackingBloc>(context)
.add(EmpTrackingEvent.setMarker());
},
),
),
if (latLongList.length > 1) SizedBox(width: 16),
if (latLongList.length > 1)
Expanded(
child: CommonButton(
label: "Clear",
onPressed: () {
showDialog(
context: context,
builder: (context) => AlertDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
CommonText(
text:
"Are you sure clear all coordinates"),
SizedBox(height: 20),
Row(
children: [
Expanded(
child: CommonButton(
onPressed: () =>
Navigator.pop(context),
label: "cancel",
),
),
SizedBox(width: 20),
Expanded(
child: CommonButton(
onPressed: () {
BlocProvider.of<
EmpTrackingBloc>(
context)
.add(EmpTrackingEvent
.clearData(
gpsCoordinates:
gpsCoordinates));
Navigator.pop(context);
},
label: "ok",
),
),
],
)
],
),
),
);
},
),
),
],
),
],
);
},
orElse: () => SizedBox(),
),
),
),
SizedBox(height: 20),
button(
onPressedSubmit: () {
if (_key.currentState?.validate() ?? false) {
if (dateCon.text.isNotEmpty) {
dateCon.text = Utils.getSQLFormatDate(DateTime.now());
empTracking.Created_Date = dateCon.text.toString();
}
String latLngPoints = gpsCoordinates.text
.replaceAll("),", "#")
.replaceAll(")", "")
.replaceAll(" ", "");
print("latLngPoints===>${latLngPoints}");
// use data where you want you()
//event to clear data-
BlocProvider.of<EmpTrackingBloc>(context).add(
EmpTrackingEvent.clearData(
gpsCoordinates: gpsCoordinates));
Navigator.pop(context);
} else {
FlutterToast.showToast("Please Select all fields");
}
},
),
],
),
),
);
}
}

here is bloc EmpTracking Bloc in which all logic and events are called

part 'emp_tracking_bloc.freezed.dart';
@freezed
class EmpTrackingState with _$EmpTrackingState {
const factory EmpTrackingState.initial() = _$InitialState;
const factory EmpTrackingState.loading() = _$LoadingState;
const factory EmpTrackingState.getMapDataState(
Position? currentPosition,
List<LatLng> latLongList,
int totalArea,
Map<MarkerId, Marker> markers,
HashSet<Polyline> polyLine) = _$getMapDataState;
const factory EmpTrackingState.pageIndex(int currentIndex) = _$pageIndex;
const factory EmpTrackingState.error({String? error}) = _$ErrorState;
const factory EmpTrackingState.getMainListData(
List<GIS_EmployeeTracking> list, String? searchValue, int tabIndex) =
_$GetMainListData;
}
@freezed
class EmpTrackingEvent with _$EmpTrackingEvent {
const factory EmpTrackingEvent.getMainListData() = _$getMainListData;
const factory EmpTrackingEvent.searching(String searchValue, int i) =
_$searching;
const factory EmpTrackingEvent.getIndex(int i) = _$getIndex;
const factory EmpTrackingEvent.saveOfflineData(
GIS_EmployeeTracking empTracking) = _$saveOfflineData;
const factory EmpTrackingEvent.getMap() = _$getMap;
const factory EmpTrackingEvent.clearData(
{TextEditingController? gpsCoordinates}) = _$clearData;
const factory EmpTrackingEvent.setMarker({LatLng? location}) = _$setMarker;
}
@LazySingleton()
class EmpTrackingBloc extends Bloc<EmpTrackingEvent, EmpTrackingState> {
final empLocalRepository = EmpLocalRepository();
final empRepository = getIt.get<IEmpRepository>();
Map<MarkerId, Marker> markers = <MarkerId, Marker>{};
List<GIS_EmployeeTracking> empMainList = [];
final polyline = HashSet<Polyline>();
int index = 0;
double totalDistance = 0.0;
List<LatLng> latLongList = [];
Position? currentLocation;
EmpTrackingBloc() : super(EmpTrackingState.initial()) {
on<EmpTrackingEvent>(
(event, emit) async {
await event.when(
getMainListData: () async {
try {
empMainList = [];
emit(EmpTrackingState.loading());
final res = await empLocalRepository.getMainList();
empMainList = res;
emit(EmpTrackingState.getMainListData(empMainList, "", 0));
} catch (e) {
emit(EmpTrackingState.error(error: e.toString()));
}
},
searching: (v, i) {
List<GIS_EmployeeTracking>? filterList = [];
emit(EmpTrackingState.loading());
if (v.isNotEmpty) {
filterList = empMainList
.where((e) =>
((e.PS_Name?.toLowerCase().contains(v) ?? false) &&
((i == 1 && e.Upload_Status == "true") ||
(i == 2 && e.Upload_Status == "false") ||
(i == 0))))
.toList();
} else {
filterList = empMainList
.where((e) =>
(i == 1 && e.Upload_Status == "false" ||
e.PK_GIS_EmployeeTracking == 0) ||
(i == 2 && e.Upload_Status == "true") ||
(i == 0))
.toList();
}
emit(EmpTrackingState.getMainListData(filterList, v, i));
},
getIndex: (int value) {
index = value;
emit(EmpTrackingState.pageIndex(value));
},
saveOfflineData: (GIS_EmployeeTracking empTracking) async {
final id = await empLocalRepository.saveEmpData(empTracking);
final list = await GIS_EmployeeTracking.getLREntryByAID(id, "");
if (list.isNotEmpty) {
final resp = await empRepository.uploadEmpTracking(list, id);
if (resp != null) {
final result = await empLocalRepository.onUploadSuccess(resp);
if (result != null) {
empRepository.uploadFRAttachmentToServer(
result.mobileRecordId,
result.recordId,
);
}
}
}
},
getMap: () async {
emit(const EmpTrackingState.loading());
var status = await Permission.location.status;
if (!status.isGranted) {
await Permission.location.request();
}
currentLocation = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high);
List<LatLng> test = [];
test.add(LatLng(
currentLocation?.latitude ?? 0,
currentLocation?.longitude ?? 0,
));
emit(EmpTrackingState.getMapDataState(
currentLocation, test, 0, markers, polyline));
},
setMarker: (location) async {
double totalDistance = 0;
currentLocation = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high);
final markerId = MarkerId(
'employee_marker_${DateTime.now().millisecondsSinceEpoch}');
latLongList.add(LatLng(
location?.latitude ?? currentLocation?.latitude ?? 0,
location?.longitude ?? currentLocation?.longitude ?? 0,
));
if (latLongList.length >= 2) {
for (int i = 0; i < latLongList.length - 1; i++) {
latLongList.remove([0]);
totalDistance += SphericalUtil.computeDistanceBetween(
latLongList[i], latLongList[i + 1]);
}
}
final marker = Marker(
markerId: markerId,
position: LatLng(
location?.latitude ?? currentLocation?.latitude ?? 0,
location?.longitude ?? currentLocation?.longitude ?? 0),
infoWindow: InfoWindow(title: "Employee ${markers.length + 1}"),
);
polyline.add(Polyline(
polylineId: PolylineId('polyline_${DateTime.now()}'),
points: latLongList,
color: Colors.blue,
width: 2));
markers[markerId] = marker;
double value = (totalDistance * 100).roundToDouble() / 100;
int roundOffTotalDistance = value.round();
emit(EmpTrackingState.getMapDataState(
currentLocation,
latLongList,
roundOffTotalDistance,
markers,
polyline,
));
},
clearData: (TextEditingController? gpsCoordinates) async {
latLongList = [];
markers.clear();
polyline.clear();
totalDistance = 0;
gpsCoordinates?.clear();
currentLocation = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high);
latLongList.add(LatLng(
currentLocation?.latitude ?? 0,
currentLocation?.longitude ?? 0,
));
emit(EmpTrackingState.getMapDataState(
currentLocation, latLongList, 0, markers, polyline));
},
);
},
);
}
}

searching and sorting event are in this bloc

Here is some calls in which abstract class is created to get the data from API or local database there is two repositories

Online repository and locale format are same but only segregate the calls of online and online

// abstract class where declaration for methonds are created
abstract class IEmpRepository {
Future<UploadModel?> uploadEmpTracking(
List<GIS_EmployeeTracking> askQueryMain, int id);
}
// implementation of abstract class
@LazySingleton(as: IEmpRepository)
class EmpRepository implements IEmpRepository {
DioClient dioClient;
EmpRepository(this.dioClient);
@override
Future<UploadModel?> uploadEmpTracking(
List<GIS_EmployeeTracking> list, int id) async {
bool isConnected = await Utils.isNetWorkConnected();
List<Map<String, dynamic>> askQueryMain =
list.map((e) => e.toJsonET()).toList();
try {
if (isConnected) {
final resp = await dioClient.post(
ApiUtils.getMainLink() + Endpoints.empTracking,
data: FormData.fromMap({
"AskQueryMain": jsonEncode({"AskQueryMain": askQueryMain})
}),
);
if (resp != null) {
if (resp.statusCode == 200) {
final model = UploadModel.fromJson(resp.data);
return model;
}
}
} else {
Utils.showToast('Connect to Internet first!');
}
} on DioError catch (e) {
throw DioExceptions.fromDioError(e);
}
return null;
}
}

Conclusion:

users live tracking polylines and markers according to user movements showing is achieved in this service

bloc structured is used with code optimization

--

--

Vaishanvi Ingole
Vaishanvi Ingole

Written by Vaishanvi Ingole

Flutter developer | API integration| State Management | Firebase | SQL