Skip to main content

Command Palette

Search for a command to run...

Chapter 15 - Coding the SearchImageUseCase (Domain layer)

Published
4 min readView as Markdown

Before we start working on the same blueprint we used earlier for the GetArtUseCase, let us pause for a moment and depict the data flow in the form of a flowchart. This will help gauge the progress of the second use case - SearchImageUseCase. This use case revolves around fetching images from the Remote API after user types in a “search” query string inside the search window.

DATA FLOW DIAGRAM

  • Domain layer defines business logic and repository contracts.

  • Data layer implements actual API calls (Retrofit) and maps DTOs → Domain Models.

  • Hilt provides singletons of API, Repository, and UseCases for dependency injection.

  • ViewModel consumes UseCases and exposes state to UI via StateFlow.

  • Composables observe ViewModel state and render UI accordingly.

  • Clean flow: UI → ViewModel → UseCase → Repository → API → Mapper → Domain Model.

Coding the Domain Model

The ImageResult class

package com.learning.artsnapapp.domain.model

data class ImageResult (
    val id: Int,
    val previewURL: String,
    val largeImageURL: String
)

The Repository Interface

Let us check the repository interface that we used for our first use case.

package com.learning.artsnapapp.domain.repository

import com.learning.artsnapapp.domain.model.Art
import com.learning.artsnapapp.domain.model.ImageResult
import com.learning.artsnapapp.util.Resource
import kotlinx.coroutines.flow.Flow

interface ArtRepositoryInterface {

    suspend fun insertArt(art: Art)
    suspend fun deleteArt(art: Art)
    fun getArt(): Flow<List<Art>>
    fun searchImage(imageString: String): Flow<Resource<List<ImageResult>>>
}

/**
 The domain layer defines what data is needed, not how it is fetched.
 The data layer will implement this interface.
 * */

We already defined the searchImage function that will return Flow of the List of ImageResult objects. This is similar to the getArt function returning the List of Art objects that we have already worked on in the previous chapters.

We are walking on the already trodden path and things will be simpler to understand this time around. The power of clarity that the Clean Architecture provides will be seen here. We have already worked on the Hilt dependency injection and hence this time we will re-use what is already done to build this SearchImageUseCase functionality quickly.

Here is the use case code

package com.learning.artsnapapp.domain.usecase

import com.learning.artsnapapp.domain.model.ImageResult
import com.learning.artsnapapp.domain.repository.ArtRepositoryInterface
import com.learning.artsnapapp.util.Resource
import kotlinx.coroutines.flow.Flow

class SearchImageUseCase(private val imageRepository: ArtRepositoryInterface)
{

    operator fun invoke(searchQuery:String) : Flow<Resource<List<ImageResult>>> {
        return imageRepository.searchImage(searchQuery)
    }

}

//Purpose: Search for artwork images online using the external remote API.
//Domain logic: Wrap API responses into Resource and map DTO → domain model.

We need to understand the Resource utility class. There is a separate package “util” to handle utility classes.

The RESOURCE class

package com.learning.artsnapapp.util

sealed class Resource<T>(
    val data: T? = null,
    val message: String? = null
) {
    class Success<T>(data: T) : Resource<T>(data)
    class Error<T>(message: String, data: T? = null) : Resource<T>(data, message)
    class Loading<T>(data: T? = null) : Resource<T>(data)

    companion object {
        fun <T> success(data: T): Resource<T> = Success(data)
        fun <T> error(msg: String, data: T? = null): Resource<T> = Error(msg, data)
        fun <T> loading(data: T? = null): Resource<T> = Loading(data)
    }
}

Resource - A wrapper class that represents Loading, Success, or Error for any data type. To make UI and ViewModel communication cleaner and handle async states elegantly.

  • Resource is not about data, it’s about data state.

  • It helps ViewModels and Composables talk in the same structured language

  • Benefits - Predictable state management, no null-check hell, smoother Compose UI updates.

KOTLIN SEALED CLASS AND BENEFITS

A sealed class in Kotlin is a restricted class hierarchy — it allows you to define a fixed set of subclasses that represent different variants of a concept.

You can think of it like a closed family of related classes. Only the types you declare inside it are allowed to exist — nothing else can extend it.

Why Use Sealed Classes?

  • Type-safe state management You can handle different outcomes (like Success, Error, Loading) clearly.

  • No magic numbers or enums Instead of using strings or codes (“STATUS=200”), each state is an actual class.

  • Smart compiler help If you forget to handle a state in a when block, the compiler warns you

In the next chapter, we will code the data layer 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.