Chapter 16 - Coding the SearchImageUseCase (Data Layer)
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.