Flutter real-time location app using firebase and google map

Stephanorazafindramena
5 min readDec 20, 2023

--

In the dynamic landscape of mobile app development, creating applications that provide real-time location tracking has become increasingly essential. Whether it’s for sharing live locations with friends, tracking delivery services, or enhancing user experience in various industries, the demand for real-time location apps continues to grow. In this article, we will explore the exciting journey of developing a robust real-time location app using the power of Flutter, Firebase, and Google Maps.

Prerequisites

In this article, I won’t delve into creating a Firebase project or obtaining a Google API Key. I assume you’re already familiar with these steps 🤝​. If not, I’ve previously covered Firebase in another article, and you can find information on obtaining a Google API Key here. Let’s now move on to the next step ​🤸‍♂️​:

1. Project setup :

Start by creating a new Flutter project:

flutter create location_streamer

Now, let’s integrate the necessary dependencies into our app :

  • google_maps_flutter: for Google Maps integration.
  • geolocator: to obtain the user’s live location.
  • firebase_core & cloud_firestore: for real-time database functionality using Firebase.
  • permission_handler: to manage the permissions required by our app.

Open your pubspec.yaml file and add the following lines:

dependencies:
google_maps_flutter: ^<latest_version>
geolocator: ^<latest_version>
firebase_core: ^<latest_version>
cloud_firestore: ^<latest_version>
permission_handler: ^<latest_version>

1.1 Configuring google map :

After setting up your Flutter project and installing the required dependencies, refer to the documentation to integrate Google Maps. Additionally, include the following permissions in your AndroidManifest.xml file:

    <!-- Allows the application to access the Internet -->
<uses-permission android:name="android.permission.INTERNET" />

<!-- Allows the application to run foreground services -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

<!-- Allows the application to access precise location using GPS -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

<!-- Allows the application to access approximate location using network-based methods -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

<!-- Allows the application to access location in the background -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

1.2 Configuring firestore database :

  1. Navigate to the Firebase Console, select your project, or create a new one if needed.
  2. Create a Firestore instance in test mode within your project to store data.
  3. Download the google-services.json file and place it in your Flutter app directory, specifically under android/app/. Alternatively, use the CLI tool to synchronize Firebase configuration with your Flutter app.

In Firestore, create a collection named “users” and add data with the following format :

class Location {
final double lat;
final double lng;

Location({
required this.lat,
required this.lng,
});

}

class User {
final String name;
final Location location;
User({
required this.name,
required this.location,
});
}

//This format includes a Location class for latitude and longitude and a User
//class containing a name and location. Ensure the data in Firestore
//follows this structure for proper integration with your Flutter app.
Manually data insertion in firestore

Add location value arround this value LatLng(-18.9216855, 47.5725194), so you can see all users in the same camera view. This is just a fake data but in real application, we manage this from the code.

1.3 Implement firestore

Create a Firestore service to manage data in Firestore. Here, we define two methods: one for updating the user’s location and another for retrieving a list of users from the Firestore database as a stream

class FirestoreService {
static final _firestore = FirebaseFirestore.instance;

static Future<void> updateUserLocation(String userId, LatLng location) async {
try {
await _firestore.collection('users').doc(userId).update({
'location': {'lat': location.latitude, 'lng': location.longitude},
});
} on FirebaseException catch (e) {
print('Ann error due to firebase occured $e');
} catch (err) {
print('Ann error occured $err');
}
}

static Stream<List<User>> userCollectionStream() {
return _firestore.collection('users').snapshots().map((snapshot) =>
snapshot.docs.map((doc) => User.fromMap(doc.data())).toList());
}
}

1.4 Implement geolocation

Now, let’s proceed to send the user’s location to the database when it changes. To achieve this, we will leverage the geolocator plugin. However, before accessing the user’s location, it’s crucial to obtain the necessary permissions using the permission_handler

class StreamLocationService {

static const LocationSettings _locationSettings =
LocationSettings(distanceFilter: 1);
static bool _isLocationGranted = false;

static Stream<Position>? get onLocationChanged {
if (_isLocationGranted) {
return Geolocator.getPositionStream(locationSettings: _locationSettings);
}
return null;
}

static Future<bool> askLocationPermission() async {
_isLocationGranted = await Permission.location.request().isGranted;
return _isLocationGranted;
}

}

1.5 Adding Live User Location to Google Map Screen :

  • Setting Up a Location Stream for Real-Time User Tracking with StreamLocationService and FirestoreService :
late StreamSubscription<Position>? locationStreamSubscription;

@override
void initState() {
super.initState();
locationStreamSubscription =
StreamLocationService.onLocationChanged?.listen(
(position) async {
await FirestoreService.updateUserLocation(
'nA7DXYrq1hoKumg3q9fu', //Hardcoded uid but this is the uid of the connected user when using authentification service
LatLng(position.latitude, position.longitude),
);
},
);
}
  • Wrap the GoogleMap widget with a StreamBuilder and update the marker with the stream data :
StreamBuilder<List<User>>(
stream: FirestoreService.userCollectionStream(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator(),
);
}
final Set<Marker> markers = {};
for (var i = 0; i < snapshot.data!.length; i++) {
final user = snapshot.data![i];
markers.add(
Marker(
markerId: MarkerId('${user.name} position $i'),
icon: user.name == 'stephano'
? BitmapDescriptor.defaultMarkerWithHue(
BitmapDescriptor.hueRed,
)
: BitmapDescriptor.defaultMarkerWithHue(
BitmapDescriptor.hueYellow,
),
position: LatLng(user.location.lat, user.location.lng),
onTap: () => {},
),
);
}
return GoogleMap(
initialCameraPosition: _initialPosition,
markers: markers,
onMapCreated: (GoogleMapController controller) {
_controller.complete(controller);
},
);
},
)

Don’t forget to dispose the stream

@override
void dispose() {
super.dispose();
locationStreamSubscription?.cancel();
}

Complete View Implementation:

class MapScreen extends StatefulWidget {
const MapScreen({super.key});

@override
State<MapScreen> createState() => MapScreenState();
}

class MapScreenState extends State<MapScreen> {
final Completer<GoogleMapController> _controller =
Completer<GoogleMapController>();

static const CameraPosition _initialPosition = CameraPosition(
target: LatLng(-18.9216855, 47.5725194),// Antananarivo, Madagascar LatLng 🇲🇬
zoom: 14.4746,
);

late StreamSubscription<Position>? locationStreamSubscription;

@override
void initState() {
super.initState();
locationStreamSubscription =
StreamLocationService.onLocationChanged?.listen(
(position) async {
await FirestoreService.updateUserLocation(
'nA7DXYrq1hoKumg3q9fu', //Hardcoded uid but this is the uid of the connected user when using authentification service
LatLng(position.latitude, position.longitude),
);
},
);
}

@override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder<List<User>>(
stream: FirestoreService.userCollectionStream(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator(),
);
}
final Set<Marker> markers = {};
for (var i = 0; i < snapshot.data!.length; i++) {
final user = snapshot.data![i];
markers.add(
Marker(
markerId: MarkerId('${user.name} position $i'),
icon: user.name == 'stephano'
? BitmapDescriptor.defaultMarkerWithHue(
BitmapDescriptor.hueRed,
)
: BitmapDescriptor.defaultMarkerWithHue(
BitmapDescriptor.hueYellow,
),
position: LatLng(user.location.lat, user.location.lng),
onTap: () => {},
),
);
}
return GoogleMap(
initialCameraPosition: _initialPosition,
markers: markers,
onMapCreated: (GoogleMapController controller) {
_controller.complete(controller);
},
);
},
),
);
}

@override
void dispose() {
super.dispose();
locationStreamSubscription?.cancel();
}
}
Demo App

Conclusion

To sum up, we’ve achieved real-time location tracking in our Flutter app with Google Maps and Firebase. By incorporating StreamBuilder and services like StreamLocationService and FirestoreService, our application now dynamically displays live user locations. This not only enhances the user experience but also opens the door for building more exciting location-based features. With Flutter, Google Maps, and Firebase, we’ve created a foundation for creating engaging and interactive location-aware apps 🌍✨.

--

--