Я использую пример Google Dagger 2 для создания собственного приложения. Я хочу отображать данные в RecyclerViev, для этого мне нужно загрузить асинхронные данные с удаленного сервера. Я не знаю, как обновить представление после получения ответа от сервера
Фрагмент
class RegionsFragment : DaggerFragment() {
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
private val viewModel by viewModels<RegionsViewModel> { viewModelFactory }
private lateinit var viewDataBinding: FragmentRegionsBinding
private lateinit var listAdapter : RegionsAdapter
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
viewDataBinding = FragmentRegionsBinding.inflate(inflater, container, false).apply {
viewmodel = viewModel
}
return viewDataBinding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
// Set the lifecycle owner to the lifecycle of the view
viewDataBinding.lifecycleOwner = this.viewLifecycleOwner
setupListAdapter()
viewModel.loadRegions(true)
}
private fun setupListAdapter() {
val viewModel = viewDataBinding.viewmodel
if (viewModel != null) {
listAdapter = RegionsAdapter(viewModel)
viewDataBinding.regionsList.adapter = listAdapter
listAdapter.onItemClick = {region ->
val intent = Intent(context, BudgetsActivity::class.java)
intent.putExtra("id_region", region.id)
startActivity(intent)
}
} else {
//Timber.w("ViewModel not initialized when attempting to set up adapter.")
}
}
}
ViewModel
class RegionsViewModel @Inject constructor(
private val regionsRepository: RegionsRepository
) : ViewModel() {
private val _items = MutableLiveData<List<Region>>().apply { value = emptyList() }
val items: LiveData<List<Region>> = _items
private val _dataLoading = MutableLiveData<Boolean>()
val dataLoading: LiveData<Boolean> = _dataLoading
private val isDataLoadingError = MutableLiveData<Boolean>()
init {
loadRegions(true)
}
fun loadRegions(forceUpdate: Boolean) {
_dataLoading.value = true
wrapEspressoIdlingResource {
viewModelScope.launch {
val regionsResult = regionsRepository.getRegions(forceUpdate)
if (regionsResult is Result.Success) {
val regions = regionsResult.data
val regionsToShow = ArrayList<Region>()
for (region in regions){
regionsToShow.add(region)
}
isDataLoadingError.value = false
_items.value = ArrayList(regionsToShow)
} else {
isDataLoadingError.value = false
_items.value = emptyList()
//showSnackbarMessage(R.string.loading_tasks_error)
}
_dataLoading.value = false
}
}
}
}
ListAdapter
class RegionsAdapter(private val viewModel: RegionsViewModel): ListAdapter<Region, ViewHolder>(TaskDiffCallback()) {
var onItemClick: ((Region) -> Unit)? = null
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position)
holder.itemView.setOnClickListener {
onItemClick?.invoke(item);
}
holder.bind(viewModel, item)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder.from(parent)
}
class ViewHolder private constructor(private val binding: RegionItemBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(viewModel: RegionsViewModel, item: Region) {
binding.viewmodel = viewModel
binding.region = item
binding.executePendingBindings()
}
companion object {
fun from(parent: ViewGroup): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = RegionItemBinding.inflate(layoutInflater, parent, false)
return ViewHolder(binding)
}
}
}
}
class TaskDiffCallback : DiffUtil.ItemCallback<Region>() {
override fun areItemsTheSame(oldItem: Region, newItem: Region): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Region, newItem: Region): Boolean {
return oldItem == newItem
}
}
Репозиторий
class DefaultRegionsRepository @Inject constructor(
@RegionsRemoteDataSource private val regionsRemoteDataSource: RegionsDataSource,
@RegionsLocalDataSource private val regionsLocalDataSource: RegionsDataSource,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) : RegionsRepository {
private var cachedRegions: ConcurrentMap<Int, Region>? = null
override suspend fun getRegions(forceUpdate: Boolean): Result<List<Region>> {
wrapEspressoIdlingResource {
return withContext(ioDispatcher) {
// Respond immediately with cache if available and not dirty
if (!forceUpdate) {
cachedRegions?.let { cachedTasks ->
return@withContext Success(cachedTasks.values.sortedBy { it.name })
}
}
val newRegions = fetchRegionsFromRemoteOrLocal(forceUpdate)
// Refresh the cache with the new tasks
(newRegions as? Success)?.let { refreshCache(it.data) }
cachedRegions?.values?.let { tasks ->
return@withContext Success(tasks.sortedBy { it.name })
}
(newRegions as? Success)?.let {
if (it.data.isEmpty()) {
return@withContext Success(it.data)
}
}
return@withContext Error(Exception("Illegal state"))
}
}
}
private suspend fun fetchRegionsFromRemoteOrLocal(forceUpdate: Boolean): Result<List<Region>> {
// Remote first
val remoteRegions = regionsRemoteDataSource.getRegions()
when (remoteRegions) {
//is Error -> Timber.w("Remote data source fetch failed")
is Success -> {
refreshLocalDataSource(remoteRegions.data)
return remoteRegions
}
else -> throw IllegalStateException()
}
// Don't read from local if it's forced
if (forceUpdate) {
return Error(Exception("Can't force refresh: remote data source is unavailable"))
}
// Local if remote fails
val localRegions = regionsLocalDataSource.getRegions()
if (localRegions is Success) return localRegions
return Error(Exception("Error fetching from remote and local"))
}
private fun refreshCache(tasks: List<Region>) {
cachedRegions?.clear()
tasks.sortedBy { it.id }.forEach {
cacheAndPerform(it) {}
}
}
private suspend fun refreshLocalDataSource(regions: List<Region>) {
//regionsLocalDataSource.deleteAllTasks()
for (region in regions) {
//regionsLocalDataSource.saveTask(region)
}
}
private fun cacheRegion(region: Region): Region {
val cachedRegion = Region(region.id, region.name, region.deptAmount, region.accrualAmount)
// Create if it doesn't exist.
if (cachedRegions == null) {
cachedRegions = ConcurrentHashMap()
}
cachedRegions?.put(cachedRegion.id, cachedRegion)
return cachedRegion
}
private inline fun cacheAndPerform(task: Region, perform: (Region) -> Unit) {
val cachedTask = cacheRegion(task)
perform(cachedTask)
}
}
DataSource - думаю, мне нужно обновить представление из источника данных
object RegionsRemoteDataSource :
RegionsDataSource {
private const val SERVICE_LATENCY_IN_MILLIS = 2000L
private var REGIONS_SERVICE_DATA = LinkedHashMap<Int, Region>(2)
private val client = OkHttpClient()
val url = "http://.."
override suspend fun getRegions(): Result<List<Region>> {
val request = Request.Builder().url(url).build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
e.printStackTrace()
}
override fun onResponse(call: Call, response: Response) {
response.use {
if (!response.isSuccessful) throw IOException("Unexpected code $response")
val gson = Gson()
val regionList: List<RegionRemote> = gson.fromJson(response.body?.string(), Array<RegionRemote>::class.java).toList()
val sortedRegionList = regionList.sortedWith(compareBy({ it.name }, { it.name }))
for(region in sortedRegionList) {
addRegion(
region.id,
region.name,
region.deptAmount + " тыс.р.",
region.accrualAmount + " тыс.р."
)
}
Update UI How??
}
}
})
/*client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
val gson = Gson()
val regionList: List<RegionRemote> = gson.fromJson(response.body?.string(), Array<RegionRemote>::class.java).toList()
val sortedRegionList = regionList.sortedWith(compareBy({ it.name }, { it.name }))
for(region in sortedRegionList) {
addRegion(
region.id,
region.name,
region.deptAmount + " тыс.р.",
region.accrualAmount + " тыс.р."
)
}
println(response.body!!.string())
}*/
val regions = REGIONS_SERVICE_DATA.values.toList()
delay(SERVICE_LATENCY_IN_MILLIS)
return Success(regions)
}
private fun addRegion(id: Int, name: String, deptAmount: String, accrualAmount: String) {
val newRegion = Region(id, name, deptAmount, accrualAmount)
REGIONS_SERVICE_DATA.put(newRegion.id, newRegion)
}
}