Skip to main content

Command Palette

Search for a command to run...

Chapter 16 - Coding the SearchImageUseCase (Data Layer)

Published
•3 min read•View as Markdown

We have already discussed about the DTO classes and the mappers required for the Image Response API.

Please checkout chapter 6 to recap the code.

The ImageResultDto and ImageResponseDto data classes showcase our DTO. The DTO to Domain layer mapper functions produces the ImageResult objects that satisfy the SearchImageUseCase.

Now we come to the important part - API Service.

ImageAPI

package com.learning.artsnapapp.data.remote.api

import com.learning.artsnapapp.data.remote.dto.ImageResponseDto
import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.Query

interface ImageApi {

    @GET(".")
    suspend fun searchImages(
        @Query("key") apiKey:String,
        @Query("q") searchQuery: String,
    ) : Response<ImageResponseDto>
}

The ImageAPI interface defines the searchImages function which takes the user Query as the parameter. The second parameter is the API key used for Pixabay. The reader is encouraged to login to the Pixabay website and obtain the API Key.

The searchImages() function will now be implemented in the ArtRepositoryImpl class. We already have the code below from chapter 7. This class used the ArtDao object to fetch records from the Room DB. This class had three more functions that were not implemented. Now is the time to jump in and implement the searchImage() function. To implement the searchImage function we need to inject the ImageApi and the API key.

/**
 * Implementation of [ArtRepositoryInterface].
 *
 * This class acts as a single source of truth for both local (Room) and
 * remote (Pixabay API) data. It maps entities and DTOs to domain models.
 *
 * Dependencies are injected using Hilt:
 * - [ArtDao] for local persistence.
 * - [ImageApi] for remote API access.
 * - `@Named("apiKey")` String for API authentication.
 *
 * The [searchImage] function emits a [Resource] stream, which allows the
 * UI layer to react to loading, success, and error states.
 */

@Singleton
class ArtRepositoryImpl @Inject constructor(
    private val artDao: ArtDao,
    private val imageApi: ImageApi,
    @Named("apiKey") private val apiKey: String
):ArtRepositoryInterface {
    override fun getArt(): Flow<List<Art>> {

        //returns a stream (Flow) that emits a list of ArtEntity objects every time the ROOM DB table changes.
        val artStream = artDao.getAllArts()

       val artObjs = artStream.map { entityList -> entityList.map { it.toDomainModel() } }
        return artObjs
    }

    override fun insertArt(art: Art) {
        TODO("Not yet implemented")
    }

    override fun deleteArt(art: Art) {
        TODO("Not yet implemented")
    }

     override fun searchImage(imageString: String): Flow<Resource<List<ImageResult>>> = flow {
        emit(Resource.loading())

        try {
            val response = imageApi.searchImages(apiKey, imageString)

            if (response.isSuccessful) {
                val body = response.body()
                if (body != null) {
                    // Map the list of DTOs to domain models
                    val results = body.hits.map {it.toDomainModel()}
                    emit(Resource.success(results))
                } else {
                    emit(Resource.error("Empty response body"))
                }
            } else {
                emit(Resource.error("API error: ${response.message()}"))
            }

        } catch (e: Exception) {
            emit(Resource.error("Network error: ${e.message}"))
        }
    }

}

The ImageApi will be provided by Hilt. So we update the AppModule class present in the “di” package

Also The ArtUseCases will be updated with the SearchImageUseCase.

@Module
@InstallIn(SingletonComponent::class)
object AppModule {

    @Provides
    @Singleton
    fun provideDatabase(@ApplicationContext context: Context): ArtDatabase = Room.databaseBuilder(
        context,
        ArtDatabase::class.java,
        "art_db"
    ).build()

    @Provides
    fun provideArtDao(db: ArtDatabase) : ArtDao = db.artDao()

    @Provides
    @Singleton
    fun provideRetrofitApi():ImageApi = Retrofit.Builder()
        .baseUrl(BuildConfig.BASE_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .build().create(ImageApi::class.java)

    @Provides
    @Singleton
    @Named("apiKey")
    fun provideImageApiKey() : String = BuildConfig.PIXABAY_API_KEY


    // --- USE CASES ----------------------------------------------------------
    @Provides
    @Singleton
    fun provideArtUseCases(repository: ArtRepositoryInterface): ArtUseCases =
        ArtUseCases(
            getArtUseCase = GetArtUseCase(repository),
            searchImageUseCase = SearchImageUseCase(repository)
        )
}

/**
 *
 * - The Pixabay API key is stored locally (in local.properties) for security.
 * - The key is injected into BuildConfig at compile-time.
 * - Access it anywhere using BuildConfig.PIXABAY_API_KEY.
 * - Never commit local.properties to version control.
 */

The app level build.gradle.kts will be updated for the PIXABAY_API_KEY and BASE_URL. The actual PIXABAY_API_KEY will reside in the local.properties.

Ensure you cannot commit this local.properties to your GIT remote repository.

PIXABAY_API_KEY=12345678-abcdefg
 defaultConfig {
        applicationId = "com.learning.artsnapapp"
        minSdk = 29
        targetSdk = 36
        versionCode = 1
        versionName = "1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

            buildConfigField(
                "String",
                "PIXABAY_API_KEY",
                "\"${project.findProperty("PIXABAY_API_KEY") ?: ""}\""
            )
        buildConfigField(
            "String",
            "BASE_URL",
            "\"https://pixabay.com/api/\""
        )

    }

The data layer code is now complete for the SearchImageUseCase

In the next chapter we will update the ArtViewModel class followed by the new Composable for the SearchImageUseCase.

More from this blog

Architecting Android: The Right way

31 posts

Exploring Clean Architecture in Android with Kotlin and Jetpack Compose — simplifying design patterns, testing, and app structure for curious developers.