geoflutterfire_plus 0.0.31 geoflutterfire_plus: ^0.0.31 copied to clipboard
geoflutterfire_plus enables your flutter app to store and query cloud firestore documents based on their geographic location.
import 'dart:async';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:geoflutterfire_plus/geoflutterfire_plus.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:rxdart/rxdart.dart';
import 'add_location.dart';
import 'firebase_options.dart';
import 'set_or_delete_location.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
runApp(const App());
}
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
sliderTheme: SliderThemeData(
overlayShape: SliderComponentShape.noOverlay,
),
),
home: const Example(),
// See using with converter example by removing comment below:
// home: const WithConverterExample(),
// See adding custom queries example by removing comment below:
// home: const AdditionalQueryExample(),
// home: const GeoFlutterFire2Example(),
);
}
}
/// Tokyo Station location for demo.
const _tokyoStation = LatLng(35.681236, 139.767125);
/// Reference to the collection where the location data is stored.
/// `withConverter` is available to type-safely define [CollectionReference].
final _collectionReference = FirebaseFirestore.instance.collection('locations');
/// Geo query geoQueryCondition.
class _GeoQueryCondition {
_GeoQueryCondition({
required this.radiusInKm,
required this.cameraPosition,
});
final double radiusInKm;
final CameraPosition cameraPosition;
}
class Example extends StatefulWidget {
const Example({super.key});
@override
ExampleState createState() => ExampleState();
}
/// Example page using [GoogleMap].
class ExampleState extends State<Example> {
/// [Marker]s on Google Maps.
Set<Marker> _markers = {};
/// [BehaviorSubject] of currently geo query radius and camera position.
final _geoQueryCondition = BehaviorSubject<_GeoQueryCondition>.seeded(
_GeoQueryCondition(
radiusInKm: _initialRadiusInKm,
cameraPosition: _initialCameraPosition,
),
);
/// [Stream] of geo query result.
late final Stream<List<DocumentSnapshot<Map<String, dynamic>>>> _stream =
_geoQueryCondition.switchMap(
(geoQueryCondition) =>
GeoCollectionReference(_collectionReference).subscribeWithin(
center: GeoFirePoint(
GeoPoint(
_cameraPosition.target.latitude,
_cameraPosition.target.longitude,
),
),
radiusInKm: geoQueryCondition.radiusInKm,
field: 'geo',
geopointFrom: (data) =>
(data['geo'] as Map<String, dynamic>)['geopoint'] as GeoPoint,
strictMode: true,
),
);
/// Updates [_markers] by fetched geo [DocumentSnapshot]s.
void _updateMarkersByDocumentSnapshots(
List<DocumentSnapshot<Map<String, dynamic>>> documentSnapshots,
) {
final markers = <Marker>{};
for (final ds in documentSnapshots) {
final id = ds.id;
final data = ds.data();
if (data == null) {
continue;
}
final name = data['name'] as String;
final geoPoint =
(data['geo'] as Map<String, dynamic>)['geopoint'] as GeoPoint;
markers.add(_createMarker(id: id, name: name, geoPoint: geoPoint));
}
debugPrint('📍 markers count: ${markers.length}');
setState(() {
_markers = markers;
});
}
/// Creates a [Marker] by fetched geo location.
Marker _createMarker({
required String id,
required String name,
required GeoPoint geoPoint,
}) =>
Marker(
markerId: MarkerId('(${geoPoint.latitude}, ${geoPoint.longitude})'),
position: LatLng(geoPoint.latitude, geoPoint.longitude),
infoWindow: InfoWindow(title: name),
onTap: () => showDialog<void>(
context: context,
builder: (context) => SetOrDeleteLocationDialog(
id: id,
name: name,
geoFirePoint: GeoFirePoint(
GeoPoint(geoPoint.latitude, geoPoint.longitude),
),
),
),
);
/// Current detecting radius in kilometers.
double get _radiusInKm => _geoQueryCondition.value.radiusInKm;
/// Current camera position on Google Maps.
CameraPosition get _cameraPosition => _geoQueryCondition.value.cameraPosition;
/// Initial geo query detection radius in km.
static const double _initialRadiusInKm = 1;
/// Google Maps initial camera zoom level.
static const double _initialZoom = 14;
/// Google Maps initial target position.
static final LatLng _initialTarget = LatLng(
_tokyoStation.latitude,
_tokyoStation.longitude,
);
/// Google Maps initial camera position.
static final _initialCameraPosition = CameraPosition(
target: _initialTarget,
zoom: _initialZoom,
);
@override
void dispose() {
_geoQueryCondition.close();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
GoogleMap(
zoomControlsEnabled: false,
myLocationButtonEnabled: false,
initialCameraPosition: _initialCameraPosition,
onMapCreated: (_) =>
_stream.listen(_updateMarkersByDocumentSnapshots),
markers: _markers,
circles: {
Circle(
circleId: const CircleId('value'),
center: LatLng(
_cameraPosition.target.latitude,
_cameraPosition.target.longitude,
),
// multiple 1000 to convert from kilometers to meters.
radius: _radiusInKm * 1000,
fillColor: Colors.black12,
strokeWidth: 0,
),
},
onCameraMove: (cameraPosition) {
debugPrint('📷 lat: ${cameraPosition.target.latitude}, '
'lng: ${cameraPosition.target.latitude}');
_geoQueryCondition.add(
_GeoQueryCondition(
radiusInKm: _radiusInKm,
cameraPosition: cameraPosition,
),
);
},
onLongPress: (latLng) => showDialog<void>(
context: context,
builder: (context) => AddLocationDialog(latLng: latLng),
),
),
Container(
width: double.infinity,
margin: const EdgeInsets.only(top: 64, left: 16, right: 16),
padding: const EdgeInsets.all(16),
decoration: const BoxDecoration(
color: Colors.black38,
borderRadius: BorderRadius.all(Radius.circular(8)),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
const Text(
'Debug window',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
'Currently detected count: '
'${_markers.length}',
style: const TextStyle(color: Colors.white),
),
const SizedBox(height: 8),
Text(
'Current radius: '
'${_radiusInKm.toStringAsFixed(1)} (km)',
style: const TextStyle(color: Colors.white),
),
const SizedBox(height: 8),
Slider(
value: _radiusInKm,
min: 1,
max: 100,
divisions: 99,
label: _radiusInKm.toStringAsFixed(1),
onChanged: (value) => _geoQueryCondition.add(
_GeoQueryCondition(
radiusInKm: value,
cameraPosition: _cameraPosition,
),
),
),
],
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () => showDialog<void>(
context: context,
builder: (context) => const AddLocationDialog(),
),
child: const Icon(Icons.add),
),
);
}
}