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

Capstone I — Firebase Auth, Firestore, Camera, Storage (Tour app)

Start the capstone Tour/Expense app. Firebase Auth for login, Firestore for tours & expenses, camera capture, and Firebase Storage for photo uploads.

0% done

Key concepts

  • Firebase project setup (google-services.json)
  • FirebaseAuth (email/password)
  • Firestore collections & documents
  • CameraX or ACTION_IMAGE_CAPTURE
  • Firebase Storage upload with progress

Lectures in this day

  1. 01

    Module / App Overview

    The capstone (final) app is a Tour Tracker. Users sign in, create a tour, add a photo and expenses, and everything is stored in Firebase (Google's free cloud). Log in on another phone — it's all still there.

    Real life · A pocket travel journal that lives in the cloud. Lose your phone, sign in on a new one — nothing lost.

    Practice · 3 quick questions

    1. Q1.Module 6 builds a…

    2. Q2.Cloud data uses…

    3. Q3.Auth uses…

    Pick one answer per question.
  2. 02

    Starter Project + Firebase Setup

    Create a free Firebase project in the browser. Register your Android app (package name and SHA-1 fingerprint). Download `google-services.json` into `app/` and add the Google Services Gradle plugin — that connects the app to Firebase.

    Real life · Registering a new SIM with a mobile carrier — once done, your phone can talk to the tower.
    gradle
    // project build.gradle
    classpath("com.google.gms:google-services:4.4.2")
    
    // app build.gradle
    apply(plugin = "com.google.gms.google-services")
    
    implementation(platform("com.google.firebase:firebase-bom:33.1.0"))
    implementation("com.google.firebase:firebase-auth-ktx")
    implementation("com.google.firebase:firebase-firestore-ktx")
    implementation("com.google.firebase:firebase-storage-ktx")

    Practice · 3 quick questions

    1. Q1.Firebase project is created in…

    2. Q2.Config file for Android is…

    3. Q3.You add Firebase SDKs via…

    Pick one answer per question.
  3. 03

    Launcher Screen Setup (I)

    The launcher (first) screen decides where to send the user. If nobody is signed in → open Login. If a user is already signed in → open Home. Then `finish()` the launcher so Back doesn't return to it.

    Real life · The bouncer at a club door: 'member? go in. new here? head to registration.'
    kotlin
    override fun onCreate(s: Bundle?) {
        super.onCreate(s)
        val next = if (FirebaseAuth.getInstance().currentUser == null)
            LoginActivity::class.java
        else
            HomeActivity::class.java
    
        startActivity(Intent(this, next))
        finish()                               0
    }

    Practice · 3 quick questions

    1. Q1.Launcher/splash decides…

    2. Q2.Check current user with…

    3. Q3.Navigate to next screen using…

    Pick one answer per question.
  4. 04

    Launcher Screen Setup (II)

    Add a proper splash screen using the SplashScreen API — a themed icon that appears the very instant your app opens, before your code even runs. It makes the app feel fast and branded.

    Real life · The airline logo on a plane's tail — the first thing you see, and it sets the mood.
    xml
    <class="tok-key">style name="Theme.App.Starting" parent="Theme.SplashScreen">
        <class="tok-key">item name="windowSplashScreenBackground">@color/brand_bg</class="tok-key">item>
        <class="tok-key">item name="windowSplashScreenAnimatedIcon">@drawable/ic_splash</class="tok-key">item>
        <class="tok-key">item name="postSplashScreenTheme">@style/Theme.App</class="tok-key">item>
    </class="tok-key">style>

    Practice · 3 quick questions

    1. Q1.You should NOT keep the splash visible…

    2. Q2.A cold start splash uses…

    3. Q3.Avoid heavy work in…

    Pick one answer per question.
  5. 05

    User Login and Registration (I)

    A simple form: email box, password box, one button. On signup call `createUserWithEmailAndPassword`. On login call `signInWithEmailAndPassword`. Show a loading spinner while it runs, and handle success/failure with callbacks.

    Real life · Getting a library card (signup), then just showing it next time to borrow books (login).
    kotlin
    auth.createUserWithEmailAndPassword(email, pass)
        .addOnSuccessListener { goHome() }
        .addOnFailureListener { toast(it.message ?: "Signup failed") }

    Practice · 3 quick questions

    1. Q1.Email/password signup uses…

    2. Q2.Sign in uses…

    3. Q3.Always validate…

    Pick one answer per question.
  6. 06

    User Login and Registration (II)

    Check the email format and password length BEFORE calling Firebase — this catches obvious mistakes fast. Also handle Firebase errors like weak password or wrong password with clear messages the user can act on.

    Real life · The registration desk telling you 'missing signature' before you get in line — you fix it and don't waste time.

    Practice · 3 quick questions

    1. Q1.On success you should…

    2. Q2.On failure show…

    3. Q3.Store extra profile data in…

    Pick one answer per question.
  7. 07

    Add Tour to Firestore

    Firestore stores JSON-like documents inside collections. `db.collection("tours").add(tour)` creates a new document with a random id. You send in a data class — Firestore converts it for you.

    Real life · Dropping a Google Doc into a shared folder — everyone with access sees it instantly.
    kotlin
    data class Tour(
        val name: String = "",
        val city: String = "",
        val startAt: Long = 0L,
        val ownerId: String = "",
    )
    
    db.collection("tours").add(
        Tour(
            name = "Sundarbans",
            city = "Khulna",
            startAt = System.currentTimeMillis(),
            ownerId = auth.currentUser!!.uid,     0
        )
    )

    Practice · 3 quick questions

    1. Q1.Firestore stores data as…

    2. Q2.Add a doc with…

    3. Q3.Each doc has a…

    Pick one answer per question.
  8. 08

    Retrieve Data from Firestore (I)

    For a one-time read call `.get()`. It returns a QuerySnapshot — think of it as a bag of matching documents. Use `.whereEqualTo(field, value)` to filter, then convert to your data class with `toObjects(...)`.

    Real life · Opening your Google Drive folder: you only see the docs you own, filtered out of the whole company's storage.
    kotlin
    db.collection("tours")
        .whereEqualTo("ownerId", uid)              0
        .get()
        .addOnSuccessListener { snap ->
            val tours = snap.toObjects(Tour::class.java)
            adapter.submitList(tours)
        }

    Practice · 3 quick questions

    1. Q1.Read all docs with…

    2. Q2.Realtime updates use…

    3. Q3.Firestore calls return…

    Pick one answer per question.
  9. 09

    Retrieve Data from Firestore (II)

    For LIVE data, use `.addSnapshotListener`. Firestore calls your code again every time the matching data changes — so your list stays fresh without a refresh button.

    Real life · A shared Google Sheet — someone edits a cell in another city and your screen updates a second later.
    kotlin
    db.collection("tours")
        .whereEqualTo("ownerId", uid)
        .addSnapshotListener { snap, err ->
            if (err != null || snap == null) return@addSnapshotListener
            adapter.submitList(snap.toObjects(Tour::class.java))
        }

    Practice · 3 quick questions

    1. Q1.Convert doc to object with…

    2. Q2.Show list in UI with…

    3. Q3.Handle empty state by…

    Pick one answer per question.
  10. 10

    Retrieve Single Document (I)

    For a detail screen, you already know the tour id. Fetch that ONE document with `.document(id).get()`. The snapshot has one thing in it — convert to your data class.

    Real life · Opening one exact Google Doc by its URL — you get that document, nothing else.
    kotlin
    db.collection("tours").document(id).get()
        .addOnSuccessListener { doc ->
            val tour = doc.toObject(Tour::class.java) ?: return@addOnSuccessListener
            render(tour)
        }

    Practice · 3 quick questions

    1. Q1.Read one document by…

    2. Q2.You need the…

    3. Q3.Pass the ID to next screen via…

    Pick one answer per question.
  11. 11

    Retrieve Single Document (II)

    For a live detail view, use `.addSnapshotListener` on the document reference. Same pattern as the list, but scoped to one document.

    Real life · Opening a doc that a friend is editing right now — you see their changes live.

    Practice · 3 quick questions

    1. Q1.Bind the loaded doc to…

    2. Q2.Loading state should show…

    3. Q3.On error you should…

    Pick one answer per question.
  12. 12

    Save / Retrieve Expenses (I)

    Expenses belong to a specific tour, so put them in a subcollection: `tours/{tourId}/expenses`. Same Firestore API — you just walk a longer path.

    Real life · Each tour folder has its own 'receipts' subfolder. Delete the tour and you can also clean up its receipts.
    kotlin
    db.collection("tours").document(tourId)
        .collection("expenses")
        .add(mapOf(
            "label" to "Bus fare",
            "amount" to 350,
            "at" to Timestamp.now(),
        ))

    Practice · 3 quick questions

    1. Q1.Expenses are usually a…

    2. Q2.Each expense has fields like…

    3. Q3.Sum totals in…

    Pick one answer per question.
  13. 13

    Save / Retrieve Expenses (II)

    Show a running total by summing the amounts of the loaded expenses. If you're using a snapshot listener, sum inside the listener so the total stays live.

    Real life · The bottom of a spreadsheet auto-summing your travel receipts as you paste new rows.

    Practice · 3 quick questions

    1. Q1.Show expenses using…

    2. Q2.Empty state should…

    3. Q3.Adding uses…

    Pick one answer per question.
  14. 14

    Take Photo using Camera (I)

    The Activity Result API is Android's modern way to launch the camera. `ActivityResultContracts.TakePicture()` handles the plumbing. You give it a URI (a file address); the camera saves the photo there.

    Real life · Handing your phone to a friend for a photo — you tell them 'save it in the family album folder', they hand it back.
    kotlin
    val photoUri = FileProvider.getUriForFile(
        this,
        "$packageName.fileprovider",
        File(cacheDir, "tour_${System.currentTimeMillis()}.jpg"),
    )
    
    val takePhoto = registerForActivityResult(
        ActivityResultContracts.TakePicture()
    ) { ok ->
        if (ok) imageView.setImageURI(photoUri)          0
    }
    
    findViewById<Button>(R.id.snap).setOnClickListener {
        takePhoto.launch(photoUri)
    }

    Practice · 3 quick questions

    1. Q1.Camera capture uses an…

    2. Q2.You must declare in manifest…

    3. Q3.The result comes back via…

    Pick one answer per question.
  15. 15

    Take Photo using Camera (II)

    Ask for the CAMERA permission only if you actually need it. Compress the JPEG before upload so it doesn't waste bandwidth. Delete the temporary file after a successful upload.

    Real life · Cropping and resizing a photo before uploading it — nice and small, still looks good in the album.

    Practice · 3 quick questions

    1. Q1.Full-size photos should be saved to…

    2. Q2.Show preview using…

    3. Q3.Handle permission denial by…

    Pick one answer per question.
  16. 16

    Upload Photo to Firebase Storage

    Firebase Storage keeps your files. Get a `StorageReference` for a path (e.g. `tours/{id}/cover.jpg`), call `putFile(uri)`, then ask for `downloadUrl` and save that URL string in the Firestore document.

    Real life · Uploading a photo to Google Drive, then pasting its share link into your notes.
    kotlin
    val ref = FirebaseStorage.getInstance()
        .reference.child("tours/$tourId/cover.jpg")
    
    ref.putFile(photoUri)
        .addOnSuccessListener {
            ref.downloadUrl.addOnSuccessListener { url ->
                db.collection("tours").document(tourId)
                    .update("photoUrl", url.toString())
            }
        }

    Practice · 3 quick questions

    1. Q1.Firebase Storage is for…

    2. Q2.Upload with…

    3. Q3.Store the resulting URL in…

    Pick one answer per question.
  17. 17

    Update Tour Status (I)

    Add a `status` field with one of a few values ("planned", "ongoing", "done"). Use `.update(field, value)` to change one field only — cheap and safe (other fields stay).

    Real life · Ticking a task from 'in progress' to 'done' — that one field changes, the rest of the record stays the same.
    kotlin
    db.collection("tours").document(tourId)
        .update(mapOf(
            "status" to "ongoing",
            "startedAt" to Timestamp.now(),
        ))

    Practice · 3 quick questions

    1. Q1.Update a field in Firestore with…

    2. Q2.Show new status in UI by…

    3. Q3.Restrict who can update via…

    Pick one answer per question.
  18. 18

    Update Tour Status (II)

    In the list, show status as a small colored chip (green/amber/gray). Tap the chip to open an AlertDialog with the 3 options, then call the same `.update(...)` when the user picks one.

    Real life · Traffic-light stickers on a project board — one glance tells you where each item stands.

    Practice · 3 quick questions

    1. Q1.Bad security rules cause…

    2. Q2.Default in dev should NOT be…

    3. Q3.Test rules with…

    Pick one answer per question.

Real-life analogies

Firestore

A tree of Google Docs folders — collections are folders, documents are the docs inside. Query, sync, and share instantly.

Firebase Storage

Google Drive, but keyed by paths your app owns. You upload a file and get back a URL you can save in Firestore.

Code examples

Email/password login

kotlin
val auth = FirebaseAuth.getInstance()

fun login(email: String, pass: String) {
    auth.signInWithEmailAndPassword(email, pass)
        .addOnSuccessListener { navigateToHome() }
        .addOnFailureListener { toast(it.message ?: "Login failed") }
}

Add & fetch a Firestore document

kotlin
val db = Firebase.firestore

data class Tour(val name: String = "", val city: String = "", val budget: Double = 0.0)

0
db.collection("tours").add(Tour("Sundarbans", "Khulna", 15000.0))

1
db.collection("tours")
  .whereEqualTo("city", "Khulna")
  .get()
  .addOnSuccessListener { snap ->
      val tours = snap.toObjects(Tour::class.java)
      renderTours(tours)
  }

Upload photo to Storage

kotlin
val ref = FirebaseStorage.getInstance()
    .reference.child("tours/$tourId/cover.jpg")

ref.putFile(photoUri)
    .addOnSuccessListener {
        ref.downloadUrl.addOnSuccessListener { url ->
            db.collection("tours").document(tourId)
                .update("photoUrl", url.toString())
        }
    }

Hands-on checklist

Milestone

Working Tour app with auth, cloud DB, and photo upload.

Common pitfalls

  • Forgetting to add SHA-1 in Firebase console — some services silently fail.
  • Not configuring Firestore rules — either wide open or nothing reads.