Я пытаюсь создать список пейджинга. Используйте 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}")
}
}