#android #kotlin #viewmodel
Вопрос:
Мое приложение содержит базу данных рецептов коктейлей, которые оно загружает с помощью вызова api модернизации, все это работает хорошо. Чтобы сосредоточиться на том, в чем заключается моя проблема, мой вариант использования-пользователь, добавляющий коктейль в список. Это делается с помощью диалогового фрагмента, и здесь отображается диалоговый фрагмент, выполняется транзакция, диалоговый фрагмент исчезает, и база данных комнат обновляется. Однако фрагмент коктейля не получает обновления — если вы переместитесь в сторону и обратно, обновление будет видно, поэтому я знаю, что транзакция сработала должным образом. Одна вещь, которую япросто заметил, что если я поверну устройство, обновление также будет подхвачено.
Вот соответствующий раздел моего фрагмента:
class CocktailDetailFragment : BaseFragment<CocktailDetailViewModel, FragmentCocktailDetailBinding, CocktailDetailRepository>() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.cocktail.observe(viewLifecycleOwner, Observer {
when(it){
is Resource.Success -> {
updateCocktail(it.value)
}
}
})
}
private fun updateCocktail(cocktail: Cocktail) {
with(binding){
detailCocktailName.text = cocktail.cocktailName
//...
//this is the piece of functionality i'm expecting the LiveData observer to execute and change the drawable
if(cocktail.numLists > 0) {
detailCocktailListImageView.setImageResource(R.drawable.list_filled)
} else {
detailCocktailListImageView.setImageResource(R.drawable.list_empty)
}
}
}
override fun getViewModel() = CocktailDetailViewModel::class.java
override fun getFragmentBinding(
inflater: LayoutInflater,
container: ViewGroup?
) = FragmentCocktailDetailBinding.inflate(inflater,container,false)
override fun getFragmentRepository(): CocktailDetailRepository {
val dao = GoodCallDatabase(requireContext()).goodCallDao()
return CocktailDetailRepository(dao)
}
}
Базовый фрагмент:
abstract class BaseFragment<VM: BaseViewModel, B: ViewBinding, R: BaseRepository>: Fragment() {
protected lateinit var userPreferences: UserPreferences
protected lateinit var binding: B
protected lateinit var viewModel: VM
protected val remoteDataSource = RemoteDataSource()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
userPreferences = UserPreferences(requireContext())
binding = getFragmentBinding(inflater, container)
val factory = ViewModelFactory(getFragmentRepository())
viewModel = ViewModelProvider(this, factory).get(getViewModel())
return binding.root
}
abstract fun getViewModel() : Class<VM>
abstract fun getFragmentBinding(inflater: LayoutInflater, container: ViewGroup?): B
abstract fun getFragmentRepository(): R
}
ViewModelFactory:
class ViewModelFactory(
private val repository: BaseRepository
): ViewModelProvider.NewInstanceFactory() {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return when{
modelClass.isAssignableFrom(CocktailDetailViewModel::class.java) -> CocktailDetailViewModel(repository as CocktailDetailRepository) as T
else -> throw IllegalArgumentException("ViewModel class not found")
}
}
}
ViewModel:
class CocktailDetailViewModel(
private val repository: CocktailDetailRepository
): BaseViewModel(repository) {
private val _cocktail: MutableLiveData<Resource<Cocktail>> = MutableLiveData()
val cocktail: LiveData<Resource<Cocktail>>
get() = _cocktail
fun getCocktailByCocktailId(cocktailId: Int) = viewModelScope.launch {
_cocktail.value = Resource.Loading
_cocktail.value = repository.getCocktail(cocktailId)
}
}
Repository:
class CocktailDetailRepository(
private val dao: GoodCallDao
):BaseRepository(dao) {
suspend fun getCocktail(cocktailId: Int) = safeApiCall {
dao.getCocktail(cocktailId)
}
}
Безопасный вызов Api (я использую это, чтобы вызовы db/api выполнялись при вводе-выводе):
interface SafeApiCall {
suspend fun <T> safeApiCall(
apiCall: suspend () -> T
): Resource<T> {
return withContext(Dispatchers.IO) {
try {
Resource.Success(apiCall.invoke())
} catch (throwable: Throwable) {
when (throwable) {
is HttpException -> {
Resource.Failure(false, throwable.code(), throwable.response()?.errorBody())
}
else -> {
Resource.Failure(true, null, null)
}
}
}
}
}
}
Ресурс:
sealed class Resource<out T> {
data class Success<out T>(val value: T) : Resource<T>()
data class Failure(
val isNetworkError: Boolean,
val errorCode: Int?,
val errorBody: ResponseBody?
): Resource<Nothing>()
object Loading: Resource<Nothing>()
}
Дао:
@Query("SELECT * FROM cocktail c WHERE c.cocktail_id = :cocktailId")
suspend fun getCocktail(cocktailId: Int): Cocktail
Заранее благодарю вас за любую помощь! Учитывая проблему и то, как работает приложение, я считаю, что предоставил все соответствующие части, но, пожалуйста, сообщите, требуется ли для этого больше моего кода.
Комментарии:
1. здесь не нужно объяснять свой уровень опыта, хороший ответ должен быть понятен всем, и если вы не можете понять ответ, вы можете добавить к нему комментарии
2. ваша проблема, вероятно, нигде здесь не кроется, потому что этот код, о котором вы уже сказали, работает, верно ? вместо
CocktailDetailFragment
того , есть ли у васCocktailFragment
место, где вы ожидаете этого обновления, где оно не отображается ?3. В
viewModel.cocktail.observe
, почему бы не проверить, получили ли выLoading
илиFailure
?4. @a_local_nobody — это работает, но не обновляется при обновлении отображаемых данных
CocktailDetailFragment
. У меня нетCocktailFragment
, происходит отсутствие обновления LiveDataCocktailDetailFragment
. Как уже упоминалось, ожидаемое обновление произойдет без перехода от фрагмента, но поворота устройства.5. @rupinderjeet — в основном ленивый в данный момент, просто пытаюсь заставить работать все функции, мне нужно решать задачи по обработке этих состояний во многих местах.