I've been struggling with MVVM pattern and android-architecture-components for last couple of months. In my last project although I did try to decouple some of my app logic but it ended with a lot of code mess. Every fragment did lots of work, from handling UI to handling network requests and ... In this new App I followed best practices for android app architecture and till now it's going well. But the thing is, I don't know how to handle network errors, and I don't get it how should I notify user if some network calls fail.
After searching and reading some blog posts I ended up with the following code (SafeApiCall and SafeApiResutl) functions to handle network requests in one place, but the thing is, All my Network Requests are done using retrofit and a NetworkDataSource class, Then I pass The NetworkDataSource and Dao to the RepositoryImpl class which is an implementation of my Repository Interface. then I pass the Repository to the viewModel, So ViewModel knows nothing about network or Dao or what so ever. So here is the problem, How can I notify user in case of any network errors ? I thought about creating a LiveData<> and pass errors to it in network layer, but in this case, Repository Must observe this, and also let's say create a LiveData in repository so viewModel observe that and so on ... But this is too much chaining, I dont like the idea of doing that. I also did take a look at the GoogleSamples Todo-MVVM-live-kotlin project, but honestly I didn't understand what is going on.
suspend fun <T : Any> safeApiCall(call: suspend () -> Response<BasicResponse<T>>, errorMessage: String): T? {
return when (val result = safeApiResult(call)) {
is NetworkResult.Success -> {
Timber.tag("safeApiCall").d("data is ${result.serverResponse.data}")
result.serverResponse.data
}
is NetworkResult.Error -> {
Timber.tag("SafeApiCall").e("$errorMessage & Exception - ${result.exception}")
null
}
else -> TODO()
}
}
private suspend fun <T : Any> safeApiResult(
call: suspend () -> Response<BasicResponse<T>>
): NetworkResult<T> {
return try {
val response = call.invoke()
Timber.tag("SafeApiResult")
.d("response code : ${response.code()}, server value : ${response.body()!!.status}, server message: ${response.body()!!.message}")
if (response.isSuccessful) {
return when (ServerResponseStatus.fromValue(response.body()!!.status)) {
ServerResponseStatus.SUCCESS -> NetworkResult.Success(response.body()!!)
ServerResponseStatus.FAILED -> TODO()
ServerResponseStatus.UNKNOWN -> TODO()
}
} else {
TODO()
}
} catch (exception: Exception) {
Timber.tag("SafeApiResultFailed").e(exception)
NetworkResult.Error(exception)
}
}
sealed class NetworkResult<out T : Any> {
data class Success<out T : Any>(val serverResponse: BasicResponse<out T>) : NetworkResult<T>()
data class Error(val exception: Exception) : NetworkResult<Nothing>()
}