Intro to Android Development
  • Welcome
  • Syllabus
  • Hack Challenge
  • Resources
    • Lecture Videos
    • Ed Discussion
    • Git & GitHub Help/How-To
    • Setting up Android Studio
    • Starting an Android Studio Project & Making an Emulator
    • Importing, Exporting, & Submitting Your Projects to CMS
  • SP25 Course Material
    • Week 1 | Course Logistics, Kotlin, & Basic UI
      • Relevant Links
      • Demo/Lecture: Eatery Card
      • A0: Eatery Card (Follow-Along)
    • Week 2 | States, Components, LazyColumn
      • Relevant Links
      • Demo: Todo List
      • A2: Shopping List
    • Week 3 | Navigation & Animations
      • Relevant Links
      • Demo: Onboarding
      • A3: Stock Trading (RobbingGood)
    • Week 4 | MVVM and Flows
      • Relevant Links
      • Demo: Eatery Card 2
      • A4: Chat of a Lifetime
    • Week 5 | Dumb Components & UIEvents
      • Relevant Links
      • Demo: Music Player
      • A5: Rate My Vibe
    • Week 6 | Coroutines, Networking, JSON
      • Relevant Links
      • Demo: Retrofit
      • A6: You Should Even Lift, Bro.
  • Bonus Week | Android Job Search
    • Relevant Links
    • Android Technical Interview Question!
  • Textbook
    • 1. Introduction to the Editor and Views
      • 1.1 Introduction to the Editor
      • 1.2 SDK Management
      • 1.3 Kotlin Overview
      • 1.4 Views
      • 1.5 Android Studio Project Demo + Understanding The Editor
    • 2. Jetpack Compose
      • 2.1 Introduction
      • 2.2 Layouts
      • 2.3 Modifiers
      • 2.4 Animations
      • 2.5 Lazy Lists
      • 2.6 Reactive UI
    • 3. Intents and Manifest
      • 3.1 Activities
      • 3.2 Implicit Intents
      • 3.3 Explicit Intents
      • 3.4 Manifest
      • 3.5 Permissions
      • 3.6 Summary
    • 4. Navigation
      • 4.1 Types of Navigation
      • 4.2 Implementation of the Bottom Navigation Bar
    • 5. Data and Persistent Storage
      • 5.1 Singleton Classes
      • 5.2 Shared Preferences
      • 5.3 Rooms
      • 5.4 Entities
      • 5.5 Data Access Objects
      • 5.6 Databases
    • 5.5 Concurrency
      • 5.5.1 Coroutines
      • 5.5.2 Implementation of Coroutines
      • 5.5.3 Coroutines with Networking Calls
    • 6. Networking and 3rd Party libraries
      • 6.1 HTTP Overview
      • 6.2 3rd Party Libraries
      • 6.3 JSON and Moshi
      • 6.4 Retrofit
      • 6.5 Summary
    • 7. MVVM Design Pattern
      • 7.1 Key Idea
      • 7.2 Implementation Ideas
    • 8. Flows
    • 9. The Art and Ontology of Software
    • 10. 🔥 Firebase
      • 10.1 Setting up Firebase
      • 10.2 Authentication
      • 10.3 Analytics
      • 10.4 Messaging
      • 10.5 Firestore
  • Additional Topics
    • Git and GitHub
    • Exporting to APK
  • Archive
    • Archived Native Android Textbook Pages
      • 1. Layouts and More Views
        • 1.1 File Structure and File Types
        • 1.2 Resource Files
        • 1.3 Button and Input Control
        • 1.4 ViewGroups
        • 1.5 Summary + A Note On Chapter 2 Topics
      • 2. RecyclerViews
        • 2.1 RecyclerViews
        • 2.2 RecyclerView Performance
        • 2.3 Implementation of a Recycler View
        • 2.4 Implementation with Input Controls
        • 2.5 Filtering RecyclerViews
        • 2.6 Recyclerview Demo
      • 3. ListViews and Searching
        • 3.1 ListView vs. RecyclerView
        • 3.2 ListView Performance
        • 3.3 Implementation of a ListView
        • 3.4 Searching in a List View
      • 4. Fragments
        • 4.1 What are Fragments?
        • 4.2 Lifecycle of a Fragment
        • 4.3 Integrating a Fragment into an Activity
        • 4.4 Sharing Data Between Fragments
        • 4.5 Fragment Slide Shows
      • 5. OkHttp
      • 6. Activity Lifecycle
      • 7. Implementation of Tab Layout
    • Fall 2024 Course Material
      • Lecture 1 & Exercise 1: Introduction to Android
      • Lecture 1.5: Beauty of Kotlin
      • Lecture 2 & HW 2: Modifiers, Lazylists and Reactive UI
      • Lecture 3 & HW 3: Animations, Intents and Manifest
      • Lecture 4 & HW 4: Coroutines & Navigation
      • Lecture 5 & HW 5: Persistent Storage, Networking, and JSON Parsing
      • Lecture 6 & HW 6: MVVM, Flows
      • Bonus Lectures & Bonus HW
      • Bonus Lecture: Industry Practice
    • Spring 2024 Course Material
      • Lecture 1 & Exercise 1: Introduction to Android
      • Lecture 4 & HW 4: LazyLists
      • Lecture 6 & HW 6: Networking, Data, and Persistent Storage
    • Spring 2020 Course Material
      • Week 1: Intro to the Editor
      • Week 2: Views and Layouts
      • Week 3: Intent and Manifest
      • Week 4: ListView and RecyclerView
      • Week 5: Fragments
      • Week 6: Networking
    • Spring 2021 Lecture & HW 8: Networking & 3rd Party APIs
    • HackOurCampus Workshop
Powered by GitBook
On this page
  • Part 1: Hilt Setup
  • Step 1: Add necessary gradle dependencies
  • Step 2: Make a MainApplication class
  • Step 3: Annotate and Inject
  • Part 2: Connecting the View and ViewModel
  • 1. Set up the EateryRepository and EateryViewModel
  • 2. Set up the View
  • Part3: Implement the nowOpen filter
  • Congrats, you've finished demo 4!

Was this helpful?

  1. SP25 Course Material
  2. Week 4 | MVVM and Flows

Demo: Eatery Card 2

EateryCard Part 2

PreviousRelevant LinksNextA4: Chat of a Lifetime

Last updated 1 month ago

Was this helpful?

In this week's demo, you'll be practicing the MVVM architecture by displaying data from a ViewModel in the View and implementing some filtering logic in the ViewModel of a pseudo-Eatery app!

Download the starter code:

Part 1: Hilt Setup

(Hilt works together with Navigation to inject dependencies into the NavGraph, so you'll also have to set up Navigation whenever you're using Hilt. We've already done that for you in the starter code though!)

Step 1: Add necessary gradle dependencies

  1. Add the hilt-android-gradle-plugin plugin to your project's root build.gradle file and hit Sync Now on the blue banner that pops up:

build.gradle.kts (Project: demo4_starter2)
plugins {
  ...
  id("com.google.dagger.hilt.android") version "2.51.1" apply false
}
  1. Apply the Gradle plugin and add these dependencies in your app/build.gradle file and Sync Now:

    1. (Some of the dependencies in the following code block depend on each other, so any red highlighting should be resolved during the sync.)

build.gradle.kts (Module: app)
plugins {
  ...
  id("kotlin-kapt")
  id("com.google.dagger.hilt.android")
}

dependencies {
  ...
  implementation("com.google.dagger:hilt-android:2.51.1")
  implementation("androidx.hilt:hilt-navigation-compose:1.1.0")
  kapt("com.google.dagger:hilt-android-compiler:2.51.1")
}

// copy this entire block
kapt {
  correctErrorTypes = true
}

Step 2: Make a MainApplication class

In the same directory as MainActivity.kt, create a MainApplication.kt file and add the following code to it. Do imports as needed!

@HiltAndroidApp
class MainApplication : Application() {}

Add the following to your AndroidManifest.xml to define your custom Application class. This is required for Hilt to work properly:

<application
        android:name="com.example.demo4_starter2.MainApplication"
        ...

Step 3: Annotate and Inject

Now that Hilt is set up, it can provide dependencies to other Android classes with the @AndroidEntryPoint annotation. For our purposes, we want to annotate the MainActivity (in MainActivity.kt) to let Hilt know that it is working on MainActivity.

//example
@AndroidEntryPoint
class MainActivity : ComponentActivity() {...}

You would usually need to Inject into ViewModels and Repositories as well, but we've already done that for you in the starter code. Take note of the syntax as you're working through the demo!

Part 2: Connecting the View and ViewModel

1. Set up the EateryRepository and EateryViewModel

Now that Hilt is set up, go into EateryRepository.kt and uncomment the commented starter code. Import @Inject.

Then, paste the following code into the EateryViewModel.kt file.

EateryViewModel Starter Code
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject

@HiltViewModel
class EateryViewModel @Inject constructor(
    private val eateryRepository: EateryRepository
) : ViewModel(){
    data class EateryUIState(
        val locationFilter: Location = Location.ALL,
        //TODO: add the nowOpen filter to this data class
        val allEateries: List<Eatery> = emptyList()
    ) {
        val filteredEateries: List<Eatery>
            get() = allEateries.filter { eatery ->
                //TODO: implement the nowOpen filter-logic here!
                if (locationFilter == Location.ALL) true else (eatery.location == locationFilter)
            }
    }

    private val _uiState = mutableStateOf(EateryUIState())
    val uiState: State<EateryUIState> = _uiState

    fun onLocationSelected(location: Location) {
        _uiState.value = _uiState.value.copy(locationFilter = location)
    }

    //TODO: implement the nowOpen filter function

    init {
        loadEateries()
    }

    private fun loadEateries() {
        _uiState.value = _uiState.value.copy(
            allEateries = eateryRepository.getAllEateries()
        )
    }
}

That's all for now, we'll come back to these files!

Keep uncommenting code as you work, a lot was commented to start because a lot of it depends on Hilt.

2. Set up the View

If you look over the starter code in EateryScreen, you'll notice we have two composables.

  • EateryScreen is our main UI screen for this project.

  • EateryScreenContent will contain the FilterBar and the EateryCards we're displaying.

    • Notice that EateryScreenContent takes a few functions as arguments. Since the viewModel will provide these functions, separating our content into two nested composables is just a layer of abstraction that helps us keep our top-level function calls cleaner. This will make more sense as we get further through the demo.

Lets start at the EateryScreenContent!

EateryScreenContent

(Uncomment the parameter uiState and import EateryUIState before you start!)

  1. Make a LazyColumn that displays EateryCards for all of the filteredEateries in the EateryRepository.

    1. Hint: The type of the uiState is EateryUIState. It has a field called filteredEateries. Try using that, and review the LazyColumn syntax from previous demos/assignments if needed!

    2. Hint: you can use verticalArrangement = Arrangement.spacedBy(10.dp) as an argument in LazyColumn to achieve spacing between each card.

Uncomment the EateryScreenContentPreview() and check to see that the EateryCards appear correctly.

Now we can finish the EateryScreen composable!

EateryScreen

We want to be able to call the EateryScreenContent composable with real data to actually display our EateryCards. For that, we need to pass two parameters into EateryScreenContent: uiState and onLocationSelected.

  1. We need to get the uiState from the ViewModel.

    1. Pass the EateryViewModel into the EateryScreen as an argument. We also need to specify that the EateryViewModel is a hiltViewModel. This should look something like

viewModel: EateryViewModel = hiltViewModel()
  1. Now we want to access the data we need to display, which is an instance of the EateryUIState that is defined in the EateryViewModel.

val uiState by viewModel.uiState
  1. Finally, the EateryScreenContent needs to know what to do when a location filter is pressed. Follow the todo in the code to pass in a function that was defined in the EateryViewModel!

    1. Hint: Remember that we're using an instance of the EateryViewModel, viewModel, that's being passed in as an argument. So to use an EateryViewModel function, we need to call it from viewModel.someFunction()

    2. Hint: We need to pass in a function, but we don't have the necessary argument. This is a case where we must use a lambda. Surround your function call in curly braces and pass in 'it' as its argument.

  2. Since we haven't defined a function for onNowOpenSelected, go ahead and keep the original empty lambda {} from the starter code for that argument of EateryScreenContent.

Part3: Implement the nowOpen filter

To get a bit of experience working in the ViewModel, we'll have you create the nowOpen filter, which just returns all eateries with isOpen = true.

Follow the todos in EateryViewModel.kt and use the locationFilter as a model!

Extra hints:

  • Todo 1: nowOpenFilter has type Boolean (it's either active/clicked or inactive/not-clicked), and it's default value should be false (for inactive)

  • Todo 2:

    • .filter (from allEateries.filter()) runs each eatery through the series of conditionals in its body. If the final evaluation is true, then the eatery is included in filteredEateries, otherwise it's not included.

    • For the filtering conditional, there are two cases: either the nowOpenFilter is active (we only keep eateries whose isOpen field is true), or the nowOpenFilter is inactive and we can keep everything

      • Hint: when the nowOpenFilter is inactive, !nowOpenFilter is always true (we keep every eatery)

      • Add your filtering logic to the existing block using && (double ampersand)

  • Todo 3:

    • This is almost exactly the same as onLocationSelected Can you figure out what we're updating in .copy?

Now you can run your emulator and test that your 'Now Open' filter is working!

Congrats, you've finished demo 4!

112KB
demo4_starter2.zip
archive