Augusto Carmo
Augusto Carmo

Reputation: 4964

What would you consider the correct way to implement Use Cases/Repositories that return a Flow (Single Source of Truth + remote fetching)

Suppose I have the following UseCase to fetch users as a flow

class FetchUsersFlowUseCase(
    private val userRepository: UserRepository
) {

    operator fun invoke(): Flow<List<User>> {
        return userRepository.fetchUsersFlow()
    }
}

And a Repository with a function that fetches users as a Flow. When that function is called, an attempt to sync the local database (which is the Single Source of Truth) is also executed.

class UserRepositoryImpl(
    private val localUserDataSource: LocalUserDatasource,
    private val remoteUserDataSource: RemoteUserDatasource,
    private val coroutineScope: CoroutineScope
) : UserRepository {

    override fun fetchUsersFlow(): Flow<List<User>> {
        coroutineScope.launch {
            val remoteUsersResult = remoteUserDataSource.fetchUsers()
            if (remoteUsersResult.isSuccess) {
                // Success: update users locally
            } else {
                // What to do? 
                // How to correctly notify who observes that something wrong happened?
            }
        }

        return localUserDataSource.fetchUsersFlow()
    }
}

But if something unexpected happens (example: no Internet connection), how can I correctly notify who observes the Flow?

Upvotes: 0

Views: 242

Answers (1)

user8681
user8681

Reputation:

Wrap your result in an object, so consumers can determine success:

sealed interface Result {
  object Error: Result
  data class Success(val value: List<User>)
}

and

    override fun fetchUsersFlow(): Flow<Result> {
        coroutineScope.launch {
            val remoteUsersResult = remoteUserDataSource.fetchUsers()
            if (remoteUsersResult.isSuccess) {
                yourFlow.emit(Result.Success(remoteUsersResult))
            } else {
                yourFlow.emit(Result.Error)
            }
        }

        return localUserDataSource.fetchUsersFlow()
    }

Then on the consumer side, you can

when(valueFromFlow) {
  Result.Error -> // handle it
  is Result.Success -> it.value // is your List<User>
}

You can add even more Result implementations to signal other cases, or make Error a data class too and give it fields holding details.

Upvotes: 0

Related Questions