Как обновить данные в представлении Recycler, если сетевой запрос не был выполнен (ItemKeyedDataSource) - PullRequest
0 голосов

Я пытаюсь создать список пейджинга. Используйте Koin + Coroutine + Room + Retrofit в мультимодульном стиле архитектуры.

Как я могу перезагрузить данные в моем Recycler View, если во время прокрутки произошла ошибка?

Например, если загрузка не удалась нужно показать кнопку, и когда пользователь нажимает на эту кнопку - перезагрузите PagedList и отобразите его в recyclerView.

Я пытаюсь вызвать callback.onResult (твиты) внутри TweetsDataSource, но согласно документации это сделать невозможно:

Обратный вызов для ItemKeyedDataSource ItemKeyedDataSource.loadBefore (LoadParams, LoadCallback) и ItemKeyedDataSource.loadAfter (LoadParams, LoadCallback) для возврата данных. Обратный вызов может быть вызван только один раз, и будет вызван при повторном вызове. Он всегда действителен для метода загрузки источника данных, который принимает обратный вызов, чтобы сохранить sh обратный вызов и вызвать его позже. Это позволяет источникам данных быть полностью асинхронными и обрабатывать временные восстанавливаемые состояния ошибок (например, сетевая ошибка, которая может быть повторена).

, и я получаю ошибку: callback.onResult уже вызван, не может позвоните еще раз.

Вот мой код. Пожалуйста, помогите всем!)

Адаптер для представления переработчика:

class TweetsPagedListAdapter() :
PagedListAdapter<Tweet, TweetsPagedListAdapter.ViewHolder>(DiffUtilCallbaks()) {

interface OnItemClickListener {
    fun onItemClick(position: Int, view: View?)
}
var listener: OnItemClickListener? = null

override fun onCreateViewHolder( parent: ViewGroup, viewType: Int): ViewHolder {
    val view = LayoutInflater.from(parent.context).inflate(R.layout.item_tweet, parent, false)
    return ViewHolder(view,listener)
}


class ViewHolder(itemView: View, val listener: TweetsPagedListAdapter.OnItemClickListener?) :
    RecyclerView.ViewHolder(itemView),
    View.OnClickListener {
    val avatarCircleImageView = itemView.avatar_image_view
    val dateTextView = itemView.date_text_vew
    val nameTextView = itemView.name_text_view
    val screenNameTextView = itemView.short_name_text_view
    val mediaImageView = itemView.media_image_view
    val tweetTextView = itemView.tweet_text_view

    val favoriteCountTextView = itemView.fav_count_text_view
    val addToFavImageView = itemView.add_to_fav_image_view
    val retweetsCountTextView = itemView.retweets_count_text_view
    val retweetImageView = itemView.retweet_image_view

    init {
        avatarCircleImageView.setOnClickListener(this)
        addToFavImageView.setOnClickListener(this)
        retweetImageView.setOnClickListener(this)
        retweetsCountTextView.setOnClickListener(this)
        retweetImageView.setOnClickListener(this)
    }

    override fun onClick(p0: View?) {
        listener?.onItemClick(position = adapterPosition, view = p0)
    }

    fun bind(tweet: Tweet) {
        with(tweet) {
            GlideApp.with(avatarCircleImageView)
                .load(userImageUrl)
                .centerCrop()
                .into(avatarCircleImageView)

            GlideApp.with(mediaImageView)
                .asDrawable()
                .load(mediaUrl)
                .centerCrop()
                .into(mediaImageView)

            tweetTextView.text = text
            dateTextView.text = dateString
            nameTextView.text = userName
            screenNameTextView.text = userScreenName
            retweetsCountTextView.text = retweetsCount.toString()
            favoriteCountTextView.text = favoriteCount.toString()
        }
    }

}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    getItem(position)?.let { holder.bind(it) }
}}

DiffUtilCallbacks:

class DiffUtilCallbaks : DiffUtil.ItemCallback<Tweet>() {
override fun areItemsTheSame(oldItem: Tweet, newItem: Tweet): Boolean {
    return oldItem.tweetId == newItem.tweetId
}
override fun areContentsTheSame(oldItem: Tweet, newItem: Tweet): Boolean {
    return oldItem.equals(newItem)
}}

TweetsDataSource:

class TweetsDataSource(
var network: RemoteRepository,
var database: TwitterDatabaseImpl,
var scope: CoroutineScope):ItemKeyedDataSource<Long,Tweet>() {

val status = MutableLiveData<PageDownloadingStatus>()


override fun loadInitial(
    params: LoadInitialParams<Long>,
    callback: LoadInitialCallback<Tweet>
) {
    status.postValue(PageLoading)
    scope.launch {
        val dbApi = database.invoke().getTweetsApi()
        var tweets = listOf<Tweet>()
        var tweetsList = dbApi.selectTweets(limit = params.requestedLoadSize)
        if (tweetsList.isEmpty()) {
            val networkStatus = network.getTweets(count = params.requestedLoadSize)
            tweets = getTweets(networkStatus)
            dbApi.insertTweets(tweets)
            callback.onResult(tweets)
        } else {
            callback.onResult(tweetsList)
            status.postValue(PageSuccessful)
        }

    }
}

override fun loadAfter(params: LoadParams<Long>, callback: LoadCallback<Tweet>) {
    status.postValue(PageLoading)
    scope.launch {
        val dbApi = database.invoke().getTweetsApi()
        var tweets = listOf<Tweet>()
        var tweetsList =
            dbApi.selectNextTweets(limit = params.requestedLoadSize, tweetStartId = params.key)
        if (tweetsList.isEmpty()) {
            val networkStatus =
                network.getTweetsAfter(count = params.requestedLoadSize, maxId = params.key)
            tweets = getTweets(networkStatus)
            dbApi.insertTweets(tweets)
            callback.onResult(tweets)
        } else {
            callback.onResult(tweetsList)
            status.postValue(PageSuccessful)
        }

    }
}

override fun loadBefore(params: LoadParams<Long>, callback: LoadCallback<Tweet>) {
    status.postValue(PageLoading)
    scope.launch {
        val dbApi = database.invoke().getTweetsApi()
        scope.launch {
            var tweets = listOf<Tweet>()
            var tweetsList = dbApi.selectPreviousTweets(
                limit = params.requestedLoadSize,
                tweetStartId = params.key
            )
            if (tweetsList.isEmpty()) {
                val networkStatus = network.getTweetsBefore(
                    count = params.requestedLoadSize,
                    startId = params.key
                )
                tweets = getTweets(networkStatus)
                dbApi.insertTweets(tweets)
                callback.onResult(tweets)
            } else {
                callback.onResult(tweetsList)
                status.postValue(PageSuccessful)
            }
        }

    }
}

override fun getKey(item: Tweet) = item.tweetId

private fun getTweets(networkResponse: NetworkResponse<List<Tweet>>): List<Tweet> {
    when (networkResponse) {
        is Successfull -> {
            status.postValue(PageSuccessful)
            return networkResponse.httpResponse
        }
        is Faile -> {
            status.postValue(PageError)
            return emptyList()
        }
    }
}}

DataSourceFactory:

class TweetsDataSourceFactory(private val tweetsDataSource: TweetsDataSource)
: DataSource.Factory<Long,Tweet>() {

val liveData= MutableLiveData<TweetsDataSource>()
override fun create(): DataSource<Long, Tweet> {
    liveData.postValue(tweetsDataSource)
    return tweetsDataSource
}}

Репозиторий:

class TweetsRepository(private val tweetsDataSourceFactory: TweetsDataSourceFactory) {
fun getStatus() = Transformations.switchMap<TweetsDataSource,
        PageDownloadingStatus>(tweetsDataSourceFactory.liveData, TweetsDataSource::status)

fun getTweets(): LiveData<PagedList<Tweet>> {
    val result = tweetsDataSourceFactory
    return LivePagedListBuilder(result, pagedListConfig()).build()
}

private fun pagedListConfig() = PagedList.Config.Builder()
    .setInitialLoadSizeHint(10)
    .setPageSize(10)
    .build()

}

ViewModel:

class PagingTweetsViewModel(val repo: TweetsRepository) : ViewModel() {
val tweets by lazy { repo.getTweets() }
val status = repo.getStatus()

}

Фрагмент:

class TweetsFragment :
Fragment(),
TweetsPagedListAdapter.OnItemClickListener {

val viewodel by sharedViewModel<PagingTweetsViewModel>()
val adapter = TweetsPagedListAdapter()

override fun onAttach(context: Context) {
    super.onAttach(context)
    tweetsPagingModuleLoader.load()
}

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    return inflater.inflate(R.layout.fragment_tweets, container, false)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)[enter image description here][1]
    view.swiper.isRefreshing=true
    adapter.listener=this
    view.tweets_recycler_view.layoutManager = LinearLayoutManager(context)
    view.tweets_recycler_view.adapter = adapter

    viewodel.tweets.observe(viewLifecycleOwner, Observer{
        adapter.submitList(it)
    })

    viewodel.status.observe(viewLifecycleOwner, Observer{
        when(it){
            is PageSuccessful->{Log.d("pagnation_log","SUCCESSFUL")
                view.error_linear_layout.visibility=View.GONE
                view.swiper.isRefreshing=false
            }
            is PageError->{
                Log.d("pagnation_log","ERROR")
                view.error_linear_layout.visibility=View.VISIBLE
                view.swiper.isRefreshing=false
            }
            is PageLoading->{Log.d("pagnation_log","LOADING...")}
        }
    })
    view.reload_button.setOnClickListener {
        view.swiper.isRefreshing=true


    }
    swiper.setOnRefreshListener{
        view.swiper.isRefreshing=true

    }
}

override fun onItemClick(position: Int, view: View?) {
    Log.d("tweets_log","onItemClick() pressed on ${position}")
   }

}

...