Demo: Eatery Card 2
EateryCard Part 2
Last updated
Was this helpful?
EateryCard Part 2
Last updated
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:
(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!)
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:
Apply the Gradle plugin and add these dependencies in your app/build.gradle
file and Sync Now:
(Some of the dependencies in the following code block depend on each other, so any red highlighting should be resolved during the sync.)
In the same directory as MainActivity.kt, create a MainApplication.kt file and add the following code to it. Do imports as needed!
Add the following to your AndroidManifest.xml to define your custom Application class. This is required for Hilt to work properly:
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.
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!
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.
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.
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!
(Uncomment the parameter uiState and import EateryUIState before you start!)
Make a LazyColumn that displays EateryCards for all of the filteredEateries in the EateryRepository.
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!
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!
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.
We need to get the uiState from the ViewModel.
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
Now we want to access the data we need to display, which is an instance of the EateryUIState that is defined in the EateryViewModel.
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!
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()
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.
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.
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!