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

Android Assignment 05

Introduction to API Calls and Image Loading

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

  • Reuse your assignment4 code and copy it to assignment5 folder.
  • Create a new OMDb API Key here http://www.omdbapi.com/apikey.aspx
  • Replace your static Movie Data source with REST API Calls to OpenMovieDB
  • Use this get call for your search http://www.omdbapi.com/?apikey=YOURKEY&s=Matrix where you replace "YOURKEY" with your own key and "Matrix" with the search string input from R.id.searchMovieTitleEditText
  • Add a poster image to your search result list and saved movies list
  • Implement Glide to load the poster in your ViewHolder

2 Views

Movie Search

Home Activity

3 Espresso Test

Hint: The test is expecting a empty app state. If you have saved movies on app start, the test will fail.

package de.hdmstuttgart.movietracker

import android.view.View
import androidx.recyclerview.widget.RecyclerView
import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.closeSoftKeyboard
import androidx.test.espresso.action.ViewActions.replaceText
import androidx.test.espresso.assertion.ViewAssertions.matches
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.hasDescendant
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.rules.ActivityScenarioRule
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.junit.Rule
import org.junit.Test


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

    @Test
    fun assignment4Test() {
        onView(withId(R.id.searchBtn))
            .perform(click())

        onView(withId(R.id.searchMovieTitleEditText))
            .perform(replaceText("Matrix"), closeSoftKeyboard())

        onView(withId(R.id.searchMovieTitleButton))
            .perform(click())

        try {
            Thread.sleep(1000)
        } catch (e: InterruptedException) {
            e.printStackTrace()
        }

        onView(withId(R.id.searchRecyclerView))
            .check(matches(atPosition(0, hasDescendant(withText("The Matrix")))))

        onView(withId(R.id.searchRecyclerView))
            .perform(
                RecyclerViewActions.actionOnItemAtPosition<MovieAdapter.ViewHolder>(
                    0,
                    click()
                )
            )

        onView(withId(R.id.homeRecyclerView))
            .check(matches(atPosition(0, hasDescendant(withText("The Matrix")))))

        activityScenarioRule.scenario.close()

        ActivityScenario.launch(MainActivity::class.java)

        try {
            Thread.sleep(1000)
        } catch (e: InterruptedException) {
            e.printStackTrace()
        }

        onView(withId(R.id.homeRecyclerView))
            .check(matches(atPosition(0, hasDescendant(withText("The Matrix")))))

        onView(withId(R.id.searchBtn))
            .perform(click())

        onView(withId(R.id.searchMovieTitleEditText))
            .perform(replaceText("her"), closeSoftKeyboard())

        onView(withId(R.id.searchMovieTitleButton))
            .perform(click())

        try {
            Thread.sleep(1000)
        } catch (e: InterruptedException) {
            e.printStackTrace()
        }

        onView(withId(R.id.searchRecyclerView))
            .check(matches(atPosition(0, hasDescendant(withText("Her")))))

        onView(withId(R.id.searchRecyclerView))
            .perform(
                RecyclerViewActions.actionOnItemAtPosition<MovieAdapter.ViewHolder>(
                    0,
                    click()
                )
            )

        onView(withId(R.id.homeRecyclerView))
            .check(matches(atPosition(1, hasDescendant(withText("Her")))))

        activityScenarioRule.scenario.close()

        ActivityScenario.launch(MainActivity::class.java)

        try {
            Thread.sleep(1000)
        } catch (e: InterruptedException) {
            e.printStackTrace()
        }

        onView(withId(R.id.homeRecyclerView))
            .check(matches(atPosition(0, hasDescendant(withText("The Matrix")))))

        onView(withId(R.id.homeRecyclerView))
            .check(matches(atPosition(1, hasDescendant(withText("Her")))))
    }

    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)
                }
            }
        }
    }
}

4 Gradle Dependencies

dependencies {
    implementation("androidx.core:core-ktx:1.12.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")
    implementation("androidx.room:room-runtime:2.5.2")
    implementation("androidx.room:room-ktx:2.5.2")
    implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
    ksp("androidx.room:room-compiler:2.5.2")
    implementation("com.github.bumptech.glide:glide:4.11.0")
    ksp("com.github.bumptech.glide:compiler:4.11.0")
    implementation("com.squareup.retrofit2:converter-gson:2.9.0")
    implementation("com.squareup.retrofit2:retrofit:2.9.0")
}