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

Android Assignment 02

Introduction to RecyclerView

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

  • Create a new Android Project in Android Studio and use your "assignment2" git folder as the project root folder.
    • Template: "Empty Views Activity"
    • Name: "MovieTracker"
    • Package name: "de.hdmstuttgart.movietracker"
    • Save location: Path to your assignment2 folder
    • Build configuration language: Kotlin DSL [Recommended]
    • Minimum SDK: API 26
  • Add RecyclerView Gradle dependency to your project implementation("androidx.recyclerview:recyclerview:1.3.1") https://developer.android.com/jetpack/androidx/releases/recyclerview
  • Add a vertical RecyclerView to your MainActivity and give it the following id: android:id="@+id/homeRecyclerView"
  • Create item views as described
  • When user clicks on a row, remove the row
  • Run espresso test to validate your implementation

2 Item Views

Use this android ids for each row:

title: @+id/title
year: @+id/year
actor: @+id/actor

Example Layout:

center

3 Row Data

Use this data to fill out your rows:

title year actor
Dr. No 1962 Sean Connery
From Russia with Love 1963 Sean Connery
Goldfinger 1964 Sean Connery
Thunderball 1965 Sean Connery
You Only Live Twice 1967 Sean Connery

4 Espresso Test

package de.hdmstuttgart.movietracker

import android.view.View
import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.Espresso
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.assertion.ViewAssertions
import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.core.internal.deps.guava.base.Preconditions
import androidx.test.espresso.matcher.BoundedMatcher
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.filters.LargeTest
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@LargeTest
@RunWith(androidx.test.ext.junit.runners.AndroidJUnit4::class)
class Assignment2Test {

    @get:Rule
    var activityScenarioRule = ActivityScenarioRule(
        MainActivity::class.java
    )

    @Test
    fun mainActivityTest() {
        Espresso.onView(withId(R.id.homeRecyclerView))
            .check(
                ViewAssertions.matches(
                    atPosition(
                        0,
                        ViewMatchers.hasDescendant(ViewMatchers.withText("Dr. No"))
                    )
                )
            )
        Espresso.onView(withId(R.id.homeRecyclerView))
            .check(
                ViewAssertions.matches(
                    atPosition(
                        1,
                        ViewMatchers.hasDescendant(ViewMatchers.withText("From Russia with Love"))
                    )
                )
            )
        Espresso.onView(withId(R.id.homeRecyclerView))
            .check(
                ViewAssertions.matches(
                    atPosition(
                        2,
                        ViewMatchers.hasDescendant(ViewMatchers.withText("Goldfinger"))
                    )
                )
            )
        Espresso.onView(withId(R.id.homeRecyclerView))
            .check(
                ViewAssertions.matches(
                    atPosition(
                        3,
                        ViewMatchers.hasDescendant(ViewMatchers.withText("Thunderball"))
                    )
                )
            )
        Espresso.onView(withId(R.id.homeRecyclerView))
            .check(
                ViewAssertions.matches(
                    atPosition(
                        4,
                        ViewMatchers.hasDescendant(ViewMatchers.withText("You Only Live Twice"))
                    )
                )
            )
        Espresso.onView(withId(R.id.homeRecyclerView))
            .perform(
                RecyclerViewActions.actionOnItemAtPosition<MovieAdapter.ViewHolder>(
                    0,
                    ViewActions.click()
                )
            )
        Espresso.onView(withId(R.id.homeRecyclerView))
            .check(
                ViewAssertions.matches(
                    atPosition(
                        0,
                        ViewMatchers.hasDescendant(ViewMatchers.withText("From Russia with Love"))
                    )
                )
            )
        Espresso.onView(withId(R.id.homeRecyclerView))
            .perform(
                RecyclerViewActions.actionOnItemAtPosition<MovieAdapter.ViewHolder>(
                    0,
                    ViewActions.click()
                )
            )
        Espresso.onView(withId(R.id.homeRecyclerView))
            .check(
                ViewAssertions.matches(
                    atPosition(
                        0,
                        ViewMatchers.hasDescendant(ViewMatchers.withText("Goldfinger"))
                    )
                )
            )
    }

    companion object {
        fun atPosition(
            position: Int,
            itemMatcher: Matcher<View?>
        ): BoundedMatcher<View?, RecyclerView> {
            Preconditions.checkNotNull(itemMatcher)
            return object : BoundedMatcher<View?, RecyclerView>(RecyclerView::class.java) {
                override fun describeTo(description: Description) {
                    description.appendText("has item at position $position: ")
                    itemMatcher.describeTo(description)
                }

                override fun matchesSafely(view: RecyclerView): Boolean {
                    val viewHolder = view.findViewHolderForAdapterPosition(position)
                        ?: // has no item on such position
                        return false
                    return itemMatcher.matches(viewHolder.itemView)
                }
            }
        }
    }
}

5 Gradle Dependencies

dependencies {
    implementation("androidx.core:core-ktx:1.9.0")
    implementation("androidx.appcompat:appcompat:1.6.1")
    implementation("com.google.android.material:material:1.9.0")
    implementation("androidx.constraintlayout:constraintlayout:2.1.4")
    implementation("androidx.recyclerview:recyclerview:1.3.1")
    implementation("androidx.test.espresso:espresso-contrib:3.5.1")
    testImplementation("junit:junit:4.13.2")
    androidTestImplementation("androidx.test.ext:junit:1.1.5")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
}