Error Handling in Clean Architecture using Flow and Jetpack Compose

Saqib
Level Up Coding
Published in
8 min readJul 10, 2023

--

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

Image Source: Android Jetpack official page

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 with Api like ApiError is a personal choice, I would suggest not to write because when we will use it in code, it will have unnecessary Error text like ErrorType.ApiError. where ErrorType.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 from ErrorType 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 type T — in our example T will be List<News>
  • Error data class will hold error of type ErrorType , 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 or errorCallback e.g reading from Firebase 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 Kotlin Flows, 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 exposing StateFlow for UiState<List<Books>> for the composable and assigning UiState.Loading initially because we want to show LoadingView in composable while data is being fetched
  • Flow collect operator block is checking Resource to either Success or Error and assigning UiState.Loaded or UiState.Error(<errormessage>) respectively.
  • Currently we are passing general error message for the Error state, we will extend/update it below to handle dynamic messages per ErrorType

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 a Composable function asString() which converts resourceId into string text. — This will be used inside composable to show text inside Text 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 and ErrorText 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 :)

Sources

— — — — — — — — — — — — — — — — — — — — — — —

Remember to follow and 👏 if you liked it :)

GitHub | LinkedIn | Twitter

--

--

Senior Mobile Engineer (Android & iOS) , Berlin | Sharing my development experience