All days
Day 12Module 6 · Capstone· 6–8 hrs

Capstone II — Location, Weather API, Google Maps, Geofencing

Finish the capstone. Detect current location, pull weather from a REST API, embed Google Maps, and set up a geofence that fires a broadcast alert on entry.

0% done

Key concepts

  • FusedLocationProviderClient
  • Runtime location permissions
  • REST call with Retrofit + coroutines
  • SupportMapFragment & Marker
  • GeofencingClient + GeofenceBroadcastReceiver

Lectures in this day

  1. 01

    Weather Section Overview

    Add a weather widget to the tour: 1) get the phone's location, 2) call OpenWeatherMap (a free weather API), 3) show temperature, humidity, and an icon. Use Retrofit (a networking library) with coroutines.

    Real life · Peeking out the window before packing for a trip — 'do I bring an umbrella?' — right inside the app.

    Practice · 3 quick questions

    1. Q1.Weather section adds…

    2. Q2.Weather data comes from…

    3. Q3.Networking library commonly used?

    Pick one answer per question.
  2. 02

    Detect Current Location (I)

    Add `ACCESS_FINE_LOCATION` to the manifest and ask the user for it at runtime with the Activity Result API. `FusedLocationProviderClient.lastLocation` gives a cached location fast — often good enough.

    Real life · Asking a passerby 'roughly where are we?' — quick answer, maybe off by a block, but plenty for a weather lookup.
    kotlin
    val ask = registerForActivityResult(
        ActivityResultContracts.RequestPermission()
    ) { granted ->
        if (granted) fetchLocation()          0
    }
    
    ask.launch(Manifest.permission.ACCESS_FINE_LOCATION)

    Practice · 3 quick questions

    1. Q1.Location on Android uses…

    2. Q2.Which permission is required?

    3. Q3.You must request permission at…

    Pick one answer per question.
  3. 03

    Detect Current Location (II)

    For a fresh (up-to-date) location, use `getCurrentLocation(PRIORITY_BALANCED_POWER_ACCURACY, null)`. It's a little slower but more accurate. Handle the case where location is off (null result).

    Real life · Asking a GPS instead of a passerby — takes a second longer, but pinpoints where you are.
    kotlin
    @SuppressLint("MissingPermission")
    fun fetchLocation(onLoc: (Location) -> Unit) {
        LocationServices.getFusedLocationProviderClient(this)
            .getCurrentLocation(Priority.PRIORITY_BALANCED_POWER_ACCURACY, null)
            .addOnSuccessListener { loc -> loc?.let(onLoc) }
    }

    Practice · 3 quick questions

    1. Q1.If user denies location, you should…

    2. Q2.Background location needs…

    3. Q3.Cache last known location because…

    Pick one answer per question.
  4. 04

    Get Weather Data

    Retrofit turns an interface into an HTTP client. Mark your API functions `suspend` so you can call them from a coroutine. Moshi or Gson converts the JSON reply into your data classes automatically.

    Real life · Calling a hotline that reads out today's forecast — you dial, they answer, you jot down what they say.
    kotlin
    interface WeatherApi {
        @GET("data/2.5/weather")
        suspend fun current(
            @Query("lat") lat: Double,
            @Query("lon") lon: Double,
            @Query("appid") key: String,
            @Query("units") units: String = "metric",
        ): WeatherResponse
    }

    Practice · 3 quick questions

    1. Q1.Retrofit maps HTTP to…

    2. Q2.JSON parsing typically uses…

    3. Q3.Weather call needs…

    Pick one answer per question.
  5. 05

    Weather Layout (I)

    Design the weather card: city name at the top, a huge temperature in the middle, a description under it, and an icon. Icon URL pattern is `https://openweathermap.org/img/wn/{icon}@2x.png`.

    Real life · The hero panel of the weather app you already use — one glance tells you if you need a jacket.
    xml
    <class="tok-key">androidx.cardview.widget.CardView
        android:layout_width="match_parent" android:layout_height="wrap_content"
        android:layout_margin="16dp" app:cardCornerRadius="16dp">
        <class="tok-key">LinearLayout android:orientation="vertical" android:padding="20dp"
            android:layout_width="match_parent" android:layout_height="wrap_content">
            <class="tok-key">TextView android:id="@+id/city" android:textSize="22sp"
                      android:layout_width="wrap_content" android:layout_height="wrap_content" />
            <class="tok-key">TextView android:id="@+id/temp" android:textSize="48sp"
                      android:layout_width="wrap_content" android:layout_height="wrap_content" />
            <class="tok-key">ImageView android:id="@+id/icon"
                       android:layout_width="80dp" android:layout_height="80dp" />
        </class="tok-key">LinearLayout>
    </class="tok-key">androidx.cardview.widget.CardView>

    Practice · 3 quick questions

    1. Q1.Weather layout usually shows…

    2. Q2.Weather icon can be loaded with…

    3. Q3.Container of choice for a simple weather card?

    Pick one answer per question.
  6. 06

    Weather Layout (II)

    Add a row of small stat chips under the main card: humidity, wind, feels-like. Use a horizontal LinearLayout with equal weights so each chip takes the same width.

    Real life · The little widgets under the big temperature — humidity, wind, UV — the details you check second.

    Practice · 3 quick questions

    1. Q1.Show loading with…

    2. Q2.On network error show…

    3. Q3.Refresh gesture uses…

    Pick one answer per question.
  7. 07

    Temperature Conversion

    OpenWeatherMap gives Celsius when you pass `units=metric`. To show Fahrenheit, use `f = c * 9/5 + 32`. Let the user pick their unit in settings, then remember it.

    Real life · Reading a Fahrenheit recipe when your oven is Celsius — one formula and you're cooking, not guessing.
    kotlin
    fun cToF(c: Double) = c * 9 / 5 + 32
    
    val display = if (useFahrenheit)
        "${cToF(temp).toInt()}°F"
    else
        "${temp.toInt()}°C"

    Practice · 3 quick questions

    1. Q1.Celsius → Fahrenheit formula is…

    2. Q2.Kelvin → Celsius is…

    3. Q3.Store unit preference in…

    Pick one answer per question.
  8. 08

    Google Maps and Maps Fragment Overview

    Add the Maps SDK to Gradle. Drop a `SupportMapFragment` in your XML. In Kotlin call `getMapAsync` — you get a `GoogleMap` object. Add a Marker at the user's lat/lon and move the camera to that point.

    Real life · Pinning your current spot on a paper map so you can plan the walk to the museum.
    xml
    <class="tok-key">fragment
        android:id="@+id/map"
        android:name="com.google.android.gms.maps.SupportMapFragment"
        android:layout_width="match_parent"
        android:layout_height="300dp" />
    
    <!-- In Kotlin: -->
    <!--
    (childFragmentManager.findFragmentById(R.id.map) as SupportMapFragment)
        .getMapAsync { map ->
            val here = LatLng(loc.latitude, loc.longitude)
            map.addMarker(MarkerOptions().position(here).title("You"))
            map.moveCamera(CameraUpdateFactory.newLatLngZoom(here, 14f))
        }
    -->

    Practice · 3 quick questions

    1. Q1.Google Maps in Android uses…

    2. Q2.You need a…

    3. Q3.Map is ready via callback…

    Pick one answer per question.
  9. 09

    Geofencing Overview (I)

    A geofence is an invisible circle on the map. The OS watches it in the background and wakes your app when the user ENTERS, EXITS, or STAYS INSIDE. You don't need to keep checking — Android does it for you.

    Real life · A dog collar that beeps when the dog leaves the yard. You don't watch — the collar does.

    Practice · 3 quick questions

    1. Q1.Geofencing triggers when…

    2. Q2.Each geofence has a…

    3. Q3.Delivered via…

    Pick one answer per question.
  10. 10

    Geofencing Overview (II)

    Register geofences with `GeofencingClient.addGeofences(...)` and a `PendingIntent` that fires a BroadcastReceiver when a boundary is crossed. On Android 10+ you also need background location permission (a separate ask).

    Real life · Handing a security company a list of 'alert zones' — they call you if anyone crosses one.
    kotlin
    val fence = Geofence.Builder()
        .setRequestId("hotel")                                 0
        .setCircularRegion(lat, lon, 150f)                     1
        .setExpirationDuration(Geofence.NEVER_EXPIRE)
        .setTransitionTypes(
            Geofence.GEOFENCE_TRANSITION_ENTER or
            Geofence.GEOFENCE_TRANSITION_EXIT
        )
        .build()

    Practice · 3 quick questions

    1. Q1.Which permission is needed for geofences on newer APIs?

    2. Q2.Register geofences with…

    3. Q3.Remove them with…

    Pick one answer per question.
  11. 11

    Location Detection Refactoring

    You'll need location in more than one screen. Move the code into a small `LocationHelper` class — every screen uses the same helper, and permission code isn't duplicated.

    Real life · Making one master keychain the whole family shares, instead of everyone copying their own set.
    kotlin
    class LocationHelper(private val ctx: Context) {
        private val client = LocationServices.getFusedLocationProviderClient(ctx)
    
        @SuppressLint("MissingPermission")
        suspend fun current(): Location? = suspendCancellableCoroutine { cont ->
            client.getCurrentLocation(Priority.PRIORITY_BALANCED_POWER_ACCURACY, null)
                .addOnSuccessListener { cont.resume(it) {} }
                .addOnFailureListener { cont.resume(null) {} }
        }
    }

    Practice · 3 quick questions

    1. Q1.Refactor moves location logic into…

    2. Q2.It helps because…

    3. Q3.The Activity/Fragment now just…

    Pick one answer per question.
  12. 12

    Create and Add Geofence (I)

    Build the Geofence object, wrap it in a `GeofencingRequest` (say what to trigger on initial state), then register it with a `PendingIntent` that names the BroadcastReceiver Android should call.

    Real life · Programming a smart lock: define the zone, wire up the door alarm, arm it.
    kotlin
    val req = GeofencingRequest.Builder()
        .setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
        .addGeofence(fence)
        .build()
    
    val pi = PendingIntent.getBroadcast(
        ctx, 0,
        Intent(ctx, GeofenceReceiver::class.java),
        PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE,
    )
    
    geofencingClient.addGeofences(req, pi)

    Practice · 3 quick questions

    1. Q1.To make a geofence you supply…

    2. Q2.Transitions include…

    3. Q3.Expiration NEVER_EXPIRE means…

    Pick one answer per question.
  13. 13

    Create and Add Geofence (II)

    When the tour ends, remove geofences with `removeGeofences(...)` so they don't keep waking your app. Also handle error codes — for example `GEOFENCE_NOT_AVAILABLE` if the user has turned off location.

    Real life · Disarming and packing up the alarm when you check out of the hotel.

    Practice · 3 quick questions

    1. Q1.Result of addGeofences is a…

    2. Q2.Common failure cause?

    3. Q3.Test on…

    Pick one answer per question.
  14. 14

    Get Geofence Alert

    A BroadcastReceiver decodes the event and posts a notification like 'Arrived at hotel'. Keep the receiver's code SHORT — hand any heavy work off to WorkManager, because a receiver has only a few seconds to run.

    Real life · The alarm rings briefly, then hands off to the security team. Short ring, longer response.
    kotlin
    class GeofenceReceiver : BroadcastReceiver() {
        override fun onReceive(ctx: Context, intent: Intent) {
            val ev = GeofencingEvent.fromIntent(intent) ?: return
            if (ev.hasError()) return
    
            val kind = when (ev.geofenceTransition) {
                Geofence.GEOFENCE_TRANSITION_ENTER -> "Arrived at"
                Geofence.GEOFENCE_TRANSITION_EXIT  -> "Left"
                else                                -> "Near"
            }
            val id = ev.triggeringGeofences?.firstOrNull()?.requestId ?: "zone"
            notify(ctx, "$kind $id")
        }
    }

    Practice · 3 quick questions

    1. Q1.Show an alert on geofence event using…

    2. Q2.Handle event in…

    3. Q3.Extract event data with…

    Pick one answer per question.
  15. 15

    Module 6 Summary

    You built a real cloud-connected app: sign-in, live data in Firestore, photos in Storage, weather over the internet, Google Maps, and geofencing. That's the same shape as apps in the Play Store.

    Real life · Graduation day. The app you built could ship tomorrow. From here it's about polishing your craft.

    Practice · 3 quick questions

    1. Q1.By Module 6's end you can build…

    2. Q2.Which is a good next step?

    3. Q3.Most important habit going forward?

    Pick one answer per question.

Real-life analogies

Geofence

A smart doorbell for a region. Cross an invisible circle around a place, Android rings your app so it can react.

Retrofit

A phone directory + auto-dialer for web APIs. You describe the numbers (endpoints); Retrofit dials and returns the transcript.

Code examples

Get current location

kotlin
val client = LocationServices.getFusedLocationProviderClient(this)

@SuppressLint("MissingPermission")
fun fetchLocation(onResult: (Location) -> Unit) {
    client.lastLocation.addOnSuccessListener { loc ->
        loc?.let(onResult)
    }
}

Weather via Retrofit

kotlin
interface WeatherApi {
    @GET("data/2.5/weather")
    suspend fun current(
        @Query("lat") lat: Double,
        @Query("lon") lon: Double,
        @Query("appid") key: String,
        @Query("units") units: String = "metric"
    ): WeatherResponse
}

class WeatherRepo(private val api: WeatherApi) {
    suspend fun near(lat: Double, lon: Double) =
        api.current(lat, lon, BuildConfig.OWM_KEY)
}

Add a geofence

kotlin
val geofence = Geofence.Builder()
    .setRequestId("home_zone")
    .setCircularRegion(23.8103, 90.4125, 200f) 0
    .setExpirationDuration(Geofence.NEVER_EXPIRE)
    .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER)
    .build()

val request = GeofencingRequest.Builder()
    .setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
    .addGeofence(geofence).build()

geofencingClient.addGeofences(request, geofencePendingIntent)

Hands-on checklist

Milestone

Complete capstone: auth + Firestore + camera/storage + REST weather + Maps + Geofencing.

Common pitfalls

  • Skipping ACCESS_BACKGROUND_LOCATION on Android 10+ — geofences won't fire reliably.
  • Hard-coding your API key in code — use local.properties + BuildConfig.