Android MVVM с сервислокатором

Я пытаюсь создать шаблон mvvm с репозиторием и сервислокатором, чтобы использовать фиктивные или удаленные вызовы, это зависит от вкуса. Что происходит сейчас, так это то, что мои liveData не обновляются после того, как я получаю ответ от сервера. Так что пока у меня всегда пустой список.

Я использую этот пример Google, чтобы попытаться сделать это. образец

Мой код ниже, используя удаленный serviceLocator Ценю вашу помощь.

class TestActivity : AppCompatActivity(){

private val viewModel = TestViewModel(ServiceLocator.provideTasksRepository())
private lateinit var  binding : TestBinding

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    binding = DataBindingUtil.setContentView(this, R.layout.test)
    binding.viewmodel = viewModel

    setupRecyclerView()
}

private fun setupRecyclerView() {
    binding.viewmodel?.run {
        binding.recyclerViewTest.adapter = TestAdapter(this)
    }
}

}

class TestAdapter(private val viewModel : TestViewModel) : ListAdapter<ResponseEntity, TestAdapter.TestViewHolder>(TestDiffCallback()) {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = TestViewHolder.from(parent)

override fun onBindViewHolder(holder: TestViewHolder, position: Int) {
    val item = getItem(position)
    holder.bind(viewModel, item)
}

class TestViewHolder private constructor(val binding: ItemTestBinding) :
    RecyclerView.ViewHolder(binding.root) {

    fun bind(viewModel: TestViewModel, item: ResponseEntity) {

        binding.viewmodel = viewModel
        binding.game = item
        binding.executePendingBindings()
    }

    companion object {
        fun from(parent: ViewGroup): TestViewHolder {
            val layoutInflater = LayoutInflater.from(parent.context)
            val binding = ItemTestBinding.inflate(layoutInflater, parent, false)

            return TestViewHolder(binding)
        }
    }
}

}

<?xml version = "1.0" encoding = "utf-8"?>

<data>

    <import type = "android.view.View" />

    <import type = "androidx.core.content.ContextCompat" />

    <variable
        name = "game"
        type = "com.test.ResponseEntity" />

    <variable
        name = "viewmodel"
        type = "com.test.TestViewModel" />

</data>

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width = "match_parent"
    android:layout_height = "match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id = "@+id/recycler_view_test"
        app:layoutManager = "androidx.recyclerview.widget.LinearLayoutManager"
        app:items = "@{viewmodel.items}"
        android:layout_width = "match_parent"
        android:layout_height = "match_parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
class MyRepository(private val testRemoteDataSource: IDataSource) :
ITestRepository {

override suspend fun getList() = testRemoteDataSource.getList()

}

class TestViewModel(private val testRepository: MyRepository) : ViewModel() {

private var _items = MutableLiveData<List<ResponseEntity>>()
val items: LiveData<List<ResponseEntity>> = _items

init {
    refreshList()
}

private fun refreshList() {
    viewModelScope.launch {
        _items = testRepository.getList()
    }
}

}

object ServiceLocator {

var testRepository: MyRepository? = null

fun provideTasksRepository(): MyRepository {
    synchronized(this) {
        return testRepository ?: createTestRepository()
    }
}

private fun createTestRepository(): MyRepository {
    val newRepo = MyRepository(
        MyRemoteDataSource(RetrofitClient.apiInterface)
    )
    testRepository = newRepo
    return newRepo
}

}

class MyRemoteDataSource(private val retroService: IService) :
IDataSource {

private var responseEntityLiveData: MutableLiveData<List<ResponseEntity>> =
    MutableLiveData<List<ResponseEntity>>()

override suspend fun getGames(): MutableLiveData<List<ResponseEntity>> {

    retroService.getList()
        .enqueue(object : Callback<List<ResponseEntity>> {
            override fun onFailure(
                call: Call<List<ResponseEntity>>,
                t: Throwable
            ) {
                responseEntityLiveData.value = emptyList()
            }

            override fun onResponse(
                call: Call<List<ResponseEntity>>,
                response: Response<List<ResponseEntity>>
            ) {
                responseEntityLiveData.value = response.body()
            }
        })

    return responseEntityLiveData
}

}

2
0
217
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я предполагаю, что смешивание функции приостановки Coroutines с Retrofit и LiveData приводит к некоторому побочному эффекту.

У меня нет единого решения, но некоторые моменты могут вам помочь.

В общем, я бы не стал смешивать LiveData с функциями приостановки. LiveData — это концепция кэширования данных для слоя UI/ViewModel. Нижним уровням не нужно знать что-то вроде конкретных вещей Android, таких как LiveData. Подробнее здесь

В вашем репозитории или источнике данных можно использовать либо функцию приостановки, которая возвращает одно значение, либо поток сопрограмм, который может выдавать более одного значения. Затем в вашей ViewModel вы можете сопоставить эти результаты с вашими LiveData.

Источник данных

В вашем источнике данных вы можете использовать suspendCoroutine или suspendCancellableCoroutine для подключения Retrofit (или любого другого интерфейса обратного вызова) с Coroutines:

class DataSource(privat val retrofitService: RetrofitService) {

    /**
     * Consider [Value] as a type placeholder for this example
     */
    fun suspend fun getValue(): Result<Value> = suspendCoroutine { continuation ->
      retrofitService.getValue().enqueue(object : Callback<Value> {
        override fun onFailure(call: Call<List<ResponseEntity>>,
                               throwable: Throwable) {
          continuation.resume(Result.Failure(throwable)))
        }

        override fun onResponse(call: Call<List<ResponseEntity>>,
                                response: Response<List<ResponseEntity>>) {
          continuation.resume(Result.Success(response.body()))
        }
      }
   }
}

Обертка результатов

Вы можете обернуть ответ своим собственным типом Result, например:

sealed class Result<out T> {

    /**
     * Indicates a success state.
     */
    data class Success<T>(val data: T) : Result<T>()

    /**
     * Indicates an error state.
     */
    data class Failure(val throwable: Throwable): Result<Nothing>
}

Я исключаю репозиторий из этого примера и вызываю непосредственно источник данных.

ViewModel

Теперь в вашем ViewModel вы можете запустить сопрограмму, получить Result и сопоставить ее с LiveData.

class TestViewModel(private val dataSource: DataSource) : ViewModel() {
    private val _value = MutableLiveData<Value>>()
    val value: LiveData<Value> = _value

    private val _error = MutableLiveData<String>()
    val error: LiveData = _error

    init {
        getValue()
    }

    private fun getValue() {
      viewModelScope.launch {
        val result: Result<Value> = dataSource.getValue()
        
        // check wether the result is of type Success or Failure
        when(result) {
          is Result.Success -> _value.postValue(result.data)
          is Result.Failure -> _error.value = throwable.message
        }
      }
    }
}

Я надеюсь, что это поможет вам немного.

Это работает и кажется лучшим решением, которое я дал. Может быть, моя вина, что я плохо понял образец. Спасибо

user1851366 20.12.2020 16:53

Другие вопросы по теме