├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── example ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── noeatsleepdev │ │ │ │ └── geoflutterfireexample │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ └── values │ │ │ └── styles.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── ios │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ └── contents.xcworkspacedata │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h ├── lib │ ├── main.dart │ └── streambuilder_test.dart ├── pubspec.yaml └── test │ └── widget_test.dart ├── geoflutterfire.iml ├── lib ├── geoflutterfire.dart └── src │ ├── collection.dart │ ├── geoflutterfire.dart │ ├── models │ └── DistanceDocSnapshot.dart │ ├── point.dart │ └── util.dart └── pubspec.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | 3 | .DS_Store 4 | .dart_tool/ 5 | 6 | .packages 7 | .pub/ 8 | pubspec.lock 9 | 10 | build/ 11 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 1407091bfb5bb535630d4d596541817737da8412 8 | channel: unknown 9 | 10 | project_type: plugin 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 2.2.2 2 | * upgraded dependencies, latest before Flutter 2.0 and null safety 3 | 4 | ## 2.2.1 5 | * upgraded dependencies 6 | 7 | ## 2.1.0 8 | * fixed breaking changes 9 | * would not be able to access data using `doc.data['distance']` anymore 10 | 11 | ## 2.0.3+8 12 | * upgraded dependencies 13 | * fix for iOS build errors 14 | * fixes for breaking changes from 2.0.3 for stream builders 15 | * added a bug-fix for supporting stream builders 16 | 17 | ## 2.0.2 18 | * added support for filtering documents strictly/easily with respect to radius 19 | 20 | ## 2.0.1+1 21 | * bumped up the versions of kotlin-plugin and gradle. 22 | * Support for GeoPoints nested inside the firestore document 23 | 24 | ## 2.0.0 25 | * **Breaking change**. Migrate from the deprecated original Android Support 26 | Library to AndroidX. This shouldn't result in any functional changes, but it 27 | requires any Android apps using this plugin to [also 28 | migrate](https://developer.android.com/jetpack/androidx/migrate) if they're 29 | using the original support library. 30 | * reverted to flutter stable channel from master. 31 | 32 | ## 1.0.2 33 | * Refactored code to adhere to best practices(again) 34 | 35 | ## 1.0.1 36 | * Refactored code to adhere to best practices 37 | 38 | ## 1.0.0 39 | * Initial Release 40 | 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Darshan Narayanaswamy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GeoFlutterFire :earth_africa: 2 | 3 | [![version][version-badge]][package] 4 | [![MIT License][license-badge]][license] 5 | [![PRs Welcome][prs-badge]](https://makeapullrequest.com) 6 | 7 | GeoFlutterFire is an open-source library that allows you to store and query a set of keys based on their geographic location. At its heart, GeoFlutterFire simply stores locations with string keys. Its main benefit, however, is the possibility of retrieving only those keys within a given geographic area - all in realtime. 8 | 9 | GeoFlutterFire uses the Firebase Firestore Database for data storage, allowing query results to be updated in realtime as they change. GeoFlutterFire selectively loads only the data near certain locations, keeping your applications light and responsive, even with extremely large datasets. 10 | 11 | GeoFlutterFire is designed as a lightweight add-on to cloud_firestore plugin. To keep things simple, GeoFlutterFire stores data in its own format within your Firestore database. This allows your existing data format and Security Rules to remain unchanged while still providing you with an easy solution for geo queries. 12 | 13 | Heavily influenced by [GeoFireX](https://github.com/codediodeio/geofirex) :fire::fire: from [Jeff Delaney](https://github.com/codediodeio) :sunglasses: 14 | 15 | :tv: Checkout this amazing tutorial on [fireship](https://fireship.io/lessons/flutter-realtime-geolocation-firebase/) by Jeff, featuring the plugin!! 16 | 17 | ## Getting Started 18 | 19 | You should ensure that you add GeoFlutterFire as a dependency in your flutter project. 20 | 21 | ```yaml 22 | dependencies: 23 | geoflutterfire: 24 | ``` 25 | 26 | You can also reference the git repo directly if you want: 27 | 28 | ```yaml 29 | dependencies: 30 | geoflutterfire: 31 | git: git://github.com/DarshanGowda0/GeoFlutterFire.git 32 | ``` 33 | 34 | You should then run `flutter packages get` or update your packages in IntelliJ. 35 | 36 | ## Example 37 | 38 | There is a detailed example project in the `example` folder. Check that out or keep reading! 39 | 40 | ## Initialize 41 | 42 | You need a firebase project with [Firestore](https://pub.dartlang.org/packages/cloud_firestore) setup. 43 | 44 | ```dart 45 | import 'package:geoflutterfire/geoflutterfire.dart'; 46 | import 'package:cloud_firestore/cloud_firestore.dart'; 47 | 48 | // Init firestore and geoFlutterFire 49 | final geo = Geoflutterfire(); 50 | final _firestore = FirebaseFirestore.instance; 51 | ``` 52 | 53 | ## Writing Geo data 54 | 55 | Add geo data to your firestore document using `GeoFirePoint` 56 | 57 | ```dart 58 | GeoFirePoint myLocation = geo.point(latitude: 12.960632, longitude: 77.641603); 59 | ``` 60 | 61 | Next, add the GeoFirePoint to you document using Firestore's add method 62 | 63 | ```dart 64 | _firestore 65 | .collection('locations') 66 | .add({'name': 'random name', 'position': myLocation.data}); 67 | ``` 68 | 69 | Calling `geoFirePoint.data` returns an object that contains a [geohash string](https://www.movable-type.co.uk/scripts/geohash.html) and a [Firestore GeoPoint](https://firebase.google.com/docs/reference/android/com/google/firebase/firestore/GeoPoint). It should look like this in your database. You can name the object whatever you want and even save multiple points on a single document. 70 | 71 | ![](https://firebasestorage.googleapis.com/v0/b/geo-test-c92e4.appspot.com/o/point1.png?alt=media&token=0c833700-3dbd-476a-99a9-41c1143dbe97) 72 | 73 | ## Query Geo data 74 | 75 | To query a collection of documents with 50kms from a point 76 | 77 | ```dart 78 | // Create a geoFirePoint 79 | GeoFirePoint center = geo.point(latitude: 12.960632, longitude: 77.641603); 80 | 81 | // get the collection reference or query 82 | var collectionReference = _firestore.collection('locations'); 83 | 84 | double radius = 50; 85 | String field = 'position'; 86 | 87 | Stream> stream = geo.collection(collectionRef: collectionReference) 88 | .within(center: center, radius: radius, field: field); 89 | ``` 90 | 91 | The within function returns a Stream of the list of DocumentSnapshot data, plus some useful metadata like distance from the centerpoint. 92 | 93 | ```dart 94 | stream.listen((List documentList) { 95 | // doSomething() 96 | }); 97 | ``` 98 | 99 | You now have a realtime stream of data to visualize on a map. 100 | ![](https://firebasestorage.googleapis.com/v0/b/geoflutterfire.appspot.com/o/geflutterfire.gif?alt=media&token=8dc3aa9c-ee68-4dfe-9093-c3c1c48979dc) 101 | 102 | ## :notebook: API 103 | 104 | ### `collection(collectionRef: CollectionReference)` 105 | 106 | Creates a GeoCollectionRef which can be used to make geo queries, alternatively can also be used to write data just like firestore's add / set functionality. 107 | 108 | Example: 109 | 110 | ```dart 111 | // Collection ref 112 | // var collectionReference = _firestore.collection('locations').where('city', isEqualTo: 'bangalore'); 113 | var collectionReference = _firestore.collection('locations'); 114 | var geoRef = geo.collection(collectionRef: collectionReference); 115 | ``` 116 | 117 | Note: collectionReference can be of type CollectionReference or Query 118 | 119 | #### Performing Geo-Queries 120 | 121 | `geoRef.within(center: GeoFirePoint, radius: double, field: String, {strictMode: bool})` 122 | 123 | Query the parent Firestore collection by geographic distance. It will return documents that exist within X kilometers of the center-point. 124 | `field` supports nested objects in the firestore document. 125 | 126 | **Note:** Use optional parameter `strictMode = true` to filter the documents strictly within the bound of given radius. 127 | 128 | Example: 129 | 130 | ```dart 131 | // For GeoFirePoint stored at the root of the firestore document 132 | geoRef.within(center: centerGeoPoint, radius: 50, field: 'position', strictMode: true); 133 | 134 | // For GeoFirePoint nested in other objects of the firestore document 135 | geoRef.within(center: centerGeoPoint, radius: 50, field: 'address.location.position', strictMode: true); 136 | ``` 137 | 138 | Each `documentSnapshot.data()` also contains `distance` calculated on the query. 139 | 140 | **Returns:** `Stream>` 141 | 142 | #### Write Data 143 | 144 | Write data just like you would in Firestore 145 | 146 | `geoRef.add(data)` 147 | 148 | Or use one of the client's conveniece methods 149 | 150 | - `geoRef.setDoc(String id, var data, {bool merge})` - Set a document in the collection with an ID. 151 | - `geoRef.setPoint(String id, String field, double latitude, double longitude)`- Add a geohash to an existing doc 152 | 153 | #### Read Data 154 | 155 | In addition to Geo-Queries, you can also read the collection like you would normally in Firestore, but as an Observable 156 | 157 | - `geoRef.data()`- Stream of documentSnapshot 158 | - `geoRef.snapshot()`- Stream of Firestore QuerySnapshot 159 | 160 | ### `point(latitude: double, longitude: double)` 161 | 162 | Returns a GeoFirePoint allowing you to create geohashes, format data, and calculate relative distance. 163 | 164 | Example: `var point = geo.point(38, -119)` 165 | 166 | #### Getters 167 | 168 | - `point.hash` Returns a geohash string at precision 9 169 | - `point.geoPoint` Returns a Firestore GeoPoint 170 | - `point.data` Returns data object suitable for saving to the Firestore database 171 | 172 | #### Geo Calculations 173 | 174 | - `point.distance(latitude, longitude)` Haversine distance to a point 175 | 176 | ## :zap: Tips 177 | 178 | ### Scale to Massive Collections 179 | 180 | It's possible to build Firestore collections with billions of documents. One of the main motivations of this project was to make geoqueries possible on a queried subset of data. You can pass a Query instead of a CollectionReference into the collection(), then all geoqueries will be scoped with the constraints of that query. 181 | 182 | Note: This query requires a composite index, which you will be prompted to create with an error from Firestore on the first request. 183 | 184 | Example: 185 | 186 | ```dart 187 | var queryRef = _firestore.collection('locations').where('city', isEqualTo: 'bangalore'); 188 | var stream = geo 189 | .collection(collectionRef: queryRef) 190 | .within(center: center, radius: rad, field: 'position'); 191 | ``` 192 | 193 | ### Usage of strictMode 194 | 195 | It's advisable to use `strictMode = false` for smaller radius to make use of documents from neighbouring hashes as well. 196 | 197 | As the radius increases to a large number, the neighbouring hash precisions fetch documents which would be considerably far from the radius bounds, hence its advisable to use `strictMode = true` for larger radius. 198 | 199 | **Note:** filtering for strictMode happens on client side, hence filtering at larger radius is at the expense of making unnecessary document reads. 200 | 201 | ### Make Dynamic Queries the RxDart Way 202 | 203 | ```dart 204 | var radius = BehaviorSubject.seeded(1.0); 205 | var collectionReference = _firestore.collection('locations'); 206 | 207 | stream = radius.switchMap((rad) { 208 | return geo 209 | .collection(collectionRef: collectionReference) 210 | .within(center: center, radius: rad, field: 'position'); 211 | }); 212 | 213 | // Now update your query 214 | radius.add(25); 215 | ``` 216 | 217 | ### Limitations 218 | 219 | - range queries on multiple fields is not supported by cloud_firestore at the moment, since this library already uses range query on `geohash` field, you cannot perform range queries with `GeoFireCollectionRef`. 220 | - `limit()` and `orderBy()` are not supported at the moment. `limit()` could be used to limit docs inside each hash individually which would result in running limit on all 9 hashes inside the specified radius. `orderBy()` is first run on `geohashes` in the library, hence appending `orderBy()` with another feild wouldn't produce expected results. Alternatively documents can be sorted on client side. 221 | 222 | [version-badge]: https://img.shields.io/pub/vpre/geoflutterfire.svg 223 | [package]: https://pub.dartlang.org/packages/geoflutterfire 224 | [license-badge]: https://img.shields.io/github/license/DarshanGowda0/GeoFlutterFire.svg 225 | [license]: https://github.com/DarshanGowda0/GeoFlutterFire/blob/master/LICENSE 226 | [prs-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square 227 | [prs]: http://makeapullrequest.com 228 | [github-watch-badge]: https://img.shields.io/github/watchers/DarshanGowda0/GeoFlutterFire.svg?style=social 229 | [github-watch]: https://github.com/DarshanGowda0/GeoFlutterFire/watchers 230 | [github-star-badge]: https://img.shields.io/github/stars/DarshanGowda0/GeoFlutterFire.svg?style=social 231 | [github-star]: https://github.com/DarshanGowda0/GeoFlutterFire/stargazers 232 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.lock 4 | *.log 5 | *.pyc 6 | *.swp 7 | .DS_Store 8 | .atom/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # Visual Studio Code related 20 | .vscode/ 21 | 22 | # Flutter/Dart/Pub related 23 | **/doc/api/ 24 | .dart_tool/ 25 | .flutter-plugins 26 | .flutter-plugins-dependencies 27 | .packages 28 | .pub-cache/ 29 | .pub/ 30 | build/ 31 | 32 | # Android related 33 | **/android/**/gradle-wrapper.jar 34 | **/android/.gradle 35 | **/android/captures/ 36 | **/android/gradlew 37 | **/android/gradlew.bat 38 | **/android/local.properties 39 | **/android/**/GeneratedPluginRegistrant.java 40 | 41 | # iOS/XCode related 42 | **/ios/**/*.mode1v3 43 | **/ios/**/*.mode2v3 44 | **/ios/**/*.moved-aside 45 | **/ios/**/*.pbxuser 46 | **/ios/**/*.perspectivev3 47 | **/ios/**/*sync/ 48 | **/ios/**/.sconsign.dblite 49 | **/ios/**/.tags* 50 | **/ios/**/.vagrant/ 51 | **/ios/**/DerivedData/ 52 | **/ios/**/Icon? 53 | **/ios/**/Pods/ 54 | **/ios/**/.symlinks/ 55 | **/ios/**/profile 56 | **/ios/**/xcuserdata 57 | **/ios/.generated/ 58 | **/ios/Flutter/flutter_export_environment.sh 59 | **/ios/Flutter/App.framework 60 | **/ios/Flutter/Flutter.framework 61 | **/ios/Flutter/Generated.xcconfig 62 | **/ios/Flutter/app.flx 63 | **/ios/Flutter/app.zip 64 | **/ios/Flutter/flutter_assets/ 65 | **/ios/ServiceDefinitions.json 66 | **/ios/Runner/GeneratedPluginRegistrant.* 67 | 68 | # Exceptions to above rules. 69 | !**/ios/**/default.mode1v3 70 | !**/ios/**/default.mode2v3 71 | !**/ios/**/default.pbxuser 72 | !**/ios/**/default.perspectivev3 73 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 74 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 1407091bfb5bb535630d4d596541817737da8412 8 | channel: unknown 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # geoflutterfire_example 2 | 3 | Demonstrates how to use the geoflutterfire plugin. 4 | 5 | ```dart 6 | import 'package:flutter/material.dart'; 7 | import 'package:geoflutterfire/geoflutterfire.dart'; 8 | import 'package:cloud_firestore/cloud_firestore.dart'; 9 | import 'package:geoflutterfire/src/point.dart'; 10 | import 'package:google_maps_flutter/google_maps_flutter.dart'; 11 | import 'package:rxdart/rxdart.dart'; 12 | 13 | void main() => runApp(MaterialApp( 14 | title: 'Geo Flutter Fire example', 15 | home: MyApp(), 16 | debugShowCheckedModeBanner: false, 17 | )); 18 | 19 | class MyApp extends StatefulWidget { 20 | @override 21 | _MyAppState createState() => _MyAppState(); 22 | } 23 | 24 | class _MyAppState extends State { 25 | GoogleMapController _mapController; 26 | TextEditingController _latitudeController, _longitudeController; 27 | 28 | // firestore init 29 | final _firestore = FirebaseFirestore.instance; 30 | Geoflutterfire geo; 31 | Stream> stream; 32 | var radius = BehaviorSubject(seedValue: 1.0); 33 | 34 | @override 35 | void initState() { 36 | super.initState(); 37 | _latitudeController = TextEditingController(); 38 | _longitudeController = TextEditingController(); 39 | 40 | geo = Geoflutterfire(); 41 | GeoFirePoint center = geo.point(latitude: 12.960632, longitude: 77.641603); 42 | stream = radius.switchMap((rad) { 43 | var collectionReference = _firestore.collection('locations'); 44 | // .where('name', isEqualTo: 'darshan'); 45 | return geo 46 | .collection(collectionRef: collectionReference) 47 | .within(center: center, radius: rad, field: 'position'); 48 | 49 | /* 50 | ****Example to specify nested object**** 51 | 52 | var collectionReference = _firestore.collection('nestedLocations'); 53 | // .where('name', isEqualTo: 'darshan'); 54 | return geo.collection(collectionRef: collectionReference).within( 55 | center: center, radius: rad, field: 'address.location.position'); 56 | 57 | */ 58 | }); 59 | } 60 | 61 | @override 62 | void dispose() { 63 | super.dispose(); 64 | radius.close(); 65 | } 66 | 67 | @override 68 | Widget build(BuildContext context) { 69 | return MaterialApp( 70 | home: Scaffold( 71 | appBar: AppBar( 72 | title: const Text('GeoFlutterFire'), 73 | actions: [ 74 | IconButton( 75 | onPressed: _mapController == null 76 | ? null 77 | : () { 78 | _showHome(); 79 | }, 80 | icon: Icon(Icons.home), 81 | ) 82 | ], 83 | ), 84 | body: Container( 85 | child: Column( 86 | crossAxisAlignment: CrossAxisAlignment.center, 87 | children: [ 88 | Center( 89 | child: Card( 90 | elevation: 4, 91 | margin: EdgeInsets.symmetric(vertical: 8), 92 | child: SizedBox( 93 | width: MediaQuery.of(context).size.width - 30, 94 | height: MediaQuery.of(context).size.height * (1 / 3), 95 | child: GoogleMap( 96 | onMapCreated: _onMapCreated, 97 | initialCameraPosition: const CameraPosition( 98 | target: LatLng(12.960632, 77.641603), 99 | zoom: 15.0, 100 | ), 101 | ), 102 | ), 103 | ), 104 | ), 105 | Padding( 106 | padding: const EdgeInsets.only(top: 8.0), 107 | child: Slider( 108 | min: 1, 109 | max: 200, 110 | divisions: 4, 111 | value: _value, 112 | label: _label, 113 | activeColor: Colors.blue, 114 | inactiveColor: Colors.blue.withOpacity(0.2), 115 | onChanged: (double value) => changed(value), 116 | ), 117 | ), 118 | Row( 119 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 120 | children: [ 121 | Container( 122 | width: 100, 123 | child: TextField( 124 | controller: _latitudeController, 125 | keyboardType: TextInputType.number, 126 | textInputAction: TextInputAction.next, 127 | decoration: InputDecoration( 128 | labelText: 'lat', 129 | border: OutlineInputBorder( 130 | borderRadius: BorderRadius.circular(8), 131 | )), 132 | ), 133 | ), 134 | Container( 135 | width: 100, 136 | child: TextField( 137 | controller: _longitudeController, 138 | keyboardType: TextInputType.number, 139 | decoration: InputDecoration( 140 | labelText: 'lng', 141 | border: OutlineInputBorder( 142 | borderRadius: BorderRadius.circular(8), 143 | )), 144 | ), 145 | ), 146 | MaterialButton( 147 | color: Colors.blue, 148 | onPressed: () { 149 | double lat = double.parse(_latitudeController.text); 150 | double lng = double.parse(_longitudeController.text); 151 | _addPoint(lat, lng); 152 | }, 153 | child: Text( 154 | 'ADD', 155 | style: TextStyle(color: Colors.white), 156 | ), 157 | ) 158 | ], 159 | ), 160 | MaterialButton( 161 | color: Colors.amber, 162 | child: Text( 163 | 'Add nested ', 164 | style: TextStyle(color: Colors.white), 165 | ), 166 | onPressed: () { 167 | double lat = double.parse(_latitudeController.text); 168 | double lng = double.parse(_longitudeController.text); 169 | _addNestedPoint(lat, lng); 170 | }, 171 | ) 172 | ], 173 | ), 174 | ), 175 | ), 176 | ); 177 | } 178 | 179 | void _onMapCreated(GoogleMapController controller) { 180 | setState(() { 181 | _mapController = controller; 182 | // _showHome(); 183 | //start listening after map is created 184 | stream.listen((List documentList) { 185 | _updateMarkers(documentList); 186 | }); 187 | }); 188 | } 189 | 190 | void _showHome() { 191 | _mapController.animateCamera(CameraUpdate.newCameraPosition( 192 | const CameraPosition( 193 | target: LatLng(12.960632, 77.641603), 194 | zoom: 15.0, 195 | ), 196 | )); 197 | } 198 | 199 | void _addPoint(double lat, double lng) { 200 | GeoFirePoint geoFirePoint = geo.point(latitude: lat, longitude: lng); 201 | _firestore 202 | .collection('locations') 203 | .add({'name': 'random name', 'position': geoFirePoint.data}).then((_) { 204 | print('added ${geoFirePoint.hash} successfully'); 205 | }); 206 | } 207 | 208 | //example to add geoFirePoint inside nested object 209 | void _addNestedPoint(double lat, double lng) { 210 | GeoFirePoint geoFirePoint = geo.point(latitude: lat, longitude: lng); 211 | _firestore.collection('nestedLocations').add({ 212 | 'name': 'random name', 213 | 'address': { 214 | 'location': {'position': geoFirePoint.data} 215 | } 216 | }).then((_) { 217 | print('added ${geoFirePoint.hash} successfully'); 218 | }); 219 | } 220 | 221 | void _addMarker(double lat, double lng) { 222 | var _marker = MarkerOptions( 223 | position: LatLng(lat, lng), 224 | icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueViolet), 225 | ); 226 | setState(() { 227 | _mapController.addMarker(_marker); 228 | }); 229 | } 230 | 231 | void _updateMarkers(List documentList) { 232 | documentList.forEach((DocumentSnapshot document) { 233 | GeoPoint point = document.data['position']['geopoint']; 234 | _addMarker(point.latitude, point.longitude); 235 | }); 236 | } 237 | 238 | double _value = 20.0; 239 | String _label = ''; 240 | 241 | changed(value) { 242 | setState(() { 243 | _value = value; 244 | _label = '${_value.toInt().toString()} kms'; 245 | _mapController.clearMarkers(); 246 | }); 247 | radius.add(value); 248 | } 249 | } 250 | ``` 251 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 28 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "com.noeatsleepdev.geoflutterfireexample" 42 | minSdkVersion 16 43 | targetSdkVersion 28 44 | multiDexEnabled true 45 | versionCode flutterVersionCode.toInteger() 46 | versionName flutterVersionName 47 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 48 | } 49 | 50 | buildTypes { 51 | release { 52 | // TODO: Add your own signing config for the release build. 53 | // Signing with the debug keys for now, so `flutter run --release` works. 54 | signingConfig signingConfigs.debug 55 | } 56 | } 57 | } 58 | 59 | flutter { 60 | source '../..' 61 | } 62 | 63 | dependencies { 64 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 65 | testImplementation 'junit:junit:4.12' 66 | androidTestImplementation 'androidx.test:runner:1.1.1' 67 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 68 | implementation 'com.google.firebase:firebase-core:16.0.8' 69 | } 70 | apply plugin: 'com.google.gms.google-services' -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 15 | 19 | 20 | 21 | 23 | 30 | 34 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/noeatsleepdev/geoflutterfireexample/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.noeatsleepdev.geoflutterfireexample 2 | 3 | import android.os.Bundle 4 | 5 | import io.flutter.app.FlutterActivity 6 | import io.flutter.plugins.GeneratedPluginRegistrant 7 | 8 | class MainActivity: FlutterActivity() { 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | GeneratedPluginRegistrant.registerWith(this) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmany/GeoFlutterFire/656e1e4af2fa5aabf8712b8109df64cc01460cf1/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmany/GeoFlutterFire/656e1e4af2fa5aabf8712b8109df64cc01460cf1/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmany/GeoFlutterFire/656e1e4af2fa5aabf8712b8109df64cc01460cf1/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmany/GeoFlutterFire/656e1e4af2fa5aabf8712b8109df64cc01460cf1/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmany/GeoFlutterFire/656e1e4af2fa5aabf8712b8109df64cc01460cf1/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.31' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.google.gms:google-services:4.2.0' 10 | classpath 'com.android.tools.build:gradle:3.4.0' 11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | google() 18 | jcenter() 19 | } 20 | } 21 | 22 | rootProject.buildDir = '../build' 23 | subprojects { 24 | project.buildDir = "${rootProject.buildDir}/${project.name}" 25 | } 26 | subprojects { 27 | project.evaluationDependsOn(':app') 28 | } 29 | 30 | task clean(type: Delete) { 31 | delete rootProject.buildDir 32 | } 33 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | android.enableR8=true 5 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat May 04 18:10:56 IST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def parse_KV_file(file, separator='=') 14 | file_abs_path = File.expand_path(file) 15 | if !File.exists? file_abs_path 16 | return []; 17 | end 18 | pods_ary = [] 19 | skip_line_start_symbols = ["#", "/"] 20 | File.foreach(file_abs_path) { |line| 21 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 22 | plugin = line.split(pattern=separator) 23 | if plugin.length == 2 24 | podname = plugin[0].strip() 25 | path = plugin[1].strip() 26 | podpath = File.expand_path("#{path}", file_abs_path) 27 | pods_ary.push({:name => podname, :path => podpath}); 28 | else 29 | puts "Invalid plugin specification: #{line}" 30 | end 31 | } 32 | return pods_ary 33 | end 34 | 35 | target 'Runner' do 36 | use_frameworks! 37 | 38 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 39 | # referring to absolute paths on developers' machines. 40 | system('rm -rf .symlinks') 41 | system('mkdir -p .symlinks/plugins') 42 | 43 | # Flutter Pods 44 | generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') 45 | if generated_xcode_build_settings.empty? 46 | puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first." 47 | end 48 | generated_xcode_build_settings.map { |p| 49 | if p[:name] == 'FLUTTER_FRAMEWORK_DIR' 50 | symlink = File.join('.symlinks', 'flutter') 51 | File.symlink(File.dirname(p[:path]), symlink) 52 | pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) 53 | end 54 | } 55 | 56 | # Plugin Pods 57 | plugin_pods = parse_KV_file('../.flutter-plugins') 58 | plugin_pods.map { |p| 59 | symlink = File.join('.symlinks', 'plugins', p[:name]) 60 | File.symlink(p[:path], symlink) 61 | pod p[:name], :path => File.join(symlink, 'ios') 62 | } 63 | end 64 | 65 | post_install do |installer| 66 | installer.pods_project.targets.each do |target| 67 | target.build_configurations.each do |config| 68 | config.build_settings['ENABLE_BITCODE'] = 'NO' 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 13 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; 14 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 15 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 16 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXCopyFilesBuildPhase section */ 20 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 21 | isa = PBXCopyFilesBuildPhase; 22 | buildActionMask = 2147483647; 23 | dstPath = ""; 24 | dstSubfolderSpec = 10; 25 | files = ( 26 | ); 27 | name = "Embed Frameworks"; 28 | runOnlyForDeploymentPostprocessing = 0; 29 | }; 30 | /* End PBXCopyFilesBuildPhase section */ 31 | 32 | /* Begin PBXFileReference section */ 33 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 34 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 35 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 36 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 37 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 38 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 39 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 40 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 41 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 43 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 44 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 45 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 46 | /* End PBXFileReference section */ 47 | 48 | /* Begin PBXFrameworksBuildPhase section */ 49 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 50 | isa = PBXFrameworksBuildPhase; 51 | buildActionMask = 2147483647; 52 | files = ( 53 | ); 54 | runOnlyForDeploymentPostprocessing = 0; 55 | }; 56 | /* End PBXFrameworksBuildPhase section */ 57 | 58 | /* Begin PBXGroup section */ 59 | 9740EEB11CF90186004384FC /* Flutter */ = { 60 | isa = PBXGroup; 61 | children = ( 62 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 63 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 64 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 65 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 66 | ); 67 | name = Flutter; 68 | sourceTree = ""; 69 | }; 70 | 97C146E51CF9000F007C117D = { 71 | isa = PBXGroup; 72 | children = ( 73 | 9740EEB11CF90186004384FC /* Flutter */, 74 | 97C146F01CF9000F007C117D /* Runner */, 75 | 97C146EF1CF9000F007C117D /* Products */, 76 | ); 77 | sourceTree = ""; 78 | }; 79 | 97C146EF1CF9000F007C117D /* Products */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | 97C146EE1CF9000F007C117D /* Runner.app */, 83 | ); 84 | name = Products; 85 | sourceTree = ""; 86 | }; 87 | 97C146F01CF9000F007C117D /* Runner */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 91 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 92 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 93 | 97C147021CF9000F007C117D /* Info.plist */, 94 | 97C146F11CF9000F007C117D /* Supporting Files */, 95 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 96 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 97 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 98 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 99 | ); 100 | path = Runner; 101 | sourceTree = ""; 102 | }; 103 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | ); 107 | name = "Supporting Files"; 108 | sourceTree = ""; 109 | }; 110 | /* End PBXGroup section */ 111 | 112 | /* Begin PBXNativeTarget section */ 113 | 97C146ED1CF9000F007C117D /* Runner */ = { 114 | isa = PBXNativeTarget; 115 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 116 | buildPhases = ( 117 | 9740EEB61CF901F6004384FC /* Run Script */, 118 | 97C146EA1CF9000F007C117D /* Sources */, 119 | 97C146EB1CF9000F007C117D /* Frameworks */, 120 | 97C146EC1CF9000F007C117D /* Resources */, 121 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 122 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 123 | ); 124 | buildRules = ( 125 | ); 126 | dependencies = ( 127 | ); 128 | name = Runner; 129 | productName = Runner; 130 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 131 | productType = "com.apple.product-type.application"; 132 | }; 133 | /* End PBXNativeTarget section */ 134 | 135 | /* Begin PBXProject section */ 136 | 97C146E61CF9000F007C117D /* Project object */ = { 137 | isa = PBXProject; 138 | attributes = { 139 | LastUpgradeCheck = 0910; 140 | ORGANIZATIONNAME = "The Chromium Authors"; 141 | TargetAttributes = { 142 | 97C146ED1CF9000F007C117D = { 143 | CreatedOnToolsVersion = 7.3.1; 144 | LastSwiftMigration = 0910; 145 | }; 146 | }; 147 | }; 148 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 149 | compatibilityVersion = "Xcode 3.2"; 150 | developmentRegion = English; 151 | hasScannedForEncodings = 0; 152 | knownRegions = ( 153 | en, 154 | Base, 155 | ); 156 | mainGroup = 97C146E51CF9000F007C117D; 157 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 158 | projectDirPath = ""; 159 | projectRoot = ""; 160 | targets = ( 161 | 97C146ED1CF9000F007C117D /* Runner */, 162 | ); 163 | }; 164 | /* End PBXProject section */ 165 | 166 | /* Begin PBXResourcesBuildPhase section */ 167 | 97C146EC1CF9000F007C117D /* Resources */ = { 168 | isa = PBXResourcesBuildPhase; 169 | buildActionMask = 2147483647; 170 | files = ( 171 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 172 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 173 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, 174 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 175 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 176 | ); 177 | runOnlyForDeploymentPostprocessing = 0; 178 | }; 179 | /* End PBXResourcesBuildPhase section */ 180 | 181 | /* Begin PBXShellScriptBuildPhase section */ 182 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 183 | isa = PBXShellScriptBuildPhase; 184 | buildActionMask = 2147483647; 185 | files = ( 186 | ); 187 | inputPaths = ( 188 | ); 189 | name = "Thin Binary"; 190 | outputPaths = ( 191 | ); 192 | runOnlyForDeploymentPostprocessing = 0; 193 | shellPath = /bin/sh; 194 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 195 | }; 196 | 9740EEB61CF901F6004384FC /* Run Script */ = { 197 | isa = PBXShellScriptBuildPhase; 198 | buildActionMask = 2147483647; 199 | files = ( 200 | ); 201 | inputPaths = ( 202 | ); 203 | name = "Run Script"; 204 | outputPaths = ( 205 | ); 206 | runOnlyForDeploymentPostprocessing = 0; 207 | shellPath = /bin/sh; 208 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 209 | }; 210 | /* End PBXShellScriptBuildPhase section */ 211 | 212 | /* Begin PBXSourcesBuildPhase section */ 213 | 97C146EA1CF9000F007C117D /* Sources */ = { 214 | isa = PBXSourcesBuildPhase; 215 | buildActionMask = 2147483647; 216 | files = ( 217 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 218 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 219 | ); 220 | runOnlyForDeploymentPostprocessing = 0; 221 | }; 222 | /* End PBXSourcesBuildPhase section */ 223 | 224 | /* Begin PBXVariantGroup section */ 225 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 226 | isa = PBXVariantGroup; 227 | children = ( 228 | 97C146FB1CF9000F007C117D /* Base */, 229 | ); 230 | name = Main.storyboard; 231 | sourceTree = ""; 232 | }; 233 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 234 | isa = PBXVariantGroup; 235 | children = ( 236 | 97C147001CF9000F007C117D /* Base */, 237 | ); 238 | name = LaunchScreen.storyboard; 239 | sourceTree = ""; 240 | }; 241 | /* End PBXVariantGroup section */ 242 | 243 | /* Begin XCBuildConfiguration section */ 244 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 245 | isa = XCBuildConfiguration; 246 | buildSettings = { 247 | ALWAYS_SEARCH_USER_PATHS = NO; 248 | CLANG_ANALYZER_NONNULL = YES; 249 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 250 | CLANG_CXX_LIBRARY = "libc++"; 251 | CLANG_ENABLE_MODULES = YES; 252 | CLANG_ENABLE_OBJC_ARC = YES; 253 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 254 | CLANG_WARN_BOOL_CONVERSION = YES; 255 | CLANG_WARN_COMMA = YES; 256 | CLANG_WARN_CONSTANT_CONVERSION = YES; 257 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 258 | CLANG_WARN_EMPTY_BODY = YES; 259 | CLANG_WARN_ENUM_CONVERSION = YES; 260 | CLANG_WARN_INFINITE_RECURSION = YES; 261 | CLANG_WARN_INT_CONVERSION = YES; 262 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 263 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 264 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 265 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 266 | CLANG_WARN_STRICT_PROTOTYPES = YES; 267 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 268 | CLANG_WARN_UNREACHABLE_CODE = YES; 269 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 270 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 271 | COPY_PHASE_STRIP = NO; 272 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 273 | ENABLE_NS_ASSERTIONS = NO; 274 | ENABLE_STRICT_OBJC_MSGSEND = YES; 275 | GCC_C_LANGUAGE_STANDARD = gnu99; 276 | GCC_NO_COMMON_BLOCKS = YES; 277 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 278 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 279 | GCC_WARN_UNDECLARED_SELECTOR = YES; 280 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 281 | GCC_WARN_UNUSED_FUNCTION = YES; 282 | GCC_WARN_UNUSED_VARIABLE = YES; 283 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 284 | MTL_ENABLE_DEBUG_INFO = NO; 285 | SDKROOT = iphoneos; 286 | TARGETED_DEVICE_FAMILY = "1,2"; 287 | VALIDATE_PRODUCT = YES; 288 | }; 289 | name = Profile; 290 | }; 291 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 292 | isa = XCBuildConfiguration; 293 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 294 | buildSettings = { 295 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 296 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 297 | DEVELOPMENT_TEAM = S8QB4VV633; 298 | ENABLE_BITCODE = NO; 299 | FRAMEWORK_SEARCH_PATHS = ( 300 | "$(inherited)", 301 | "$(PROJECT_DIR)/Flutter", 302 | ); 303 | INFOPLIST_FILE = Runner/Info.plist; 304 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 305 | LIBRARY_SEARCH_PATHS = ( 306 | "$(inherited)", 307 | "$(PROJECT_DIR)/Flutter", 308 | ); 309 | PRODUCT_BUNDLE_IDENTIFIER = com.noeatsleepdev.geoflutterfireExample; 310 | PRODUCT_NAME = "$(TARGET_NAME)"; 311 | SWIFT_VERSION = 4.0; 312 | VERSIONING_SYSTEM = "apple-generic"; 313 | }; 314 | name = Profile; 315 | }; 316 | 97C147031CF9000F007C117D /* Debug */ = { 317 | isa = XCBuildConfiguration; 318 | buildSettings = { 319 | ALWAYS_SEARCH_USER_PATHS = NO; 320 | CLANG_ANALYZER_NONNULL = YES; 321 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 322 | CLANG_CXX_LIBRARY = "libc++"; 323 | CLANG_ENABLE_MODULES = YES; 324 | CLANG_ENABLE_OBJC_ARC = YES; 325 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 326 | CLANG_WARN_BOOL_CONVERSION = YES; 327 | CLANG_WARN_COMMA = YES; 328 | CLANG_WARN_CONSTANT_CONVERSION = YES; 329 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 330 | CLANG_WARN_EMPTY_BODY = YES; 331 | CLANG_WARN_ENUM_CONVERSION = YES; 332 | CLANG_WARN_INFINITE_RECURSION = YES; 333 | CLANG_WARN_INT_CONVERSION = YES; 334 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 335 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 336 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 337 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 338 | CLANG_WARN_STRICT_PROTOTYPES = YES; 339 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 340 | CLANG_WARN_UNREACHABLE_CODE = YES; 341 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 342 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 343 | COPY_PHASE_STRIP = NO; 344 | DEBUG_INFORMATION_FORMAT = dwarf; 345 | ENABLE_STRICT_OBJC_MSGSEND = YES; 346 | ENABLE_TESTABILITY = YES; 347 | GCC_C_LANGUAGE_STANDARD = gnu99; 348 | GCC_DYNAMIC_NO_PIC = NO; 349 | GCC_NO_COMMON_BLOCKS = YES; 350 | GCC_OPTIMIZATION_LEVEL = 0; 351 | GCC_PREPROCESSOR_DEFINITIONS = ( 352 | "DEBUG=1", 353 | "$(inherited)", 354 | ); 355 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 356 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 357 | GCC_WARN_UNDECLARED_SELECTOR = YES; 358 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 359 | GCC_WARN_UNUSED_FUNCTION = YES; 360 | GCC_WARN_UNUSED_VARIABLE = YES; 361 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 362 | MTL_ENABLE_DEBUG_INFO = YES; 363 | ONLY_ACTIVE_ARCH = YES; 364 | SDKROOT = iphoneos; 365 | TARGETED_DEVICE_FAMILY = "1,2"; 366 | }; 367 | name = Debug; 368 | }; 369 | 97C147041CF9000F007C117D /* Release */ = { 370 | isa = XCBuildConfiguration; 371 | buildSettings = { 372 | ALWAYS_SEARCH_USER_PATHS = NO; 373 | CLANG_ANALYZER_NONNULL = YES; 374 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 375 | CLANG_CXX_LIBRARY = "libc++"; 376 | CLANG_ENABLE_MODULES = YES; 377 | CLANG_ENABLE_OBJC_ARC = YES; 378 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 379 | CLANG_WARN_BOOL_CONVERSION = YES; 380 | CLANG_WARN_COMMA = YES; 381 | CLANG_WARN_CONSTANT_CONVERSION = YES; 382 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 383 | CLANG_WARN_EMPTY_BODY = YES; 384 | CLANG_WARN_ENUM_CONVERSION = YES; 385 | CLANG_WARN_INFINITE_RECURSION = YES; 386 | CLANG_WARN_INT_CONVERSION = YES; 387 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 388 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 389 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 390 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 391 | CLANG_WARN_STRICT_PROTOTYPES = YES; 392 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 393 | CLANG_WARN_UNREACHABLE_CODE = YES; 394 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 395 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 396 | COPY_PHASE_STRIP = NO; 397 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 398 | ENABLE_NS_ASSERTIONS = NO; 399 | ENABLE_STRICT_OBJC_MSGSEND = YES; 400 | GCC_C_LANGUAGE_STANDARD = gnu99; 401 | GCC_NO_COMMON_BLOCKS = YES; 402 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 403 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 404 | GCC_WARN_UNDECLARED_SELECTOR = YES; 405 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 406 | GCC_WARN_UNUSED_FUNCTION = YES; 407 | GCC_WARN_UNUSED_VARIABLE = YES; 408 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 409 | MTL_ENABLE_DEBUG_INFO = NO; 410 | SDKROOT = iphoneos; 411 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 412 | TARGETED_DEVICE_FAMILY = "1,2"; 413 | VALIDATE_PRODUCT = YES; 414 | }; 415 | name = Release; 416 | }; 417 | 97C147061CF9000F007C117D /* Debug */ = { 418 | isa = XCBuildConfiguration; 419 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 420 | buildSettings = { 421 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 422 | CLANG_ENABLE_MODULES = YES; 423 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 424 | ENABLE_BITCODE = NO; 425 | FRAMEWORK_SEARCH_PATHS = ( 426 | "$(inherited)", 427 | "$(PROJECT_DIR)/Flutter", 428 | ); 429 | INFOPLIST_FILE = Runner/Info.plist; 430 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 431 | LIBRARY_SEARCH_PATHS = ( 432 | "$(inherited)", 433 | "$(PROJECT_DIR)/Flutter", 434 | ); 435 | PRODUCT_BUNDLE_IDENTIFIER = com.noeatsleepdev.geoflutterfireExample; 436 | PRODUCT_NAME = "$(TARGET_NAME)"; 437 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 438 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 439 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 440 | SWIFT_VERSION = 4.0; 441 | VERSIONING_SYSTEM = "apple-generic"; 442 | }; 443 | name = Debug; 444 | }; 445 | 97C147071CF9000F007C117D /* Release */ = { 446 | isa = XCBuildConfiguration; 447 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 448 | buildSettings = { 449 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 450 | CLANG_ENABLE_MODULES = YES; 451 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 452 | ENABLE_BITCODE = NO; 453 | FRAMEWORK_SEARCH_PATHS = ( 454 | "$(inherited)", 455 | "$(PROJECT_DIR)/Flutter", 456 | ); 457 | INFOPLIST_FILE = Runner/Info.plist; 458 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 459 | LIBRARY_SEARCH_PATHS = ( 460 | "$(inherited)", 461 | "$(PROJECT_DIR)/Flutter", 462 | ); 463 | PRODUCT_BUNDLE_IDENTIFIER = com.noeatsleepdev.geoflutterfireExample; 464 | PRODUCT_NAME = "$(TARGET_NAME)"; 465 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 466 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 467 | SWIFT_VERSION = 4.0; 468 | VERSIONING_SYSTEM = "apple-generic"; 469 | }; 470 | name = Release; 471 | }; 472 | /* End XCBuildConfiguration section */ 473 | 474 | /* Begin XCConfigurationList section */ 475 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 476 | isa = XCConfigurationList; 477 | buildConfigurations = ( 478 | 97C147031CF9000F007C117D /* Debug */, 479 | 97C147041CF9000F007C117D /* Release */, 480 | 249021D3217E4FDB00AE95B9 /* Profile */, 481 | ); 482 | defaultConfigurationIsVisible = 0; 483 | defaultConfigurationName = Release; 484 | }; 485 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 486 | isa = XCConfigurationList; 487 | buildConfigurations = ( 488 | 97C147061CF9000F007C117D /* Debug */, 489 | 97C147071CF9000F007C117D /* Release */, 490 | 249021D4217E4FDB00AE95B9 /* Profile */, 491 | ); 492 | defaultConfigurationIsVisible = 0; 493 | defaultConfigurationName = Release; 494 | }; 495 | /* End XCConfigurationList section */ 496 | 497 | }; 498 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 499 | } 500 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmany/GeoFlutterFire/656e1e4af2fa5aabf8712b8109df64cc01460cf1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmany/GeoFlutterFire/656e1e4af2fa5aabf8712b8109df64cc01460cf1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmany/GeoFlutterFire/656e1e4af2fa5aabf8712b8109df64cc01460cf1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmany/GeoFlutterFire/656e1e4af2fa5aabf8712b8109df64cc01460cf1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmany/GeoFlutterFire/656e1e4af2fa5aabf8712b8109df64cc01460cf1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmany/GeoFlutterFire/656e1e4af2fa5aabf8712b8109df64cc01460cf1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmany/GeoFlutterFire/656e1e4af2fa5aabf8712b8109df64cc01460cf1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmany/GeoFlutterFire/656e1e4af2fa5aabf8712b8109df64cc01460cf1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmany/GeoFlutterFire/656e1e4af2fa5aabf8712b8109df64cc01460cf1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmany/GeoFlutterFire/656e1e4af2fa5aabf8712b8109df64cc01460cf1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmany/GeoFlutterFire/656e1e4af2fa5aabf8712b8109df64cc01460cf1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmany/GeoFlutterFire/656e1e4af2fa5aabf8712b8109df64cc01460cf1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmany/GeoFlutterFire/656e1e4af2fa5aabf8712b8109df64cc01460cf1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmany/GeoFlutterFire/656e1e4af2fa5aabf8712b8109df64cc01460cf1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmany/GeoFlutterFire/656e1e4af2fa5aabf8712b8109df64cc01460cf1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmany/GeoFlutterFire/656e1e4af2fa5aabf8712b8109df64cc01460cf1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmany/GeoFlutterFire/656e1e4af2fa5aabf8712b8109df64cc01460cf1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmany/GeoFlutterFire/656e1e4af2fa5aabf8712b8109df64cc01460cf1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | geoflutterfire_example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:geoflutterfire/geoflutterfire.dart'; 3 | import 'package:cloud_firestore/cloud_firestore.dart'; 4 | import 'streambuilder_test.dart'; 5 | import 'package:google_maps_flutter/google_maps_flutter.dart'; 6 | import 'package:rxdart/rxdart.dart'; 7 | import 'package:firebase_core/firebase_core.dart'; 8 | 9 | void main() async { 10 | WidgetsFlutterBinding.ensureInitialized(); 11 | await Firebase.initializeApp(); 12 | runApp(MaterialApp( 13 | title: 'Geo Flutter Fire example', 14 | home: MyApp(), 15 | debugShowCheckedModeBanner: false, 16 | )); 17 | } 18 | 19 | class MyApp extends StatefulWidget { 20 | @override 21 | _MyAppState createState() => _MyAppState(); 22 | } 23 | 24 | class _MyAppState extends State { 25 | GoogleMapController _mapController; 26 | TextEditingController _latitudeController, _longitudeController; 27 | 28 | // firestore init 29 | final _firestore = FirebaseFirestore.instance; 30 | Geoflutterfire geo; 31 | Stream> stream; 32 | final radius = BehaviorSubject.seeded(1.0); 33 | Map markers = {}; 34 | 35 | @override 36 | void initState() { 37 | super.initState(); 38 | _latitudeController = TextEditingController(); 39 | _longitudeController = TextEditingController(); 40 | 41 | geo = Geoflutterfire(); 42 | GeoFirePoint center = geo.point(latitude: 12.960632, longitude: 77.641603); 43 | stream = radius.switchMap((rad) { 44 | var collectionReference = _firestore.collection('locations'); 45 | // .where('name', isEqualTo: 'darshan'); 46 | return geo.collection(collectionRef: collectionReference).within( 47 | center: center, radius: rad, field: 'position', strictMode: true); 48 | 49 | /* 50 | ****Example to specify nested object**** 51 | 52 | var collectionReference = _firestore.collection('nestedLocations'); 53 | // .where('name', isEqualTo: 'darshan'); 54 | return geo.collection(collectionRef: collectionReference).within( 55 | center: center, radius: rad, field: 'address.location.position'); 56 | 57 | */ 58 | }); 59 | } 60 | 61 | @override 62 | void dispose() { 63 | _latitudeController.dispose(); 64 | _longitudeController.dispose(); 65 | radius.close(); 66 | super.dispose(); 67 | } 68 | 69 | @override 70 | Widget build(BuildContext context) { 71 | final mediaQuery = MediaQuery.of(context); 72 | return MaterialApp( 73 | home: Scaffold( 74 | appBar: AppBar( 75 | title: const Text('GeoFlutterFire'), 76 | actions: [ 77 | IconButton( 78 | onPressed: _mapController == null 79 | ? null 80 | : () { 81 | _showHome(); 82 | }, 83 | icon: Icon(Icons.home), 84 | ) 85 | ], 86 | ), 87 | floatingActionButton: FloatingActionButton( 88 | onPressed: () { 89 | Navigator.of(context).push(MaterialPageRoute(builder: (context) { 90 | return StreamTestWidget(); 91 | })); 92 | }, 93 | child: Icon(Icons.navigate_next), 94 | ), 95 | body: Container( 96 | child: Column( 97 | crossAxisAlignment: CrossAxisAlignment.center, 98 | children: [ 99 | Center( 100 | child: Card( 101 | elevation: 4, 102 | margin: EdgeInsets.symmetric(vertical: 8), 103 | child: SizedBox( 104 | width: mediaQuery.size.width - 30, 105 | height: mediaQuery.size.height * (1 / 3), 106 | child: GoogleMap( 107 | onMapCreated: _onMapCreated, 108 | initialCameraPosition: const CameraPosition( 109 | target: LatLng(12.960632, 77.641603), 110 | zoom: 15.0, 111 | ), 112 | markers: Set.of(markers.values), 113 | ), 114 | ), 115 | ), 116 | ), 117 | Padding( 118 | padding: const EdgeInsets.only(top: 8.0), 119 | child: Slider( 120 | min: 1, 121 | max: 200, 122 | divisions: 4, 123 | value: _value, 124 | label: _label, 125 | activeColor: Colors.blue, 126 | inactiveColor: Colors.blue.withOpacity(0.2), 127 | onChanged: (double value) => changed(value), 128 | ), 129 | ), 130 | Row( 131 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 132 | children: [ 133 | Container( 134 | width: 100, 135 | child: TextField( 136 | controller: _latitudeController, 137 | keyboardType: TextInputType.number, 138 | textInputAction: TextInputAction.next, 139 | decoration: InputDecoration( 140 | labelText: 'lat', 141 | border: OutlineInputBorder( 142 | borderRadius: BorderRadius.circular(8), 143 | )), 144 | ), 145 | ), 146 | Container( 147 | width: 100, 148 | child: TextField( 149 | controller: _longitudeController, 150 | keyboardType: TextInputType.number, 151 | decoration: InputDecoration( 152 | labelText: 'lng', 153 | border: OutlineInputBorder( 154 | borderRadius: BorderRadius.circular(8), 155 | )), 156 | ), 157 | ), 158 | MaterialButton( 159 | color: Colors.blue, 160 | onPressed: () { 161 | final lat = double.parse(_latitudeController.text); 162 | final lng = double.parse(_longitudeController.text); 163 | _addPoint(lat, lng); 164 | }, 165 | child: const Text( 166 | 'ADD', 167 | style: TextStyle(color: Colors.white), 168 | ), 169 | ) 170 | ], 171 | ), 172 | MaterialButton( 173 | color: Colors.amber, 174 | child: const Text( 175 | 'Add nested ', 176 | style: TextStyle(color: Colors.white), 177 | ), 178 | onPressed: () { 179 | final lat = double.parse(_latitudeController.text); 180 | final lng = double.parse(_longitudeController.text); 181 | _addNestedPoint(lat, lng); 182 | }, 183 | ) 184 | ], 185 | ), 186 | ), 187 | ), 188 | ); 189 | } 190 | 191 | void _onMapCreated(GoogleMapController controller) { 192 | setState(() { 193 | _mapController = controller; 194 | // _showHome(); 195 | //start listening after map is created 196 | stream.listen((List documentList) { 197 | _updateMarkers(documentList); 198 | }); 199 | }); 200 | } 201 | 202 | void _showHome() { 203 | _mapController.animateCamera(CameraUpdate.newCameraPosition( 204 | const CameraPosition( 205 | target: LatLng(12.960632, 77.641603), 206 | zoom: 15.0, 207 | ), 208 | )); 209 | } 210 | 211 | void _addPoint(double lat, double lng) { 212 | GeoFirePoint geoFirePoint = geo.point(latitude: lat, longitude: lng); 213 | _firestore 214 | .collection('locations') 215 | .add({'name': 'random name', 'position': geoFirePoint.data}).then((_) { 216 | print('added ${geoFirePoint.hash} successfully'); 217 | }); 218 | } 219 | 220 | //example to add geoFirePoint inside nested object 221 | void _addNestedPoint(double lat, double lng) { 222 | GeoFirePoint geoFirePoint = geo.point(latitude: lat, longitude: lng); 223 | _firestore.collection('nestedLocations').add({ 224 | 'name': 'random name', 225 | 'address': { 226 | 'location': {'position': geoFirePoint.data} 227 | } 228 | }).then((_) { 229 | print('added ${geoFirePoint.hash} successfully'); 230 | }); 231 | } 232 | 233 | void _addMarker(double lat, double lng) { 234 | final id = MarkerId(lat.toString() + lng.toString()); 235 | final _marker = Marker( 236 | markerId: id, 237 | position: LatLng(lat, lng), 238 | icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueViolet), 239 | infoWindow: InfoWindow(title: 'latLng', snippet: '$lat,$lng'), 240 | ); 241 | setState(() { 242 | markers[id] = _marker; 243 | }); 244 | } 245 | 246 | void _updateMarkers(List documentList) { 247 | documentList.forEach((DocumentSnapshot document) { 248 | final GeoPoint point = document.data()['position']['geopoint']; 249 | _addMarker(point.latitude, point.longitude); 250 | }); 251 | } 252 | 253 | double _value = 20.0; 254 | String _label = ''; 255 | 256 | changed(value) { 257 | setState(() { 258 | _value = value; 259 | _label = '${_value.toInt().toString()} kms'; 260 | markers.clear(); 261 | }); 262 | radius.add(value); 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /example/lib/streambuilder_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:geoflutterfire/geoflutterfire.dart'; 4 | import 'package:rxdart/rxdart.dart'; 5 | 6 | class StreamTestWidget extends StatefulWidget { 7 | @override 8 | _StreamTestWidgetState createState() => _StreamTestWidgetState(); 9 | } 10 | 11 | class _StreamTestWidgetState extends State { 12 | Stream> stream; 13 | final _firestore = FirebaseFirestore.instance; 14 | final geo = Geoflutterfire(); 15 | 16 | // ignore: close_sinks 17 | var radius = BehaviorSubject.seeded(1.0); 18 | 19 | @override 20 | void initState() { 21 | super.initState(); 22 | final center = geo.point(latitude: 12.960632, longitude: 77.641603); 23 | stream = radius.switchMap((rad) { 24 | var collectionReference = _firestore.collection('locations'); 25 | return geo.collection(collectionRef: collectionReference).within( 26 | center: center, radius: rad, field: 'position', strictMode: true); 27 | }); 28 | } 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | return Scaffold( 33 | appBar: AppBar(), 34 | body: Column( 35 | children: [ 36 | StreamBuilder( 37 | stream: stream, 38 | builder: (BuildContext context, 39 | AsyncSnapshot> snapshots) { 40 | if (snapshots.connectionState == ConnectionState.active && 41 | snapshots.hasData) { 42 | print('data ${snapshots.data}'); 43 | return Container( 44 | height: MediaQuery.of(context).size.height * 2 / 3, 45 | child: ListView.builder( 46 | itemBuilder: (context, index) { 47 | final doc = snapshots.data[index]; 48 | final data = doc.data(); 49 | print( 50 | 'doc with id ${doc.id} distance ${data['distance']}'); 51 | GeoPoint point = data['position']['geopoint']; 52 | return ListTile( 53 | title: Text( 54 | doc.id, 55 | style: const TextStyle(fontWeight: FontWeight.bold), 56 | ), 57 | subtitle: Text('${point.latitude}, ${point.longitude}'), 58 | trailing: Text( 59 | '${data['documentType'] == DocumentChangeType.added ? 'Added' : 'Modified'}'), 60 | ); 61 | }, 62 | itemCount: snapshots.data.length, 63 | ), 64 | ); 65 | } else { 66 | return const Center(child: CircularProgressIndicator()); 67 | } 68 | }, 69 | ), 70 | Padding( 71 | padding: const EdgeInsets.only(top: 8.0), 72 | child: Slider( 73 | min: 1, 74 | max: 200, 75 | divisions: 4, 76 | value: _value, 77 | label: _label, 78 | activeColor: Colors.blue, 79 | inactiveColor: Colors.blue.withOpacity(0.2), 80 | onChanged: (double value) => changed(value), 81 | ), 82 | ), 83 | ], 84 | ), 85 | ); 86 | } 87 | 88 | double _value = 20.0; 89 | String _label = ''; 90 | 91 | changed(value) { 92 | setState(() { 93 | _value = value; 94 | print(_value); 95 | _label = '${_value.toInt().toString()} kms'; 96 | }); 97 | radius.add(value); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: geoflutterfire_example 2 | description: Demonstrates how to use the geoflutterfire plugin. 3 | publish_to: "none" 4 | 5 | environment: 6 | sdk: ">=2.1.0 <3.0.0" 7 | 8 | dependencies: 9 | flutter: 10 | sdk: flutter 11 | 12 | # The following adds the Cupertino Icons font to your application. 13 | # Use with the CupertinoIcons class for iOS style icons. 14 | cupertino_icons: ^0.1.2 15 | 16 | dev_dependencies: 17 | flutter_test: 18 | sdk: flutter 19 | 20 | geoflutterfire: 21 | path: ../ 22 | 23 | cloud_firestore: ^0.16.0 24 | google_maps_flutter: ^1.2.0 25 | 26 | # For information on the generic Dart part of this file, see the 27 | # following page: https://www.dartlang.org/tools/pub/pubspec 28 | 29 | # The following section is specific to Flutter. 30 | flutter: 31 | # The following line ensures that the Material Icons font is 32 | # included with your application, so that you can use the icons in 33 | # the material Icons class. 34 | uses-material-design: true 35 | # To add assets to your application, add an assets section, like this: 36 | # assets: 37 | # - images/a_dot_burr.jpeg 38 | # - images/a_dot_ham.jpeg 39 | # An image asset can refer to one or more resolution-specific "variants", see 40 | # https://flutter.io/assets-and-images/#resolution-aware. 41 | # For details regarding adding assets from package dependencies, see 42 | # https://flutter.io/assets-and-images/#from-packages 43 | # To add custom fonts to your application, add a fonts section here, 44 | # in this "flutter" section. Each entry in this list should have a 45 | # "family" key with the font family name, and a "fonts" key with a 46 | # list giving the asset and other descriptors for the font. For 47 | # example: 48 | # fonts: 49 | # - family: Schyler 50 | # fonts: 51 | # - asset: fonts/Schyler-Regular.ttf 52 | # - asset: fonts/Schyler-Italic.ttf 53 | # style: italic 54 | # - family: Trajan Pro 55 | # fonts: 56 | # - asset: fonts/TrajanPro.ttf 57 | # - asset: fonts/TrajanPro_Bold.ttf 58 | # weight: 700 59 | # 60 | # For details regarding fonts from package dependencies, 61 | # see https://flutter.io/custom-fonts/#from-packages 62 | -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:geoflutterfire_example/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Verify Platform version', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that platform version is retrieved. 19 | expect( 20 | find.byWidgetPredicate( 21 | (Widget widget) => 22 | widget is Text && widget.data.startsWith('Running on:'), 23 | ), 24 | findsOneWidget, 25 | ); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /geoflutterfire.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /lib/geoflutterfire.dart: -------------------------------------------------------------------------------- 1 | export 'src/geoflutterfire.dart'; 2 | export 'src/collection.dart'; 3 | export 'src/point.dart'; 4 | -------------------------------------------------------------------------------- /lib/src/collection.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:flutter/foundation.dart'; 3 | import 'package:geoflutterfire/src/models/DistanceDocSnapshot.dart'; 4 | import 'package:geoflutterfire/src/point.dart'; 5 | import 'util.dart'; 6 | import 'package:rxdart/rxdart.dart'; 7 | import 'dart:async'; 8 | 9 | class GeoFireCollectionRef { 10 | Query _collectionReference; 11 | Stream _stream; 12 | 13 | GeoFireCollectionRef(this._collectionReference) 14 | : assert(_collectionReference != null) { 15 | _stream = _createStream(_collectionReference).shareReplay(maxSize: 1); 16 | } 17 | 18 | /// return QuerySnapshot stream 19 | Stream snapshot() { 20 | return _stream; 21 | } 22 | 23 | /// return the Document mapped to the [id] 24 | Stream> data(String id) { 25 | return _stream.map((QuerySnapshot querySnapshot) { 26 | querySnapshot.docs.where((DocumentSnapshot documentSnapshot) { 27 | return documentSnapshot.id == id; 28 | }); 29 | return querySnapshot.docs; 30 | }); 31 | } 32 | 33 | /// add a document to collection with [data] 34 | Future add(Map data) { 35 | try { 36 | CollectionReference colRef = _collectionReference; 37 | return colRef.add(data); 38 | } catch (e) { 39 | throw Exception( 40 | 'cannot call add on Query, use collection reference instead'); 41 | } 42 | } 43 | 44 | /// delete document with [id] from the collection 45 | Future delete(id) { 46 | try { 47 | CollectionReference colRef = _collectionReference; 48 | return colRef.doc(id).delete(); 49 | } catch (e) { 50 | throw Exception( 51 | 'cannot call delete on Query, use collection reference instead'); 52 | } 53 | } 54 | 55 | /// create or update a document with [id], [merge] defines whether the document should overwrite 56 | Future setDoc(String id, var data, {bool merge = false}) { 57 | try { 58 | CollectionReference colRef = _collectionReference; 59 | return colRef.doc(id).set(data, SetOptions(merge: merge)); 60 | } catch (e) { 61 | throw Exception( 62 | 'cannot call set on Query, use collection reference instead'); 63 | } 64 | } 65 | 66 | /// set a geo point with [latitude] and [longitude] using [field] as the object key to the document with [id] 67 | Future setPoint( 68 | String id, String field, double latitude, double longitude) { 69 | try { 70 | CollectionReference colRef = _collectionReference; 71 | var point = GeoFirePoint(latitude, longitude).data; 72 | return colRef.doc(id).set({'$field': point}, SetOptions(merge: true)); 73 | } catch (e) { 74 | throw Exception( 75 | 'cannot call set on Query, use collection reference instead'); 76 | } 77 | } 78 | 79 | /// query firestore documents based on geographic [radius] from geoFirePoint [center] 80 | /// [field] specifies the name of the key in the document 81 | Stream> within({ 82 | @required GeoFirePoint center, 83 | @required double radius, 84 | @required String field, 85 | bool strictMode = false, 86 | }) { 87 | final precision = Util.setPrecision(radius); 88 | final centerHash = center.hash.substring(0, precision); 89 | final area = GeoFirePoint.neighborsOf(hash: centerHash)..add(centerHash); 90 | 91 | Iterable>> queries = area.map((hash) { 92 | final tempQuery = _queryPoint(hash, field); 93 | return _createStream(tempQuery).map((QuerySnapshot querySnapshot) { 94 | return querySnapshot.docs 95 | .map((element) => DistanceDocSnapshot(element, null)) 96 | .toList(); 97 | }); 98 | }); 99 | 100 | Stream> mergedObservable = 101 | mergeObservable(queries); 102 | 103 | var filtered = mergedObservable.map((List list) { 104 | var mappedList = list.map((DistanceDocSnapshot distanceDocSnapshot) { 105 | // split and fetch geoPoint from the nested Map 106 | final fieldList = field.split('.'); 107 | var geoPointField = 108 | distanceDocSnapshot.documentSnapshot.data()[fieldList[0]]; 109 | if (fieldList.length > 1) { 110 | for (int i = 1; i < fieldList.length; i++) { 111 | geoPointField = geoPointField[fieldList[i]]; 112 | } 113 | } 114 | final GeoPoint geoPoint = geoPointField['geopoint']; 115 | distanceDocSnapshot.distance = 116 | center.distance(lat: geoPoint.latitude, lng: geoPoint.longitude); 117 | return distanceDocSnapshot; 118 | }); 119 | 120 | final filteredList = strictMode 121 | ? mappedList 122 | .where((DistanceDocSnapshot doc) => 123 | doc.distance <= 124 | radius * 1.02 // buffer for edge distances; 125 | ) 126 | .toList() 127 | : mappedList.toList(); 128 | filteredList.sort((a, b) { 129 | final distA = a.distance; 130 | final distB = b.distance; 131 | final val = (distA * 1000).toInt() - (distB * 1000).toInt(); 132 | return val; 133 | }); 134 | return filteredList.map((element) => element.documentSnapshot).toList(); 135 | }); 136 | return filtered.asBroadcastStream(); 137 | } 138 | 139 | Stream> mergeObservable( 140 | Iterable>> queries) { 141 | Stream> mergedObservable = Rx.combineLatest( 142 | queries, (List> originalList) { 143 | final reducedList = []; 144 | originalList.forEach((t) { 145 | reducedList.addAll(t); 146 | }); 147 | return reducedList; 148 | }); 149 | return mergedObservable; 150 | } 151 | 152 | /// INTERNAL FUNCTIONS 153 | 154 | /// construct a query for the [geoHash] and [field] 155 | Query _queryPoint(String geoHash, String field) { 156 | final end = '$geoHash~'; 157 | final temp = _collectionReference; 158 | return temp.orderBy('$field.geohash').startAt([geoHash]).endAt([end]); 159 | } 160 | 161 | /// create an observable for [ref], [ref] can be [Query] or [CollectionReference] 162 | Stream _createStream(var ref) { 163 | return ref.snapshots(); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /lib/src/geoflutterfire.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:flutter/foundation.dart'; 3 | import 'package:geoflutterfire/src/point.dart'; 4 | import 'package:geoflutterfire/src/collection.dart'; 5 | 6 | class Geoflutterfire { 7 | Geoflutterfire(); 8 | 9 | GeoFireCollectionRef collection({@required Query collectionRef}) { 10 | return GeoFireCollectionRef(collectionRef); 11 | } 12 | 13 | GeoFirePoint point({@required double latitude, @required double longitude}) { 14 | return GeoFirePoint(latitude, longitude); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/src/models/DistanceDocSnapshot.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | 3 | class DistanceDocSnapshot { 4 | final DocumentSnapshot documentSnapshot; 5 | double distance; 6 | 7 | DistanceDocSnapshot(this.documentSnapshot, this.distance); 8 | } 9 | -------------------------------------------------------------------------------- /lib/src/point.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | 3 | import 'util.dart'; 4 | import 'package:cloud_firestore/cloud_firestore.dart'; 5 | 6 | class GeoFirePoint { 7 | static Util _util = Util(); 8 | double latitude, longitude; 9 | 10 | GeoFirePoint(this.latitude, this.longitude); 11 | 12 | /// return geographical distance between two Co-ordinates 13 | static double distanceBetween( 14 | {@required Coordinates to, @required Coordinates from}) { 15 | return Util.distance(to, from); 16 | } 17 | 18 | /// return neighboring geo-hashes of [hash] 19 | static List neighborsOf({@required String hash}) { 20 | return _util.neighbors(hash); 21 | } 22 | 23 | /// return hash of [GeoFirePoint] 24 | String get hash { 25 | return _util.encode(this.latitude, this.longitude, 9); 26 | } 27 | 28 | /// return all neighbors of [GeoFirePoint] 29 | List get neighbors { 30 | return _util.neighbors(this.hash); 31 | } 32 | 33 | /// return [GeoPoint] of [GeoFirePoint] 34 | GeoPoint get geoPoint { 35 | return GeoPoint(this.latitude, this.longitude); 36 | } 37 | 38 | Coordinates get coords { 39 | return Coordinates(this.latitude, this.longitude); 40 | } 41 | 42 | /// return distance between [GeoFirePoint] and ([lat], [lng]) 43 | double distance({@required double lat, @required double lng}) { 44 | return distanceBetween(from: coords, to: Coordinates(lat, lng)); 45 | } 46 | 47 | get data { 48 | return {'geopoint': this.geoPoint, 'geohash': this.hash}; 49 | } 50 | 51 | /// haversine distance between [GeoFirePoint] and ([lat], [lng]) 52 | haversineDistance({@required double lat, @required double lng}) { 53 | return GeoFirePoint.distanceBetween( 54 | from: coords, to: Coordinates(lat, lng)); 55 | } 56 | } 57 | 58 | class Coordinates { 59 | double latitude; 60 | double longitude; 61 | 62 | Coordinates(this.latitude, this.longitude); 63 | } 64 | -------------------------------------------------------------------------------- /lib/src/util.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | import 'package:geoflutterfire/src/point.dart'; 3 | 4 | class Util { 5 | static const BASE32_CODES = '0123456789bcdefghjkmnpqrstuvwxyz'; 6 | Map base32CodesDic = new Map(); 7 | 8 | Util() { 9 | for (var i = 0; i < BASE32_CODES.length; i++) { 10 | base32CodesDic.putIfAbsent(BASE32_CODES[i], () => i); 11 | } 12 | } 13 | 14 | var encodeAuto = 'auto'; 15 | 16 | /// 17 | /// Significant Figure Hash Length 18 | /// 19 | /// This is a quick and dirty lookup to figure out how long our hash 20 | /// should be in order to guarantee a certain amount of trailing 21 | /// significant figures. This was calculated by determining the error: 22 | /// 45/2^(n-1) where n is the number of bits for a latitude or 23 | /// longitude. Key is # of desired sig figs, value is minimum length of 24 | /// the geohash. 25 | /// @type Array 26 | // Desired sig figs: 0 1 2 3 4 5 6 7 8 9 10 27 | var sigfigHashLength = [0, 5, 7, 8, 11, 12, 13, 15, 16, 17, 18]; 28 | 29 | /// 30 | /// Encode 31 | /// Create a geohash from latitude and longitude 32 | /// that is 'number of chars' long 33 | String encode(var latitude, var longitude, var numberOfChars) { 34 | if (numberOfChars == encodeAuto) { 35 | if (latitude.runtimeType == double || longitude.runtimeType == double) { 36 | throw new Exception('string notation required for auto precision.'); 37 | } 38 | int decSigFigsLat = latitude.split('.')[1].length; 39 | int decSigFigsLon = longitude.split('.')[1].length; 40 | int numberOfSigFigs = max(decSigFigsLat, decSigFigsLon); 41 | numberOfChars = sigfigHashLength[numberOfSigFigs]; 42 | } else if (numberOfChars == null) { 43 | numberOfChars = 9; 44 | } 45 | 46 | var chars = [], bits = 0, bitsTotal = 0, hashValue = 0; 47 | double maxLat = 90, minLat = -90, maxLon = 180, minLon = -180, mid; 48 | 49 | while (chars.length < numberOfChars) { 50 | if (bitsTotal % 2 == 0) { 51 | mid = (maxLon + minLon) / 2; 52 | if (longitude > mid) { 53 | hashValue = (hashValue << 1) + 1; 54 | minLon = mid; 55 | } else { 56 | hashValue = (hashValue << 1) + 0; 57 | maxLon = mid; 58 | } 59 | } else { 60 | mid = (maxLat + minLat) / 2; 61 | if (latitude > mid) { 62 | hashValue = (hashValue << 1) + 1; 63 | minLat = mid; 64 | } else { 65 | hashValue = (hashValue << 1) + 0; 66 | maxLat = mid; 67 | } 68 | } 69 | 70 | bits++; 71 | bitsTotal++; 72 | if (bits == 5) { 73 | var code = BASE32_CODES[hashValue]; 74 | chars.add(code); 75 | bits = 0; 76 | hashValue = 0; 77 | } 78 | } 79 | 80 | return chars.join(''); 81 | } 82 | 83 | /// 84 | /// Decode Bounding box 85 | /// 86 | /// Decode a hashString into a bound box that matches it. 87 | /// Data returned in a List [minLat, minLon, maxLat, maxLon] 88 | List decodeBbox(String hashString) { 89 | var isLon = true; 90 | double maxLat = 90, minLat = -90, maxLon = 180, minLon = -180, mid; 91 | 92 | var hashValue = 0; 93 | for (var i = 0, l = hashString.length; i < l; i++) { 94 | var code = hashString[i].toLowerCase(); 95 | hashValue = base32CodesDic[code]; 96 | 97 | for (var bits = 4; bits >= 0; bits--) { 98 | var bit = (hashValue >> bits) & 1; 99 | if (isLon) { 100 | mid = (maxLon + minLon) / 2; 101 | if (bit == 1) { 102 | minLon = mid; 103 | } else { 104 | maxLon = mid; 105 | } 106 | } else { 107 | mid = (maxLat + minLat) / 2; 108 | if (bit == 1) { 109 | minLat = mid; 110 | } else { 111 | maxLat = mid; 112 | } 113 | } 114 | isLon = !isLon; 115 | } 116 | } 117 | return [minLat, minLon, maxLat, maxLon]; 118 | } 119 | 120 | /// 121 | /// Decode a [hashString] into a pair of latitude and longitude. 122 | /// A map is returned with keys 'latitude', 'longitude','latitudeError','longitudeError' 123 | Map decode(String hashString) { 124 | List bbox = decodeBbox(hashString); 125 | double lat = (bbox[0] + bbox[2]) / 2; 126 | double lon = (bbox[1] + bbox[3]) / 2; 127 | double latErr = bbox[2] - lat; 128 | double lonErr = bbox[3] - lon; 129 | return { 130 | 'latitude': lat, 131 | 'longitude': lon, 132 | 'latitudeError': latErr, 133 | 'longitudeError': lonErr, 134 | }; 135 | } 136 | 137 | /// 138 | /// Neighbor 139 | /// 140 | /// Find neighbor of a geohash string in certain direction. 141 | /// Direction is a two-element array, i.e. [1,0] means north, [-1,-1] means southwest. 142 | /// 143 | /// direction [lat, lon], i.e. 144 | /// [1,0] - north 145 | /// [1,1] - northeast 146 | String neighbor(String hashString, var direction) { 147 | var lonLat = decode(hashString); 148 | var neighborLat = 149 | lonLat['latitude'] + direction[0] * lonLat['latitudeError'] * 2; 150 | var neighborLon = 151 | lonLat['longitude'] + direction[1] * lonLat['longitudeError'] * 2; 152 | return encode(neighborLat, neighborLon, hashString.length); 153 | } 154 | 155 | /// 156 | /// Neighbors 157 | /// Returns all neighbors' hashstrings clockwise from north around to northwest 158 | /// 7 0 1 159 | /// 6 X 2 160 | /// 5 4 3 161 | List neighbors(String hashString) { 162 | int hashStringLength = hashString.length; 163 | var lonlat = decode(hashString); 164 | double lat = lonlat['latitude']; 165 | double lon = lonlat['longitude']; 166 | double latErr = lonlat['latitudeError'] * 2; 167 | double lonErr = lonlat['longitudeError'] * 2; 168 | 169 | var neighborLat, neighborLon; 170 | 171 | String encodeNeighbor(neighborLatDir, neighborLonDir) { 172 | neighborLat = lat + neighborLatDir * latErr; 173 | neighborLon = lon + neighborLonDir * lonErr; 174 | return encode(neighborLat, neighborLon, hashStringLength); 175 | } 176 | 177 | var neighborHashList = [ 178 | encodeNeighbor(1, 0), 179 | encodeNeighbor(1, 1), 180 | encodeNeighbor(0, 1), 181 | encodeNeighbor(-1, 1), 182 | encodeNeighbor(-1, 0), 183 | encodeNeighbor(-1, -1), 184 | encodeNeighbor(0, -1), 185 | encodeNeighbor(1, -1) 186 | ]; 187 | 188 | return neighborHashList; 189 | } 190 | 191 | static int setPrecision(double km) { 192 | /* 193 | * 1 ≤ 5,000km × 5,000km 194 | * 2 ≤ 1,250km × 625km 195 | * 3 ≤ 156km × 156km 196 | * 4 ≤ 39.1km × 19.5km 197 | * 5 ≤ 4.89km × 4.89km 198 | * 6 ≤ 1.22km × 0.61km 199 | * 7 ≤ 153m × 153m 200 | * 8 ≤ 38.2m × 19.1m 201 | * 9 ≤ 4.77m × 4.77m 202 | * 203 | */ 204 | 205 | if (km <= 0.00477) 206 | return 9; 207 | else if (km <= 0.0382) 208 | return 8; 209 | else if (km <= 0.153) 210 | return 7; 211 | else if (km <= 1.22) 212 | return 6; 213 | else if (km <= 4.89) 214 | return 5; 215 | else if (km <= 39.1) 216 | return 4; 217 | else if (km <= 156) 218 | return 3; 219 | else if (km <= 1250) 220 | return 2; 221 | else 222 | return 1; 223 | } 224 | 225 | static const double MAX_SUPPORTED_RADIUS = 8587; 226 | 227 | // Length of a degree latitude at the equator 228 | static const double METERS_PER_DEGREE_LATITUDE = 110574; 229 | 230 | // The equatorial circumference of the earth in meters 231 | static const double EARTH_MERIDIONAL_CIRCUMFERENCE = 40007860; 232 | 233 | // The equatorial radius of the earth in meters 234 | static const double EARTH_EQ_RADIUS = 6378137; 235 | 236 | // The meridional radius of the earth in meters 237 | static const double EARTH_POLAR_RADIUS = 6357852.3; 238 | 239 | /* The following value assumes a polar radius of 240 | * r_p = 6356752.3 241 | * and an equatorial radius of 242 | * r_e = 6378137 243 | * The value is calculated as e2 == (r_e^2 - r_p^2)/(r_e^2) 244 | * Use exact value to avoid rounding errors 245 | */ 246 | static const double EARTH_E2 = 0.00669447819799; 247 | 248 | // Cutoff for floating point calculations 249 | static const double EPSILON = 1e-12; 250 | 251 | static double distance(Coordinates location1, Coordinates location2) { 252 | return calcDistance(location1.latitude, location1.longitude, 253 | location2.latitude, location2.longitude); 254 | } 255 | 256 | static double calcDistance( 257 | double lat1, double long1, double lat2, double long2) { 258 | // Earth's mean radius in meters 259 | final double radius = (EARTH_EQ_RADIUS + EARTH_POLAR_RADIUS) / 2; 260 | double latDelta = _toRadians(lat1 - lat2); 261 | double lonDelta = _toRadians(long1 - long2); 262 | 263 | double a = (sin(latDelta / 2) * sin(latDelta / 2)) + 264 | (cos(_toRadians(lat1)) * 265 | cos(_toRadians(lat2)) * 266 | sin(lonDelta / 2) * 267 | sin(lonDelta / 2)); 268 | double distance = radius * 2 * atan2(sqrt(a), sqrt(1 - a)) / 1000; 269 | return double.parse(distance.toStringAsFixed(3)); 270 | } 271 | 272 | static double _toRadians(double num) { 273 | return num * (pi / 180.0); 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: geoflutterfire 2 | description: GeoFlutterFire is an open-source library that allows you to store and query firestore documents based on their geographic location. 3 | version: 2.2.1 4 | homepage: https://github.com/DarshanGowda0/GeoFlutterFire 5 | 6 | environment: 7 | sdk: ">=2.0.0 <3.0.0" 8 | flutter: ">=1.12.0 <2.0.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | cloud_firestore: ^3.1.0 14 | rxdart: ^0.25.0 15 | --------------------------------------------------------------------------------