NetworkBoundResource helper class without Room - android

When I tried to implements the NetworkBoundResource and Resource helper class for the Room Db and Retrofit, it works perfect. However, I need to implement the Search Result from RESTful using Retrofit only without Room. The Resources class is good and I dont need to change it. What I want to do is try to remove db source inside this class.
public abstract class NetworkBoundResource<ResultType, RequestType> {
private final AppExecutors appExecutors;
private final MediatorLiveData<Resource<ResultType>> result = new MediatorLiveData<>();
#MainThread
public NetworkBoundResource(AppExecutors appExecutors) {
this.appExecutors = appExecutors;
result.setValue(Resource.loading(null));
LiveData<ResultType> dbSource = loadFromDb();
result.addSource(dbSource, data -> {
result.removeSource(dbSource);
if (shouldFetch(data)) {
fetchFromNetwork(dbSource);
} else {
result.addSource(dbSource, newData -> setValue(Resource.success(newData)));
}
});
}
#MainThread
private void setValue(Resource<ResultType> newValue) {
if (!Objects.equals(result.getValue(), newValue)) {
result.setValue(newValue);
}
}
private void fetchFromNetwork(final LiveData<ResultType> dbSource) {
LiveData<ApiResponse<RequestType>> apiResponse = createCall();
// we re-attach dbSource as a new source, it will dispatch its latest value quickly
result.addSource(dbSource, newData -> setValue(Resource.loading(newData)));
result.addSource(apiResponse, response -> {
result.removeSource(apiResponse);
result.removeSource(dbSource);
//noinspection ConstantConditions
if (response.isSuccessful()) {
appExecutors.diskIO().execute(() -> {
saveCallResult(processResponse(response));
appExecutors.mainThread().execute(() ->
// we specially request a new live data,
// otherwise we will get immediately last cached value,
// which may not be updated with latest results received from network.
result.addSource(loadFromDb(),
newData -> setValue(Resource.success(newData)))
);
});
} else {
onFetchFailed();
result.addSource(dbSource,
newData -> setValue(Resource.error(response.errorMessage, newData)));
}
});
}
protected void onFetchFailed() {
}
public LiveData<Resource<ResultType>> asLiveData() {
return result;
}
#WorkerThread
protected RequestType processResponse(ApiResponse<RequestType> response) {
return response.body;
}
#WorkerThread
protected abstract void saveCallResult(#NonNull RequestType item);
#MainThread
protected abstract boolean shouldFetch(#Nullable ResultType data);
#NonNull
#MainThread
protected abstract LiveData<ResultType> loadFromDb();
#NonNull
#MainThread
protected abstract LiveData<ApiResponse<RequestType>> createCall();
}

The problem is that any loaded data have to go through the database first, then loading it from the database to the UI, as NetworkBoundResource does. Consequently, What I did is to decouple the persistent database and create a temporary field to load from.
For example if I wanted to edit the original search method, I would suggest:
public LiveData<Resource<List<Repo>>> search(String query) {
return new NetworkBoundResource<List<Repo>, RepoSearchResponse>(appExecutors) {
// Temp ResultType
private List<Repo> resultsDb;
#Override
protected void saveCallResult(#NonNull RepoSearchResponse item) {
// if you don't care about order
resultsDb = item.getItems();
}
#Override
protected boolean shouldFetch(#Nullable List<Repo> data) {
// always fetch.
return true;
}
#NonNull
#Override
protected LiveData<List<Repo>> loadFromDb() {
if (resultsDb == null) {
return AbsentLiveData.create();
}else {
return new LiveData<List<Repo>>() {
#Override
protected void onActive() {
super.onActive();
setValue(resultsDb);
}
};
}
}
#NonNull
#Override
protected LiveData<ApiResponse<RepoSearchResponse>> createCall() {
return githubService.searchRepos(query);
}
#Override
protected RepoSearchResponse processResponse(ApiResponse<RepoSearchResponse> response) {
RepoSearchResponse body = response.body;
if (body != null) {
body.setNextPage(response.getNextPage());
}
return body;
}
}.asLiveData();
}
I ran it and it works.
Edit:
I made another simpler class to handle that (There is another answer here by Daniel Wilson has more feature and is updated).
However, this class has no dependencies and is converted to the basics to make fetch response only:
abstract class NetworkBoundResource<RequestType> {
private val result = MediatorLiveData<Resource<RequestType>>()
init {
setValue(Resource.loading(null))
fetchFromNetwork()
}
#MainThread
private fun setValue(newValue: Resource<RequestType>) {
if (result.value != newValue) {
result.value = newValue
}
}
private fun fetchFromNetwork() {
val apiResponse = createCall()
result.addSource(apiResponse) { response ->
result.removeSource(apiResponse)
when (response) {
is ApiSuccessResponse -> {
setValue(Resource.success(processResponse(response)))
}
is ApiErrorResponse -> {
onFetchFailed()
setValue(Resource.error(response.errorMessage, null))
}
}
}
}
protected fun onFetchFailed() {
}
fun asLiveData() = result as LiveData<Resource<RequestType>>
#WorkerThread
protected open fun processResponse(response: ApiSuccessResponse<RequestType>) = response.body
#MainThread
protected abstract fun createCall(): LiveData<ApiResponse<RequestType>>
}
So when using it, only one method could be implemented createCall():
fun login(email: String, password: String) = object : NetworkBoundResource<Envelope<User>>() {
override fun createCall() = api.login(email, password)
}.asLiveData()

Here is my attempt after a long while!
abstract class NetworkOnlyResource<ResultType, RequestType>
#MainThread constructor(private val appExecutors: AppExecutors) {
private val result = MediatorLiveData<Resource<ResultType>>() //List<Repo>
private val request = MediatorLiveData<Resource<RequestType>>() //RepoSearchResponse
init {
result.value = Resource.loading(null)
fetchFromNetwork()
}
#MainThread
private fun setResultValue(newValue: Resource<ResultType>) {
if (result.value != newValue) {
result.value = newValue
}
}
private fun fetchFromNetwork() {
val apiResponse = createCall()
result.addSource(apiResponse) { response ->
result.removeSource(apiResponse)
response?.let {
if (response.isSuccessful) {
appExecutors.diskIO().execute({
val requestType = processResponse(response)
val resultType = processResult(requestType)
appExecutors.mainThread().execute({
setResultValue(Resource.success(resultType))
}
)
})
} else {
val errorMessage = when (response.errorThrowable) {
is HttpException -> "An error has occurred: ${response.errorThrowable.code()} Please try again."
is SocketTimeoutException -> "A timeout error has occurred, please check your internet connection and try again"
is IOException -> "An IO error has occurred, most likely a network issue. Please check your internet connection and try again"
is UnauthorizedCredentialsException -> "This user name or password is not recognized"
else -> {
response.errorMessage
}
}
Timber.e(errorMessage)
errorMessage?.let {
val requestType = processResponse(response)
val resultType = processResult(requestType)
setResultValue(Resource.error(errorMessage, resultType, response.errorThrowable))
}
onFetchFailed()
}
}
}
}
protected open fun onFetchFailed() {}
fun asLiveData() = result as LiveData<Resource<ResultType>>
#WorkerThread
protected open fun processResponse(response: ApiResponse<RequestType>) = response.body
#WorkerThread
protected abstract fun processResult(item: RequestType?): ResultType?
#MainThread
protected abstract fun createCall(): LiveData<ApiResponse<RequestType>>
}
The processResult() function allows you to transform a successful RequestType into a ResultType. It seems to work for me but would love any feedback from someone that knows what they are doing :)
Fyi Yigit has since updated the NetworkBoundResource with better error handling which should also work here in the not-successful 'else' statement.

Here's my version which I wrote sometime back:
import android.arch.lifecycle.LiveData
import android.arch.lifecycle.MediatorLiveData
import android.support.annotation.MainThread
/**
* A generic class to send loading event up-stream when fetching data
* only from network.
*
* #param <RequestType>
</RequestType></ResultType> */
abstract class NetworkResource<RequestType> #MainThread constructor() {
/**
* The final result LiveData
*/
private val result = MediatorLiveData<Resource<RequestType>>()
init {
// Send loading state to UI
result.value = Resource.loading()
fetchFromNetwork()
}
/**
* Fetch the data from network and then send it upstream to UI.
*/
private fun fetchFromNetwork() {
val apiResponse = createCall()
// Make the network call
result.addSource(apiResponse) { response ->
result.removeSource(apiResponse)
// Dispatch the result
response?.apply {
when {
status.isSuccessful() -> setValue(this)
else -> setValue(Resource.error(errorMessage))
}
}
}
}
#MainThread
private fun setValue(newValue: Resource<RequestType>) {
if (result.value != newValue) result.value = newValue
}
fun asLiveData(): LiveData<Resource<RequestType>> {
return result
}
#MainThread
protected abstract fun createCall(): LiveData<Resource<RequestType>>
}

This for database operation only in case you needed it (with kotlin coroutine
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
/**
* A generic class that can provide a resource backed by the sqlite database.
*
*
* #param <ResultType>
</ResultType> */
abstract class DatabaseResource<ResultType> {
private val result = MediatorLiveData<Resource<ResultType>>()
init {
result.value = Resource.loading(null)
GlobalScope.launch(Dispatchers.IO) {
val dbSource = performDbOperation()
GlobalScope.launch(Dispatchers.Main) {
result.addSource(dbSource) { data ->
result.removeSource(dbSource)
result.addSource(dbSource) { newData ->
setValue(Resource.success(newData))
}
}
}
}
}
private fun setValue(newValue: Resource<ResultType>) {
if (result.value != newValue) {
result.value = newValue
}
}
fun asLiveData() = result as LiveData<Resource<ResultType>>
protected abstract fun performDbOperation(): LiveData<ResultType>
}

For future Kotlin users, make it simple as:
1. Resource class:
sealed class Resource<T>(
val data: T? = null,
val error: Throwable? = null
) {
class Success<T>(data: T) : Resource<T>(data)
class Loading<T>(data: T? = null) : Resource<T>(data)
class Error<T>(throwable: Throwable, data: T? = null) : Resource<T>(data, throwable)
}
2. NetworkBoundResource:
inline fun <T> networkBoundResource(
crossinline fetch : suspend () -> Response<T>
) = flow {
emit(Resource.Loading(null))
try {
emit(Resource.Success(fetch().body()))
}catch(throwable : Throwable){
emit(Resource.Error(throwable, null))
}
}

Related

How to until wait 2 parallel retrofit calls both finish?

I want to call 2 retrofit services in parallel and then do an action only when both of them finished, but I don't seem to figuer it out how.
I have a viewModel where I have defined my services:
var config= List<Configuration>
fun getClientProducts() {
getClientClientConfigUseCase
.build(this)
.executeWithError({ config ->
config = config
}, {
})
}
var therapies = List<DtoTherapy>
fun getTherapies() {
getTherapiesUseCase
.build(this)
.executeWithError({ config ->
therapies = it
}, {
})
}
And then I want to call both services in parallel in my fragment:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupUi(view)
loadUserData()
viewModel.getClientProducts()
viewModel.getTherapies()
}
And when both variables config and therapies have the value do an action. But as I said maybe one service take 1 sec to respond and another 4 secs, and I want only to perfom an action when both have finished. Any help with be appreciated.
Here is the class I use to build the use case call:
abstract class SingleUseCase<T> : UseCase() {
private lateinit var single: Single<T>
private lateinit var useCaseInterface: UseCaseInterface
private var withLoader: Boolean = false
private var withErrorMessage: Boolean = false
internal abstract fun buildUseCaseSingle(): Single<T>
fun build(useCaseInterface: UseCaseInterface): SingleUseCase<T> {
this.withLoader = false
this.withErrorMessage = false
this.useCaseInterface = useCaseInterface
this.single = buildUseCaseSingle()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doAfterSuccess { useCaseInterface.onSuccess(it) }
return this
}
fun withLoader(): SingleUseCase<T> {
this.withLoader = true
return this
}
fun withErrorMessage(): SingleUseCase<T> {
this.withErrorMessage = true
return this
}
fun single(): Single<T> {
return this.single
}
fun execute(onSuccess: ((t: T) -> Unit)) {
useCaseInterface.onPrepareRequest(withLoader)
buildObservable(onSuccess)
}
private fun buildObservable(onSuccess: ((t: T) -> Unit)) {
disposeLast()
lastDisposable = single
.doFinally { useCaseInterface.onFinishRequest(this.withLoader) }
.subscribe(
{ onSuccess(it) },
{
useCaseInterface.onError(mapError(it), withErrorMessage)
})
lastDisposable?.let {
compositeDisposable.add(it)
}
}
fun executeWithError(onSuccess: ((success: T) -> Unit), onError: ((error: ApiError ) -> Unit)) {
useCaseInterface.onPrepareRequest(withLoader)
buildObservable(onSuccess, onError)
}
private fun buildObservable(onSuccess: ((success: T) -> Unit), onError: ((error: ApiError ) -> Unit)) {
disposeLast()
lastDisposable = single
.doFinally { useCaseInterface.onFinishRequest(this.withLoader) }
.subscribe(
{ onSuccess(it) },
{
onError(mapError(it))
useCaseInterface.onError(mapError(it), withErrorMessage)
})
lastDisposable?.let {
compositeDisposable.add(it)
}
}
private fun mapError(t: Throwable): ApiError {
return if(t is HttpException) {
val apiError = t.response()?.errorBody()?.string()
try {
ApiError (t.code(), t.response()?.errorBody()?.string(), Gson().fromJson(apiError, GenericError::class.java))
} catch(e: Exception) {
ApiError (-2, "Unkown error")
}
} else ApiError (-1, "Unkown error")
}
}
And this is a specific usecase class:
class GetClientConfigUseCase #Inject constructor(private val repository: UserRepository) :
SingleUseCase<ClientConfigResponse>() {
override fun buildUseCaseSingle(): Single<ClientConfigResponse> {
return repository.getUserConfig()
}
}
I guess you need zip operation. With zip operation you can have a result of two observable in one place when both of them received data.
Observable<List<ClientProducts>> observable1 = ...
Observable<List<DtoTherapy>> observable2 = ...
Observable.zip(observable1, observable2, new BiFunction<List<ClientProducts>, List<DtoTherapy>, Result>() {
#Override
public Result apply(List<ClientProducts> products, List<DtoTherapy> therapies) throws Exception
{
// here you have both of your data
// do operations on products and therapies
// then return the result
return result;
}
});

ClassCastException in NetworkBoundResource<ResultType, RequestType>

I am following the "best practices"(?) from the GitHub sample: https://github.com/android/architecture-components-samples/blob/master/GithubBrowserSample/app/src/main/java/com/android/example/github/repository/NetworkBoundResource.kt
I am getting an error
java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to com.xxx.yyy.data.model.TestUser
I've tried using Gson to convert to JSON and back to the Generic RequestType, but that doesn't work either. The data is coming back just fine. If I cast the response in the NetworkBoundResource, then it works - but that kind of defeats the purpose of generics. I am also using the aws-android-sdk to invoke lambda, which makes things a HUGE pain, but that's AWS for ya
TestUser
public class TestUser {
private String username;
public TestUser() {
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
NetworkBoundResource
abstract class NetworkBoundResource<ResultType, RequestType>
#MainThread constructor(private val appExecutors: AppExecutors) {
private val result = MediatorLiveData<Resource<ResultType>>()
init {
result.value = Resource.loading(null)
val dbSource = loadFromDb()
result.addSource(dbSource) { data ->
result.removeSource(dbSource)
if (shouldFetch(data)) {
Log.e("NetworkBoundResource", "fetching from network")
appExecutors.networkIO().execute {
fetchFromNetwork(dbSource)
}
} else {
Log.e("NetworkBoundResource", "not fetching from network")
result.addSource(dbSource) { newData ->
setValue(Resource.success(newData))
}
}
}
}
#MainThread
private fun setValue(newValue: Resource<ResultType>) {
if (result.value != newValue) {
result.value = newValue
}
}
private fun fetchFromNetwork(dbSource: LiveData<ResultType>) {
val apiResponse = MutableLiveData<ApiResponse<RequestType>>()
// This is super dumb, but can't createCall() on the main thread 🤦
var lambdaResponse: LambdaResponse<RequestType>? = null
var exception: Exception? = null
try {
lambdaResponse = createCall()
} catch (e: Exception) {
exception = e
}
appExecutors.mainThread().execute {
// Can't setValue on a background thread 🤦
if (exception != null) {
apiResponse.value = ApiResponse.create(exception)
} else if (lambdaResponse != null) {
apiResponse.value = ApiResponse.create(lambdaResponse)
}
// we re-attach dbSource as a new source, it will dispatch its latest value quickly
result.addSource(dbSource) { newData ->
setValue(Resource.loading(newData))
}
result.addSource(apiResponse) { response ->
result.removeSource(apiResponse)
result.removeSource(dbSource)
when (response) {
is ApiSuccessResponse -> {
appExecutors.diskIO().execute {
val x = processResponse(response)
// FAILING HERE
saveCallResult(x)
appExecutors.mainThread().execute {
// we specially request a new live data,
// otherwise we will get immediately last cached value,
// which may not be updated with latest results received from network.
result.addSource(loadFromDb()) { newData ->
setValue(Resource.success(newData))
}
}
}
}
is ApiEmptyResponse -> {
appExecutors.mainThread().execute {
// reload from disk whatever we had
result.addSource(loadFromDb()) { newData ->
setValue(Resource.success(newData))
}
}
}
is ApiErrorResponse -> {
onFetchFailed()
result.addSource(dbSource) { newData ->
setValue(Resource.error(response.errorMessage, newData))
}
}
}
}
}
}
protected open fun onFetchFailed() {
Log.e("NetworkBoundResource", "onFetchFailed")
}
fun asLiveData() = result as LiveData<Resource<ResultType>>
#WorkerThread
protected open fun processResponse(response: ApiSuccessResponse<RequestType>) = response.body
#WorkerThread
protected abstract fun saveCallResult(item: RequestType)
#MainThread
protected abstract fun shouldFetch(data: ResultType?): Boolean
#MainThread
protected abstract fun loadFromDb(): LiveData<ResultType>
#MainThread
protected abstract fun createCall(): LambdaResponse<RequestType>
}
LoginDataSource
fun auth(authRequest: AuthRequest): LiveData<Resource<User>> {
return object : NetworkBoundResource<User, TestUser>(appExecutors) {
override fun saveCallResult(item: TestUser) {
Log.e("username", item.username)
// Log.e("saveCallResult", "saving user to db: ${item.federatedIdentity}")
// userDao.insertAll(item)
}
override fun shouldFetch(data: User?): Boolean {
val fetch = data == null
Log.e("shouldFetch", "$fetch")
return true
}
override fun loadFromDb(): LiveData<User> {
Log.e("loadFromDb", "findById: ${authRequest.federatedIdentity}")
return userDao.findById(authRequest.federatedIdentity)
}
override fun createCall(): LambdaResponse<TestUser> {
Log.e("createCall", "authenticating user: ${authRequest.federatedIdentity}")
return authApiService.auth(authRequest)
}
}.asLiveData()
}

Network Only Resource From Livedata To RxJava

I have this class that make request call with livedata... I want to transform this class to rxjava class... Because i want to use rxjava on my repository / viewmodel classes...
Can someone help me to make this chages please?
abstract class NetworkOnlyResource<RequestType> {
private val result = MediatorLiveData<Resource<RequestType>>()
init {
result.value = Resource.loading(null)
fetchFromNetwork()
}
#MainThread
private fun setValue(newValue: Resource<RequestType>) {
if (result.value != newValue) {
result.value = newValue
}
}
private fun fetchFromNetwork() {
val apiResponse = createCall()
result.addSource(apiResponse) { response ->
result.removeSource(apiResponse)
when (response) {
is ApiSuccessResponse -> {
setValue(Resource.success(processResponse(response)))
}
is ApiEmptyResponse -> {
setValue(Resource.success(null))
}
is ApiErrorResponse -> {
onFetchFailed()
setValue(Resource.error(response.errorMessage, null))
}
}
}
}
protected open fun onFetchFailed() {}
fun asLiveData() = result as LiveData<Resource<RequestType>>
#WorkerThread
protected open fun processResponse(response: ApiSuccessResponse<RequestType>) = response.body
#MainThread
protected abstract fun createCall(): LiveData<ApiResponse<RequestType>>
}
Answer1
If you want to "transform this class to rxjava class", something similar to this will probably do the job:
abstract class RxNetworkOnlyResource<RequestType> {
private val result = BehaviorSubject.create<Resource<RequestType>>()
init {
createCall()
.toObservable()
.map { response ->
when (response) {
is ApiSuccessResponse -> {
Resource.success(processResponse(response))
}
is ApiEmptyResponse -> {
Resource.success(null)
}
is ApiErrorResponse -> {
onFetchFailed()
Resource.error(response.errorMessage, null)
}
}
}
.startWith(Resource.loading(null))
.subscribe(result)
}
protected open fun onFetchFailed() {}
fun asObservable() = result as Observable<Resource<RequestType>>
protected open fun processResponse(response: ApiSuccessResponse<RequestType>) = response.body
protected abstract fun createCall(): Single<ApiResponse<RequestType>>
}
BehaviorRelay might make more sense but BehaviorSubject will probably work fine in your specific case.
Answer2
What I recommend is to get rid of this class (vs trying to translate to RxJava). You won't need complex classes, nor all these MediatorLivedata and Transformer shenanigans with RxJava.
For example, the LiveData class you had can be used like this:
object: NetworkOnlyResource<RequestType>() {
override fun createCall(): LiveData<ApiResponse<RequestType>> {
return someCall()
}
}.asLiveData() // This returns LiveData<Resource<RequestType>> instance
Instead of having this NetworkOnlyResource class, you can simply do:
someCall().toObservable()
.map(Utils::processResponse)
.startWith(Resource.loading(null))

Get item by id in Room

I'm using Room + LiveData in my Android project. Following to Google Blueprints, I've implemented data layer of my application.
This is how my Dao looks like:
#Query("SELECT * FROM events WHERE id=:arg0")
fun loadSingle(id: String): LiveData<Event>
I'm calling it from my EventRepository:
fun loadSingle(eventId: String): LiveData<RequestReader<Event>> {
return object: NetworkManager<Event, Event>(appExecutors!!) {
override fun loadLocal(): LiveData<Event> {
val item = eventLocal!!.loadSingle("Title 1")
Crashlytics.log(Log.VERBOSE, TAG, "loadFromServer::loadLocal=$item")
return item
}
override fun isUpdateForced(data: Event?): Boolean {
Crashlytics.log(Log.VERBOSE, TAG, "loadFromServer::isUpdateForced")
return data == null || requestTimeout.isAllowed(UNDEFINED_KEY.toString())
}
override fun makeRequest(): LiveData<ApiResponse<Event>> {
Crashlytics.log(Log.VERBOSE, TAG, "loadFromServer::makeRequest")
return Database.createService(EventRemote::class.java).load(eventId)
}
override fun onSuccess(item: Event) {
eventLocal?.save(item)
}
override fun onFail() {
Crashlytics.log(Log.VERBOSE, TAG, "loadFromServer::onFail")
requestTimeout.reset(UNDEFINED_KEY.toString())
}
}.liveData
}
Where NetworkManager class is (has been "taken" from here):
abstract class NetworkManager<ResultType, RequestType> #MainThread constructor(val appExecutors: AppExecutors) {
companion object {
private val TAG = "TAG_NETWORK_MANAGER"
}
val liveData: MediatorLiveData<RequestReader<ResultType>> = MediatorLiveData()
init {
liveData.value = RequestReader.loading(null)
val localSource: LiveData<ResultType> = loadLocal()
Log.d(TAG, "before add::localSource=${localSource.value}")
liveData.addSource(localSource, { data ->
Log.d(TAG, "data=$data")
liveData.removeSource(localSource)
if (isUpdateForced(data)) {
loadRemote(localSource)
} else {
liveData.addSource(localSource, { reusedData -> liveData.value = RequestReader.success(reusedData)})
}
})
}
private fun loadRemote(localSource: LiveData<ResultType>) {
val remoteSource = makeRequest()
liveData.addSource(localSource, {
liveData.value = RequestReader.success(it)
})
liveData.addSource(remoteSource) { response ->
liveData.removeSource(localSource)
liveData.removeSource(remoteSource)
if (response!!.isSuccessful) {
appExecutors.diskIO.execute {
onSuccess(processResponse(response))
appExecutors.mainThread.execute {
liveData.addSource(localSource, {
liveData.value = RequestReader.success(it)
})
}
}
} else {
onFail()
liveData.addSource(localSource, {
liveData.value = RequestReader.error("Error: ${response.errorMessage}", it)
})
}
}
}
#MainThread
protected abstract fun loadLocal(): LiveData<ResultType>
#MainThread
protected abstract fun isUpdateForced(data: ResultType?): Boolean
#MainThread
protected abstract fun makeRequest(): LiveData<ApiResponse<RequestType>>
#WorkerThread
protected abstract fun onSuccess(item: RequestType)
#MainThread
protected abstract fun onFail()
#WorkerThread
protected fun processResponse(response: ApiResponse<RequestType>): RequestType {
return response.body!!
}
}
And after i expect to get my LiveData in ViewModel:
open class EventSingleViewModel: ViewModel(), RepositoryComponent.Injectable {
companion object {
private val TAG = "TAG_EVENT_SINGLE_VIEW_MODEL"
}
#Inject lateinit var eventRepository: EventRepository
var eventSingle: LiveData<RequestReader<Event>>? = null
override fun inject(repositoryComponent: RepositoryComponent) {
repositoryComponent.inject(this)
eventSingle = MutableLiveData<RequestReader<Event>>()
}
fun load(eventId: String) {
Crashlytics.log(Log.VERBOSE, TAG, "starts to loadList::eventId=$eventId")
eventSingle = eventRepository.loadSingle(eventId)
}
}
The problem.
I'm getting a list of events the same way (it works!) I've described above, but with a single event (this event is already in database) it doesn't work. I've found out that localSource.value is null (in NetworkManager). Maybe my query is bad or.. something else.
Check again your DAO implementation, the argument must be the same in both, the function parameter and the annotation arg.
Change this:
#Query("SELECT * FROM events WHERE id=:arg0")
fun loadSingle(id: String): LiveData<Event>
To:
#Query("SELECT * FROM events WHERE id=:id ")
fun loadSingle(id: String): LiveData<Event>

How to handle error states with LiveData?

The new LiveData can be used as a replacement for RxJava's observables in some scenarios. However, unlike Observable, LiveData has no callback for errors.
My question is: How should I handle errors in LiveData, e.g. when it's backed by some network resource that can fail to be retrieved due to an IOException?
In one of Google's sample apps for Android Architecture Components they wrap the LiveData emitted object in a class that can contain a status, data, and message for the emitted object.
https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample/app/src/main/java/com/android/example/github/vo/Resource.kt
With this approach you can use the status to determine if there was an error.
You can extend from MutableLiveData and create a holder Model to wrap your data.
This is your Wrapper Model
public class StateData<T> {
#NonNull
private DataStatus status;
#Nullable
private T data;
#Nullable
private Throwable error;
public StateData() {
this.status = DataStatus.CREATED;
this.data = null;
this.error = null;
}
public StateData<T> loading() {
this.status = DataStatus.LOADING;
this.data = null;
this.error = null;
return this;
}
public StateData<T> success(#NonNull T data) {
this.status = DataStatus.SUCCESS;
this.data = data;
this.error = null;
return this;
}
public StateData<T> error(#NonNull Throwable error) {
this.status = DataStatus.ERROR;
this.data = null;
this.error = error;
return this;
}
public StateData<T> complete() {
this.status = DataStatus.COMPLETE;
return this;
}
#NonNull
public DataStatus getStatus() {
return status;
}
#Nullable
public T getData() {
return data;
}
#Nullable
public Throwable getError() {
return error;
}
public enum DataStatus {
CREATED,
SUCCESS,
ERROR,
LOADING,
COMPLETE
}
}
This is your extended LiveData Object
public class StateLiveData<T> extends MutableLiveData<StateData<T>> {
/**
* Use this to put the Data on a LOADING Status
*/
public void postLoading() {
postValue(new StateData<T>().loading());
}
/**
* Use this to put the Data on a ERROR DataStatus
* #param throwable the error to be handled
*/
public void postError(Throwable throwable) {
postValue(new StateData<T>().error(throwable));
}
/**
* Use this to put the Data on a SUCCESS DataStatus
* #param data
*/
public void postSuccess(T data) {
postValue(new StateData<T>().success(data));
}
/**
* Use this to put the Data on a COMPLETE DataStatus
*/
public void postComplete() {
postValue(new StateData<T>().complete());
}
}
And this is how you use it
StateLiveData<List<Book>> bookListLiveData;
bookListLiveData.postLoading();
bookListLiveData.postSuccess(books);
bookListLiveData.postError(e);
And how it can be observed:
private void observeBooks() {
viewModel.getBookList().observe(this, this::handleBooks);
}
private void handleBooks(#NonNull StateData<List<Book>> books) {
switch (books.getStatus()) {
case SUCCESS:
List<Book> bookList = books.getData();
//TODO: Do something with your book data
break;
case ERROR:
Throwable e = books.getError();
//TODO: Do something with your error
break;
case LOADING:
//TODO: Do Loading stuff
break;
case COMPLETE:
//TODO: Do complete stuff if necessary
break;
}
}
Wrap the Data that you return from LiveData with some sort of error Messaging
public class DataWrapper<T>T{
private T data;
private ErrorObject error; //or A message String, Or whatever
}
//Now in your LifecycleRegistryOwner Class
LiveData<DataWrapper<SomeObjectClass>> result = modelView.getResult();
result.observe(this, newData ->{
if(newData.error != null){ //Can also have a Status Enum
//Handle Error
}
else{
//Handle data
}
});
Just Catch an Exception instead or throwing it. use the error Object to pass this Data to the UI.
MutableLiveData<DataWrapper<SomObject>> liveData = new...;
//On Exception catching:
liveData.set(new DataWrapper(null, new ErrorObject(e));
Another approach is to use MediatorLiveData that will take sources of LiveData of different type. This will give you separation of each event:
For example:
open class BaseViewModel : ViewModel() {
private val errorLiveData: MutableLiveData<Throwable> = MutableLiveData()
private val loadingStateLiveData: MutableLiveData<Int> = MutableLiveData()
lateinit var errorObserver: Observer<Throwable>
lateinit var loadingObserver: Observer<Int>
fun <T> fromPublisher(publisher: Publisher<T>): MediatorLiveData<T> {
val mainLiveData = MediatorLiveData<T>()
mainLiveData.addSource(errorLiveData, errorObserver)
mainLiveData.addSource(loadingStateLiveData, loadingObserver)
publisher.subscribe(object : Subscriber<T> {
override fun onSubscribe(s: Subscription) {
s.request(java.lang.Long.MAX_VALUE)
loadingStateLiveData.postValue(LoadingState.LOADING)
}
override fun onNext(t: T) {
mainLiveData.postValue(t)
}
override fun onError(t: Throwable) {
errorLiveData.postValue(t)
}
override fun onComplete() {
loadingStateLiveData.postValue(LoadingState.NOT_LOADING)
}
})
return mainLiveData
}
}
In this example loading and error LiveData will start being observed once the MediatorLiveData will have active observers.
In my app, I had to translate RxJava Observables into LiveData. While doing that, I of course had to maintain the error state. Here's how I did it (Kotlin)
class LiveDataResult<T>(val data: T?, val error: Throwable?)
class LiveObservableData<T>(private val observable: Observable<T>) : LiveData<LiveDataResult<T>>() {
private var disposable = CompositeDisposable()
override fun onActive() {
super.onActive()
disposable.add(observable.subscribe({
postValue(LiveDataResult(it, null))
}, {
postValue(LiveDataResult(null, it))
}))
}
override fun onInactive() {
super.onInactive()
disposable.clear()
}
}
Just some implementation of the method from Chris Cook's answer:
At first, we need the object that will contain response data and exceptions:
/**
* A generic class that holds a value with its loading status.
*
* #see Sample apps for Android Architecture Components
*/
data class Resource<out T>(val status: Status, val data: T?, val exception: Throwable?) {
enum class Status {
LOADING,
SUCCESS,
ERROR,
}
companion object {
fun <T> success(data: T?): Resource<T> {
return Resource(Status.SUCCESS, data, null)
}
fun <T> error(exception: Throwable): Resource<T> {
return Resource(Status.ERROR, null, exception)
}
fun <T> loading(): Resource<T> {
return Resource(Status.LOADING, null, null)
}
}
}
And then my own invention - AsyncExecutor.
This small class do 3 important things:
Return standard convenient LiveData object.
Call provided callback asynchronously.
Takes the result of the callback or catch any exception and put it to the LiveData.
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
class AsyncExecutor {
companion object {
fun <T> run(callback: () -> T): LiveData<Resource<T>> {
val resourceData: MutableLiveData<Resource<T>> = MutableLiveData()
Thread(Runnable {
try {
resourceData.postValue(Resource.loading())
val callResult: T = callback()
resourceData.postValue(Resource.success(callResult))
} catch (e: Throwable) {
resourceData.postValue(Resource.error(e))
}
}).start()
return resourceData
}
}
}
Then you can create a LiveData in your ViewModel, contains the result of your callback or exception:
class GalleryViewModel : ViewModel() {
val myData: LiveData<Resource<MyData>>
init {
myData = AsyncExecutor.run {
// here you can do your synchronous operation and just throw any exceptions
return MyData()
}
}
}
And then you can get your data and any exceptions in the UI:
class GalleryFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
galleryViewModel = ViewModelProviders.of(this).get(GalleryViewModel::class.java)
// ...
// Subscribe to the data:
galleryViewModel.myData.observe(viewLifecycleOwner, Observer {
when {
it.status === Resource.Status.LOADING -> {
println("Data is loading...")
}
it.status === Resource.Status.ERROR -> {
it.exception!!.printStackTrace()
}
it.status === Resource.Status.SUCCESS -> {
println("Data has been received: " + it.data!!.someField)
}
}
})
return root
}
}
I have built a movie search app here in which I have used to different LiveData objects, one for the successful response from the network and one for the unsuccessful:
private val resultListObservable = MutableLiveData<List<String>>()
private val resultListErrorObservable = MutableLiveData<HttpException>()
fun findAddress(address: String) {
mainModel.fetchAddress(address)!!.subscribeOn(schedulersWrapper.io()).observeOn(schedulersWrapper.main()).subscribeWith(object : DisposableSingleObserver<List<MainModel.ResultEntity>?>() {
override fun onSuccess(t: List<MainModel.ResultEntity>) {
entityList = t
resultListObservable.postValue(fetchItemTextFrom(t))
}
override fun onError(e: Throwable) {
resultListErrorObservable.postValue(e as HttpException)
}
})
}

Categories

Resources