Car Location Tracking Android App With Firebase Tutorial | Part 2

This is the second part of the location tracking tutorial. I hope you guy’s have seen my previous article in which we’ve developed the Driver App.

In this tutorial, we’re going to develop the Passenger App. Here, we are not gonna to do the same work which we’ve done in the previous article like creating a Google Map API key or creating a Firebase project.

So, without wasting a time let’s dive into Android Studio and start making the Passenger App.

Passenger App

First, add the below dependencies to your app level build.gradle file.

implementation 'com.google.android.gms:play-services-location:15.0.1'
implementation 'com.google.android.gms:play-services-maps:15.0.1'
implementation 'com.google.firebase:firebase-database:16.0.1'

After adding the dependencies you need to sync or build your project. Later the successful build adds the Internet and Location permission to Android. Manifest file.

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

There’s one more thing we need to do, to show the Google Maps in the Android App. Add the below meta tags in Android. Manifest file.

<meta-data android:name="com.google.android.gms.version" 
           android:value="@integer/google_play_services_version" />
 <meta-data android:name="com.google.android.geo.API_KEY" 
            android:value="@string/map_api_key" />  // change it your Google Maps Api key or directly paste API key here.

Now in this app, we don’t need to create a new Firebase project. Below are the steps you need to do for add firebase in Passenger App.

  1. Sign In Firebase Console and open the previous project which you create when working with Driver App.
  2. Click on Settings Icon -> Project Settings.
  3. In General, tab click on Add app -> Add Firebase to your Android app.
  4. After adding the Android app to firebase project download the latest google-services.json file and add to the app directory of your Android app.

All the prep work being done regarding firebase and Google Maps. Now let’s start making Passenger app. Below is the UI of Passenger App that we’re gonna make.

Passenger App UI


You see the UI is very basic, we’ve got a Google Maps and a TextView which tells how many drivers are currently online. The following shows the code of the above UI.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <fragment
        android:id="@+id/supportMap"
        android:name="com.google.android.gms.maps.SupportMapFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="spartons.com.frisbeeGo.fragments.MapFragment" />

    <TextView
        android:id="@+id/totalOnlineDrivers"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginEnd="30dp"
        android:layout_marginStart="30dp"
        android:layout_marginTop="40dp"
        android:background="#FFFFFF"
        android:gravity="center"
        android:padding="10dp"
        android:text="@string/total_online_drivers"
        android:textColor="#000000"
        android:textSize="15sp" />

</RelativeLayout>

MainActivity

class MainActivity : AppCompatActivity(), FirebaseDriverListener {

    companion object {
        private const val MY_PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION = 6161
        private const val ONLINE_DRIVERS = "online_drivers"
    }

    private lateinit var googleMap: GoogleMap
    private lateinit var locationProviderClient: FusedLocationProviderClient
    private lateinit var locationRequest: LocationRequest
    private lateinit var locationCallback: LocationCallback
    private var locationFlag = true
    private lateinit var valueEventListener: FirebaseEventListenerHelper
    private val uiHelper = UiHelper()
    private val googleMapHelper = GoogleMapHelper()
    private val databaseReference = FirebaseDatabase.getInstance().reference.child(ONLINE_DRIVERS)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val mapFragment: SupportMapFragment = supportFragmentManager.findFragmentById(R.id.supportMap) as SupportMapFragment
        mapFragment.getMapAsync { googleMap = it }
        createLocationCallback()
        locationProviderClient = LocationServices.getFusedLocationProviderClient(this)
        locationRequest = uiHelper.getLocationRequest()
        if (!uiHelper.isPlayServicesAvailable(this)) {
            Toast.makeText(this, "Play Services did not installed!", Toast.LENGTH_SHORT).show()
            finish()
        } else requestLocationUpdate()
        valueEventListener = FirebaseEventListenerHelper(this)
        databaseReference.addChildEventListener(valueEventListener)
    }

    @SuppressLint("MissingPermission")
    private fun requestLocationUpdate() {
        if (!uiHelper.isHaveLocationPermission(this)) {
            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), MY_PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION)
            return
        }
        if (uiHelper.isLocationProviderEnabled(this))
            uiHelper.showPositiveDialogWithListener(this, resources.getString(R.string.need_location), resources.getString(R.string.location_content), object : IPositiveNegativeListener {
                override fun onPositive() {
                    startActivity(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS))
                }
            }, "Turn On", false)
        locationProviderClient.requestLocationUpdates(locationRequest, locationCallback, Looper.myLooper())
    }

    private fun createLocationCallback() {
        locationCallback = object : LocationCallback() {
            override fun onLocationResult(locationResult: LocationResult?) {
                super.onLocationResult(locationResult)
                if (locationResult!!.lastLocation == null) return
                val latLng = LatLng(locationResult.lastLocation.latitude, locationResult.lastLocation.longitude)
                Log.e("Location", latLng.latitude.toString() + " , " + latLng.longitude)
                if (locationFlag) {
                    locationFlag = false
                    animateCamera(latLng)
                }
            }
        }
    }

    private fun animateCamera(latLng: LatLng) {
        val cameraUpdate = googleMapHelper.buildCameraUpdate(latLng)
        googleMap.animateCamera(cameraUpdate, 10, null)
    }

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == MY_PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION) {
            val value = grantResults[0]
            if (value == PackageManager.PERMISSION_DENIED) {
                Toast.makeText(this, "Location Permission denied", Toast.LENGTH_SHORT).show()
                finish()
            } else if (value == PackageManager.PERMISSION_GRANTED) requestLocationUpdate()
        }
    }

    override fun onDriverAdded(driver: Driver) {
        val markerOptions = googleMapHelper.getDriverMarkerOptions(LatLng(driver.lat, driver.lng))
        val marker = googleMap.addMarker(markerOptions)
        marker.tag = driver.driverId
        MarkerCollection.insertMarker(marker)
        totalOnlineDrivers.text = resources.getString(R.string.total_online_drivers).plus(" ").plus(MarkerCollection.allMarkers().size)
    }

    override fun onDriverRemoved(driver: Driver) {
        MarkerCollection.removeMarker(driver.driverId)
        totalOnlineDrivers.text = resources.getString(R.string.total_online_drivers).plus(" ").plus(MarkerCollection.allMarkers().size)
    }

    override fun onDriverUpdated(driver: Driver) {
        val marker = MarkerCollection.getMarker(driverId = driver.driverId)
        MarkerAnimationHelper.animateMarkerToGB(marker!!, LatLng(driver.lat, driver.lng), LatLngInterpolator.Spherical())
    }

    override fun onDestroy() {
        super.onDestroy()
        databaseReference.removeEventListener(valueEventListener)
        locationProviderClient.removeLocationUpdates(locationCallback)
        MarkerCollection.clearMarkers()
    }
}

The MainActivity got a bunch of important points which I’m going to explain below.

  • databaseReference: The DatabaseReference will be the online-drivers node because the passenger wants to track all drivers. Now if there is more than one driver then the passenger will be able to see all drivers on Google Map. If you want to listen to the specific driver then another child node will be added with the driverId.
  • onDriverAdded: This method will be called whenever new driver online in the firebase real-time database. When the new driver comes online we show the Marker to Google Maps and store the Marker in our MarkerCollection.
  • onDriverRemoved: This function will be called when the driver goes offline in the firebase real-time database. Later that we’ll remove the driver Marker from Google Maps and from MarkerCollection as well.
  • onDriverUpdated: This method will be called when the driver current position changed. Later that we get the driver Marker from MarkerCollection and animate the Marker to driver new position.
  • onDestroy: In this method, we’re removing firebase events, removing the current location updates and clearing the MarkerCollection.

Other methods of MainActivity are same as Driver App which I’ve explained in my previous article.

MarkerCollection

object MarkerCollection {

    private val markers: MutableList<Marker> = LinkedList()

    fun insertMarker(marker: Marker) = apply {
        markers.add(marker)
    }

    fun getMarker(driverId: String): Marker? {
        for (marker in markers)
            if (marker.tag == driverId) return marker
        return null
    }

    fun clearMarkers() = apply {
        markers.clear()
    }

    fun removeMarker(driverId: String) {
        val marker = getMarker(driverId)
        marker?.remove()
        if (marker != null) markers.remove(marker)
    }

    fun allMarkers() = markers
}

The following explains about MarkerCollection class.

  • insertMarker: Add the Marker instance to markers collection.
  • getMarker: When creating a Marker in MainActivity we set the driverId as a tag so, we search the Marker on the basis of Tag.
  • removeMarker: This method remove the specific driver Marker from the Google Maps when he goes offline.
  • clearMarker: Empty the Marker Collection.

FirebaseEventListenerHelper

class FirebaseEventListenerHelper(private val firebaseDriverListener: FirebaseDriverListener) : ChildEventListener {

    override fun onCancelled(p0: DatabaseError) {

    }

    override fun onChildMoved(p0: DataSnapshot, p1: String?) {

    }

    override fun onChildChanged(p0: DataSnapshot, p1: String?) {
        val driver = p0.getValue(Driver::class.java)
        firebaseDriverListener.onDriverUpdated(driver!!)
    }

    override fun onChildAdded(p0: DataSnapshot, p1: String?) {
        val driver = p0.getValue(Driver::class.java)
        firebaseDriverListener.onDriverAdded(driver!!)
    }

    override fun onChildRemoved(p0: DataSnapshot) {
        val driver = p0.getValue(Driver::class.java)
        firebaseDriverListener.onDriverRemoved(driver!!)
    }
}

This is a helper class for listening to the events in firebase real-time database. The FirebaseEventListenerHelper class implements ChildEventListener interface.

FirebaseDriverListener

interface FirebaseDriverListener {

    fun onDriverAdded(driver: Driver)

    fun onDriverRemoved(driver: Driver)

    fun onDriverUpdated(driver: Driver)
}

The GoogleMapHelper, UiHelper, MarkerAnimationHelper, Driver, IPositiveNegativeListener and LatLngInterpolator classes I’ve discussed these classes in my previous article.

This is only a small part of my app in PlayStore. Both apps are in testing mode see feel free to take a look DriverApp and PassengerApp.

Alright, guys, this is here we complete both of our apps. If you’ve any queries regarding this post please do comment below.

Download Complete Code

Thank you for being here and keep reading…

Previous Part

4 COMMENTS

    • Yes, You can use Google GeoCoder class to parse the current latitude and longitude into readable location name. The following shows the example.
      // Note: Do use this code in a background thread.
      val geoCoder = Geocoder(context : this)
      val values = geoCoder.getFromLocation(lat : currentLat, lng : currentLng, maxResult : 1)
      val address : String = values[0].getAddressLine(0)

    • Uploaded the Java version of the above app on GitHub. I’m sorry for the late reply I’ve got so much busy in my office work and didn’t able to see my account. Apologies!

      Best Regards,
      Ahsen Saeed

LEAVE A REPLY

Please enter your comment!
Please enter your name here