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 шт):
Если я правильно понял ваш вопрос, то вам должен помочь класс 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)
}
}
}
Я использую примерно такую архитектуру:
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)
}
}
На самом деле вариантов использования много:
- Делать запросы к репозиторию, который будет сохранять в базу данных, а данные будут прилетать через LiveData из бд в случае изменении данных
- Делать чисто запросы а результат ожидать в этом же ответе, промежуточно сохранять.
- Ну или этот вариант.
Даже в этом варианте очень много способов. Например можно сначала делать выборку из базы данных, чтобы были видны кешированные данные, а потом только делать запрос, обновленные данные вставлять в базу данных, и по LiveData они обновяться.
Можно Insert и delete старых данных объединить в Транзакцию.
Большое количество вариантов использования.
Нужно наверно стремиться к Clean Architecture. Согласно ей, все приложение должны разделены на слои. Не должно быть зависимости от базы данных и внешнего сервиса, поэтому нужно будет добавлять еще 2 уровня абстрации (что - то наподобие LocalRepository (бд) и RemoteRepository (внешний сервис)