Google map Integration
..
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
- User can calculate area by manually taping on map
- 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:
- Google Map Integration
- live Tracking Of user location
- Area calculation
- markers draw on map
- 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 serviceAdd 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




