Я пытаюсь создать шаблон 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
}
}
Я предполагаю, что смешивание функции приостановки 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
}
}
}
}
Я надеюсь, что это поможет вам немного.
Это работает и кажется лучшим решением, которое я дал. Может быть, моя вина, что я плохо понял образец. Спасибо