Error Handling in Clean Architecture using Flow and Jetpack Compose
How to handle errors nicely in Clean Architecture using Kotlin Flows and Jetpack Compose?

We should avoid showing general error messages to the user every time any error occurs, at the same time we can not handle all different sorts of error codes and types but rather we should find a middle way to handle errors smartly to show those errors which occur often and are critical.
While handling errors the following are important questions to answer:
- What errors should we handle?
- How to intercept and propagate errors from one layer to the other layer of the Architecture?
- How to encapsulate errors information belonging to a specific layer and only pass relevant information to the other layer?
- How to give the user the option to retry from an error state?
In the story we will find answers to these questions and will also see further how to handle Loading
, Error
and Loaded
states inside our composable.
In the end the story will contain link to the sample github repository.
Prerequisites
You have understanding of
- Clean Architecture
- Kotlin Flows
- Jetpack Compose
Architecture
Summarising layers and their responsibilities
- UI — View Model, Composable, Layout etc
- Domain — use-cases, entities : This is optional depending upon the project and need.
- Data — Local Data Source, Remote Data Source, Repositories, another other source of data.
If we split Data Layer
further it consists of following
- Remote Data Source— Network Api, 3rd party callbacks e.g Firebase RealtimeDatabase callbacks etc
- Local Data Source — Room Database, Data Store etc
- Repository — Centralises data reads/writes/updates and uses Remote Data and Local Data Sources
Page Content
To overview what’s included in the page
- What errors to handle?
- Defining errors for Data Layer (
ErrorType
) - Propagate error from Data Layer
- Intercept and transform Api Errors/Exceptions into
ErrorType
- Receiving error inside Presentation/UI Layer
- Showing error message inside Presentation/UI Layer
- Handling dynamic error message inside Composable
- Github repo
What errors to handle?
Generally errors can happen in any layer but in the story we will look into errors happening inside the Data Layer e.g while fetching data from the Network, reading data from the Database etc such errors are like Network Connection issues, Server Unavailable, Resource Not Found, Database read failed etc.
Errors happening inside the Data Layer must not be directly available inside the UI Layer. e.g UI Layer must not know about IOException
,404
or any other type of HttpException
or error code. This information belongs to the Data Layer, doing so will overload UI Layer with unncessary logic of identifying and categorising Data Layers errors and converting them into UI specific information.
We must intercept errors inside the Data Layer and propagate relevant information to the UI Layer.
Defining errors for Data Layer ( ErrorType )
As mentioned before we should not expose specific error types and error codes e.g 503, 404 etc to the upper layers.
This information specifically belongs to the Data Layer so should be encapsulated inside the Data Layer.
In order to achieve that we will create a sealed class ErrorType
which will represent an error. We will derive specific error types from it, like below we are deriving Api
class from ErrorType
which contains Api related ErrorTypes
.
Having
Error
postfix withApi
likeApiError
is a personal choice, I would suggest not to write because when we will use it in code, it will have unnecessaryError
text likeErrorType.ApiError.
whereErrorType.Api.
is enough and self explanatory.
Let’s look at the ErrorType
sealed class.
There are many errors which can occur, the list can be seen here but we are taking the most relevant here, you can add the one you want.
The sealed class
ErrorType
is extendable to other categories of errors such as Database errors, where you will derive another class fromErrorType
and will define specific database errors.
Propagate Error from Data Layer
We have created ErrorType
class which knows about particular error but how to propagate error happening inside Remote Data source (Data Layer) to the other upper layers and eventually to the UI Layer?
In order to achieve that and also be able to handle any type of resource we will create Resource<T>
sealed class which has two derived data classes Success
and Error
.
Return type from repository will be Flow<Resource<List<News>>>
Success
data class will hold data of typeT
— in our exampleT
will beList<News>
Error
data class will hold error of typeErrorType
, holding error related information
ErrorType
we created in our previous step.
Resource<T>
class definition:
Intercept and transform Api Errors/Exceptions Into ErrorType
When an Error
or Exception
occurs we have to intercept it and transform it into ErrorType
.
There are different use-cases where Error
or Exception
can happen, we will see two of them.
- Case 1: Reading data from third party callbacks we have either
successCallback
orerrorCallback
e.g reading fromFirebase
RealtimeDatabase
is the example we will use. - Case 2: Regular Api calls where we fetch data over the Network using API. We will use
BooksApi
to fetch list of Books
Case1: Firebase Error Callback : When Firebase
RealtimeDatabase
respond with error/canceled callback, it provides an error via DatabaseError
class which we will use to transform it into ErrorType
. We will write an extension method over DatabaseError
which will convert DatabaseError
into ErrorType
as below.
In Firebase onCanceled
callback we will use this extension function to transform DatabaseError
error into ErrorType
and eventually return Resource.Error
passing the ErrorType
as below code.
If you want to read about: How to convert
Firebase
RealtimeDatabase
callbacks into KotlinFlows
, you can read my other story here.
Case 2: Fetching data from API: Fetching data via API is a common use-case. We will fetch a list of books using API.
Let’s look at the Book
Model, for the sake of example Book
Model only has title
as below.
BooksRemoteDataSourceImpl
implements the behaviour to fetch a list of Books from API, for this example we will fake the Books
Api response emitting
directly inside BooksRemoteDataSourceImpl
using Kotlin Flows
.
In reality we fetch list of Books from Api using Retrofit
or any other framework, so in reality the code looks like below
class BooksRemoteDataSourceImpl: BooksRemoteDataSource {
override fun fetchBooks() = flow {
emit(retrofitBookApi.fetchBooks())
}
}
We will fake Error
or Exception
case to either return a list of Books or throw Exception
using random boolean, updated BooksRemoteDataSourceImpl
looks like below.
We can see the source of Error
/ Exception
is the Remote Data component of Data Layer. In BooksRepository
we will transform such Exception
into ErrorType
so that it can be made available eventually to presentation/UI Layer.
We are catching exceptions occurring inside booksRemoteDataSource.fetchBooks()
using catch
block of Flow
and transforming Throwable
into ErrorType
.
catch
block is providing a Throwable
so extension method to convert Throwable
to ErrorType
looks like below.
I am not using any dependency injection for the sample project/examples But in reality we must use Dependency Injector to resolve dependencies.
Receiving Error inside Presentation/UI Layer
Recap: Error
/Exception
occurred inside the Network BooksRemoteDataSource
is intercepted inside NewsRepository
and is transformed into ErrorType
to propagate it to the presentation Layer using Resource<T>
.
In the UI Layer inside ViewModel we will collect the Flow
and check if provided Resource
is of kind Success
or Error
and if it its of Error
we would like to show corresponding error.
Let’s see BooksViewModel
code.
Showing Error Message inside Presentation Layer
We have successfully propagated ErrorType
to the presentation/UI Layer but how to show the string messages specific for each ErrorType
?
We will answer this question below, But let’s first see how UiState must be handled in composable.
A Comosable can have three states
- Loading — When data is being fetched from backend
- Loaded — When data is fetched successfully and displayed on UI
- Error — When data fetching failed due to any error and we had no data to show on UI.
Let’s create a sealed class UiState
which will hold these three states.
Updating BooksViewModel
to support UiState
.
BooksViewModel
is exposingStateFlow
forUiState<List<Books>>
for the composable and assigningUiState.Loading
initially because we want to showLoadingView
in composable while data is being fetched- Flow
collect
operator block is checkingResource
to eitherSuccess
orError
and assigningUiState.Loaded
orUiState.Error(<errormessage>)
respectively. - Currently we are passing general error message for the
Error
state, we will extend/update it below to handle dynamic messages perErrorType
Just to quickly look into Composable
implementation as below.
Composable
is collecting UiState
from viewModel and using when
to show corresponding state.
Handling dynamic Error Messages inside Composable
In the previous code example we are passing a hard coded string as an error message for UiState.Error
data state, which we don’t want.
We want to show reasonable information based on each ErrorType
received from Data Layer
.
While doing that practical approach will be that we want to use a string resource against each ErrorType
to support multiple languages.
To achieve that let’s create a ErrorText
class which will hold string
resource id
.
StringResource
derived class is taking string resource id and is marked as@StringRes
ErrorText
exposes aComposable
functionasString()
which convertsresourceId
into string text. — This will be used inside composable to show text insideText
composable
Updating UiState
sealed class to take ErrorText
parameter in case of Error
as below.
We need to write a converter/mapper which will map ErrorType
to ErrorText
as below.
In future if you have to add more ErrorTypes
you can easily extend it and using when
will make it easier as it will complain about missing cases.
Now changing insideBooksViewModel
to use the converter to convert ErrorType
to ErrorText
while assigning it to the StateFlow
.
The last step: We have to update ErrorView
composable to use asString
composable function to display error text showing in code below.
This way we will display meaningful and relevant information to the user based on which Error occurred in our Data Layer.
You can extend
ErrorType
andErrorText
for any type of errors without changing implementation details.
That’s it for now, I hope it was helpful. looking forward to any suggestions/improvements in the comments :)