Android LiveData MVVM вернуть emit и добавть в базу при помощи DAO

Внутри ViewModel при помощи ретрофита получаю данные стандартным способом

fun getUsers() = liveData(Dispatchers.IO) {
    emit(Resource.loading(data = null))
    try {
        emit(Resource.success(data = nalRepository.getNal()))
    } catch (exception: Exception) {
        emit(Resource.error(data = null, message = exception.message ?: "Error      Occurred!"))
    }
}

Можно ли эти данные перед тем как вернуть в liveData, сохранить в базу

private val db = AppDatabase.getInstance(getApplication())
private val dao = db!!.CurrencyDao()
private val service = Executors.newFixedThreadPool(1)

fun addListBook(list: List<Currency>)
{
    service.submit {
        dao.insertList(list)
    }
   service.shutdown()
}

У меня сохранение addListBook() происходит уже внутри фрагмента при подписки на liveData, но логичнее это было-бы это сделать прямо внутри ViewModel, но как подлезть к emit(Resource.success(data = nalRepository.getNal())) и отправить в базу непонятно. То есть так не работает

 //Получить данные
 var list = emit(Resource.success(data = nalRepository.getNal()))
 //Сохранить в базу
 addListBook(list)

Ответы (2 шт):

Автор решения: Sergei Buvaka

Если я правильно понял ваш вопрос, то вам должен помочь класс Transformation. Вот пример:

private fun madLiveData(): LiveData<Int> { 
    return Transformations.map(dao.getLiveDataModel()) { model: Model ->
        doSomething(model) model.id } 
} 

private fun doSomething(Model: Model) {
     //Здесь сохраняйте в бд
}

PS. Меня сбила с толку ваша liveData - я подумал, что это возвращаемый тип, а сейчас понял, что это имя coroutineScope.

Тогда вам стоит поступить по другому.

Вы можете написать что-то вроде такого

sealed class Result<out T> {
    object Loading: Result<Nothing>()
    data class Success<out T>(val data: T) : Result<T>()
    data class Error(val exception: Exception) : Result<Nothing>()
} 

В целом вы можете убрать здесь дженерики и подставить свой тип, тогда будет проще, но это решение универсальное.

В том месте где вы получаете ответ от ретрофита вы можете сделать что-то вроде такого:

override suspend fun get(args: ArgumentsWrapper?): Result<MODEL> {
        return try {
            val result = parseResponse(getResponse(args))
            Result.Success(result)
        } catch (e: Exception) {
            Result.Error(e)
        }
    }


private fun parseResponse(response: Response<RESPONSE>): RESPONSE {
        val data: RESPONSE?

        if (response.isSuccessful)
            data = response.body()
        else
            throw getNetworkException(response) // это кастомный метод для генерации ошибки сервера в мой класс, можете тут бросать то что удобно будет вам. 

        if (data != null)
            return data
        else
            throw NetworkException(statusMessage = "Generated Network Error Something went wrong") // это кастомный Exception, вы тут можете вернуть либо свой либо один из готовых
    }

И соответственно во ViewModel вам уже будет возвращаться результат либо Error либо Success. И во ViewModel обрабатываете это примерно так

fun getUsers() {
        liveData.launch {
            when (val result = dataSource.get()) {
                is Result.Success -> doSomethingWithData(result.data)
                is Result.Error -> handleError(result.exception)
            }
        }
    }
→ Ссылка
Автор решения: danilshik

Я использую примерно такую архитектуру:

ViewModel

class AlbumListViewModel @Inject constructor(private val repository: AlbumRepository): ViewModel(){
    private var query = mutableLiveData("")
    private var albums : LiveData<Result<List<Album>>> = Transformations.switchMap(query, ::getAlbumsBySearch)

    private fun getAlbumsBySearch(searchQuery: String): LiveData<Result<List<Album>>>{
        return repository.getAlbumsBySearch(searchQuery)
    }
    fun handleSearchQuery(text: String?) {
        query.value = text
        Log.d("AlbumListViewModel SV", text.toString())
    }
}

Repository

class AlbumRepository @Inject constructor(private val remoteSource: AlbumRemoteDataSource, private val database: AppDatabase){

    fun getAlbumsBySearch(searchQuery: String): LiveData<Result<List<Album>>>{


        Log.d("RepositoryNew", searchQuery)
        val resultLiveData = resultLiveData(
            databaseQuery = {database.getAlbumDao().getAlbumsBySearch("%${searchQuery}%")},
            networkCall = {remoteSource.getAlbumsBySearch(searchQuery)},
            saveCallResult = {database.getAlbumDao().insertAll(it.results)}
        )
        Log.d("AlbumRepository", resultLiveData.value.toString())
        return resultLiveData
    }


}

RemoteRepository

class AlbumRemoteDataSource @Inject constructor(
    private val apiService: APIService
): BaseDataSource(){
    suspend fun getAlbumsBySearch(searchQuery: String): Result<BaseResponse<Album>>{
        val result = getResult{apiService.getAlbumsBySearch(searchQuery)}
        Log.d("AlbumRemoteDataSource", result.toString())
        return result
    }


}

getResult

suspend fun <T> getResult(call : suspend () -> Response<T>) : Result<T>{
        return try {
            //Получаем ответ от вызова
            val response = call()
            if(response.isSuccessful) {
                //тело
                val body = response.body()
                if (body != null)
                    return Result.success(body)
            }
            error("${response.code()} ${response.message()} ${response.errorBody()}")
        } catch (e: Exception){
            Log.d("BaseDataSource", e.toString())
            Result.error(e.message ?: e.toString())
        }
    }

APIService

interface APIService{
    /**
     * Поиск album по любым параметрам с поиковым запросом "jack-johnson
     * https://itunes.apple.com/search?term=jack+johnson&entity=album
     */
    @GET("search")
    suspend fun getAlbumsBySearch(
        @Query("term") searchQuery: String,
        @Query("entity") typeEntity: String = "album") : Response<BaseResponse<Album>>

LiveData Extension

fun <T, A> resultLiveData(
    databaseQuery: () -> LiveData<T>,
    networkCall: suspend () -> Result<A>,
    saveCallResult: suspend (A) -> Unit): LiveData<Result<T>> =

    liveData(Dispatchers.IO){
            emit(Result.loading())
            val responseSource = networkCall.invoke()
            Log.d("ResultLiveData Network", responseSource.toString())
            if(responseSource.status == Result.Status.SUCCESS) {
                saveCallResult(responseSource.data!!)
                Log.d("resultLiveData", "save database")
            }
            else if(responseSource.status == Result.Status.ERROR){
                emit(Result.error(responseSource.error))
                Log.d("resultLiveData", "error ${responseSource.error}")
            }
            val databaseSource = databaseQuery.invoke()
            emitSource(databaseSource.map { Result.success(it) })
        }

fun <T> mutableLiveData(defaultValue: T? = null): MutableLiveData<T>{
    val data = MutableLiveData<T>()

    if(defaultValue != null){
        data.value = defaultValue
    }

    return data
}

Класс - обертка

data class Result<out T>(
    val status : Status,
    val data: T?,
    val error: String?
){
    enum class Status{
        SUCCESS,
        LOADING,
        ERROR
    }

    companion object{
        fun <T> success(data: T) : Result<T> = Result(Status.SUCCESS, data, null)
        fun <T> error(error: String?, data: T? = null): Result<T> = Result(Status.ERROR, data, error)
        fun <T> loading(data: T? = null): Result<T> = Result(Status.LOADING, data, null)
    }

}

На самом деле вариантов использования много:

  1. Делать запросы к репозиторию, который будет сохранять в базу данных, а данные будут прилетать через LiveData из бд в случае изменении данных
  2. Делать чисто запросы а результат ожидать в этом же ответе, промежуточно сохранять.
  3. Ну или этот вариант.

Даже в этом варианте очень много способов. Например можно сначала делать выборку из базы данных, чтобы были видны кешированные данные, а потом только делать запрос, обновленные данные вставлять в базу данных, и по LiveData они обновяться.

Можно Insert и delete старых данных объединить в Транзакцию.

Большое количество вариантов использования.

Нужно наверно стремиться к Clean Architecture. Согласно ей, все приложение должны разделены на слои. Не должно быть зависимости от базы данных и внешнего сервиса, поэтому нужно будет добавлять еще 2 уровня абстрации (что - то наподобие LocalRepository (бд) и RemoteRepository (внешний сервис)

→ Ссылка