Open menu with table of contents Android Assignment 03
Logo of Stuttgart Media University for light theme Logo of Stuttgart Media University for dark theme
Android Development

Android Assignment 03

Introduction to Navigation

Stuttgart Media University

Please conduct the following tasks alone. For implementation details you can refer to the lecture slides or the Android developer website. Please do not hesitate to ask me or the tutor if you have any questions.

1 Tasks

  • Copy the starter project assignmentCode/assignment3 into the assignment3 folder of your Git repository. The starter is the solution of assignment 2, restructured into packages, plus a NavHost with two screens.
  • Goal: use the Navigation component (NavHostController, NavHost) to navigate between the home screen and a search screen.
  • Home screen (ui/home/HomeScreen.kt, already implemented): shows a button with the test tag openSearchButton above the list of saved movies. The list is empty on app start.
  • Search screen (ui/search/SearchScreen.kt): a TextField (test tag textField), a Button (test tag searchButton) and a MovieList with the search results.
  • Implement the three TODOs of the starter:
    1. SearchScreen: when the search button is clicked, show all movies from MovieData.bondMovies whose title contains the entered text (ignore case). Example: the search string "uss" shows only "From Russia with Love". Results of the previous search must disappear.
    2. MainActivity: show the SearchScreen for the search route. When a result is clicked, add the movie to savedMovies and navigate back to the home screen. Saved movies are shown in the order they were clicked (first clicked on top).
    3. MainActivity: when a saved movie is clicked on the home screen, remove it from savedMovies.
  • Run the UI test to validate your implementation.

2 The starter project

app/src/main/java/de/hdmstuttgart/movietracker/
├── MainActivity.kt             Routes, NavHost and the hoisted state "savedMovies"   <- TODO
├── model/Movie.kt              data class Movie(title, year, actor)
├── model/MovieData.kt          the five Bond movies as static data source
├── ui/home/HomeScreen.kt       button + MovieList of the saved movies
├── ui/search/SearchScreen.kt   TextField, Button, MovieList of the search results   <- TODO
└── ui/composables/             MovieList and MovieItem from assignment 2

2.1 Hint

  • The list of saved movies is created in MovieTrackerNavHost with remember { mutableStateListOf<Movie>() } and passed down to the screens as parameter (state hoisting). This way the list survives when you navigate between the two screens.
  • navController.navigate(Routes.SEARCH) opens the search screen, navController.popBackStack() closes it again.
  • Every screen gets the data and the click handlers as parameters, the screens do not know the NavController.
  • See the lecture "Android Navigation".

3 Screens

3.1 Home screen

On app start:

center 30%

With one saved movie:

center 30%

With multiple saved movies:

center 30%

3.2 Search screen

When opened:

center 30%

With search results:

center 30%

4 UI test

The test is part of the starter (Assignment3Test.kt). It saves two movies, checks their order on the home screen and removes the first one again:

package de.hdmstuttgart.movietracker

import androidx.compose.ui.test.assertTextContains
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onAllNodesWithTag
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTextInput
import org.junit.Rule
import org.junit.Test

class Assignment3Test {

    @get:Rule
    val composeTestRule = createAndroidComposeRule<MainActivity>()

    @Test
    fun myTest() {
        // Search "Gold", save the only result and check the home screen
        composeTestRule.onNodeWithTag("openSearchButton").performClick()
        composeTestRule.onNodeWithTag("textField").performTextInput("Gold")
        composeTestRule.onNodeWithTag("searchButton").performClick()
        composeTestRule.onAllNodesWithTag("movieItem")[0].assertTextContains("Goldfinger")
        composeTestRule.onAllNodesWithTag("movieItem")[0].performClick()
        composeTestRule.onAllNodesWithTag("movieItem")[0].assertTextContains("Goldfinger")

        // Save a second movie, it has to appear below the first one
        composeTestRule.onNodeWithTag("openSearchButton").performClick()
        composeTestRule.onNodeWithTag("textField").performTextInput("From Russia with Love")
        composeTestRule.onNodeWithTag("searchButton").performClick()
        composeTestRule.onAllNodesWithTag("movieItem")[0].performClick()
        composeTestRule.onAllNodesWithTag("movieItem")[0].assertTextContains("Goldfinger")
        composeTestRule.onAllNodesWithTag("movieItem")[1].assertTextContains("From Russia with Love")

        // Clicking a saved movie removes it
        composeTestRule.onAllNodesWithTag("movieItem")[0].performClick()
        composeTestRule.onAllNodesWithTag("movieItem")[0].assertTextContains("From Russia with Love")
    }
}

5 Gradle dependencies

The Navigation component is already part of the starter. These are the relevant lines, in case you want to add it to your own project later:

gradle/libs.versions.toml:

[versions]
navigationCompose = "2.9.8"

[libraries]
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" }

Module app/build.gradle.kts:

dependencies {
    implementation(libs.androidx.navigation.compose)
    // ...
}

Submission

  • Push the project to the assignment3 folder of your Git repository (assignment3/app/src/main/...).
  • For grading, your Assignment3Test.kt is replaced by the reference version and executed on a fresh emulator.