How to fix android architecture components paging onItemAtEndLoaded get in loop? - android

I am trying to practice the android architecture components Paging
Local + Remote Datasource with Room, MVVM and LiveData
When i first time scroll the list(get remote data), it get into loop by onItemAtEndLoaded in PagedList.BoundaryCallback, but it scroll smooth when open the activity next time (get local data)
Here is my github link here!
Can anyone take a look and help me how to fix it, Thanks!
Activity
class PagingActivity : AppCompatActivity() {
lateinit var viewModel: PagingViewModel
lateinit var adapter: PagingAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_paging)
val factory = PagingViewModelFactory(PagingRepository(), application)
viewModel = ViewModelProviders.of(this,factory).get(PagingViewModel::class.java)
adapter = PagingAdapter()
recyclerView.adapter = adapter
viewModel.pagedListLiveData.observe(this, Observer {
adapter.submitList(it)
})
}
}
ViewModel
class PagingViewModel(repository: PagingRepository, application: Application) :
AndroidViewModel(application) {
val pagedListLiveData = repository.getDataItem(application)
}
Repository
class PagingRepository : PagingRepositoryCallback {
private lateinit var localDataSource: DataSource.Factory<Int, DataItem>
override fun getDataItem(application: Application): LiveData<PagedList<DataItem>> {
val pagedListLiveData: LiveData<PagedList<DataItem>> by lazy {
localDataSource = DataItemDbHelper(application).getRoomDataItemDao().getAllDataItem()
val config = PagedList.Config.Builder()
.setPageSize(25)
.setEnablePlaceholders(false)
.build()
LivePagedListBuilder(localDataSource, config)
.setBoundaryCallback(PagingBoundaryCallback(application))
.build()
}
return pagedListLiveData
}
}
interface PagingRepositoryCallback {
fun getDataItem(application: Application): LiveData<PagedList<DataItem>>
}
BoundaryCallback
class PagingBoundaryCallback(context: Context) :
PagedList.BoundaryCallback<DataItem>() {
private var page = 2
private val api = AllPlayerApi.api
private val dao = DataItemDbHelper(context).getRoomDataItemDao()
override fun onZeroItemsLoaded() {
super.onZeroItemsLoaded()
api.getAllPlayer().enqueue(createWebserviceCallback())
}
override fun onItemAtEndLoaded(itemAtEnd: DataItem) {
super.onItemAtEndLoaded(itemAtEnd)
api.getAllPlayer(page).clone().enqueue(createWebserviceCallback())
}
private fun createWebserviceCallback(): Callback<AllPlayerData> {
return object : Callback<AllPlayerData> {
override fun onFailure(call: Call<AllPlayerData>?, t: Throwable?) {
Log.d("Huang", " get player fail ")
}
override fun onResponse(call: Call<AllPlayerData>?, response: Response<AllPlayerData>) {
Log.d("Huang", " onResponse " + page)
response.body()!!.data!!.forEach {
it.imageUrl = "https://pdc.princeton.edu/sites/pdc/files/events/new-nba-logo-1.png"
}
insertItemsIntoDb(response)
page++
}
}
}
private fun insertItemsIntoDb(response: Response<AllPlayerData>) {
GlobalScope.launch {
response.body()!!.data!!.forEach {
dao.insert(it)
}
}
}
}

Logic for, If onItemAtEndLoaded get the same itemAtEnd , then do nothing.
var lastItemAtEnd:DataItem? = null
override fun onItemAtEndLoaded(itemAtEnd: DataItem) {
lastItemAtEnd?.timestamp?.apply{
if(itemAtEnd.timestamp==this){
return;
}
}
super.onItemAtEndLoaded(itemAtEnd)
api.getAllPlayer(page).clone().enqueue(createWebserviceCallback())
}

As your page size is 25 so Pagelist config should have setInitialLoadSizeHint as 25 for avoiding looping/unnecessary call of onItemAtEndLoaded method
val config = PagedList.Config.Builder()
.setPageSize(25)
.setInitialLoadSizeHint(25) //same as your page size
.setEnablePlaceholders(false)
.build()

I know it's been long but i just post the solution in case someone need.
you should register an observer for your adapter and listen for onItemRangeInserted event and if the start position of item range is zero just simply scroll adapter to zero position, this make your RecyclerView on first load stay in zero position and by the way you should set setPrefetchDistance value smaller than the setInitialLoadSizeHint.
This is the Java code for adapters observer
adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
#Override
public void onItemRangeInserted(int positionStart, int itemCount) {
super.onItemRangeInserted(positionStart, itemCount);
if(positionStart == 0)
recyclerView.scrollToPosition(positionStart);
}
});

Related

Using coroutines with Android Room database

For about a week now I've been working on adding coroutines to my Room database. I'm using Googles ViewModels with recyclers that need to update as the database is modified.
I finally got something apparently working but I am new to Android, and Kotlin (6 months) and so I know enough to know I don't know enough and would like to get feedback on any design issues. I wasn't able to find a simple example that fit my needs but was able to piece this together from what I did find on stackoverflow, Google, and YouTube.
Main Fragment:
class MainFragment : Fragment()
{
...
private fun recyclerSetup()
{
adapter = MainListAdapter(R.layout.main_list_item)
val recyclerView: RecyclerView? = view?.findViewById(R.id.main_list_recycler)
recyclerView?.layoutManager = LinearLayoutManager(context)
recyclerView?.adapter = adapter
}
private fun addObservers()
{
Log.i(TAG, MyUtils.header0(this, object {}))
viewModel.reminderEntries?.observe(viewLifecycleOwner) { reminderEntries ->
adapter?.setReminderEntryList(reminderEntries)
}
}
...
}
*** Edit #1 ***
(Added Adapter Code)
Adapter:
class MainListAdapter(private val productItemLayout:Int):RecyclerView.Adapter<MainListAdapter.ViewHolder>()
{
private var reminderEntryList:List<ReminderEntry>?=null
override fun onBindViewHolder(holder: ViewHolder, listPosition: Int) {
val itemQuestion = holder.itemQuestion
val itemTime = holder.itemCorrectAnswer
reminderEntryList.let {
val reminderEntry: ReminderEntry = it!![listPosition]
itemQuestion.text = reminderEntry.question
itemTime.text = reminderEntry.correctAnser
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder
{
val view = LayoutInflater.from(parent.context).inflate(productItemLayout, parent, false)
return ViewHolder(view)
}
fun setReminderEntryList(reminderEntries:List<ReminderEntry>)
{
reminderEntryList = reminderEntries
notifyDataSetChanged()
}
override fun getItemCount(): Int {
return if(reminderEntryList==null) 0 else reminderEntryList!!.size
}
class ViewHolder(itemView:View):RecyclerView.ViewHolder(itemView)
{
var itemQuestion:TextView = itemView.findViewById(R.id.question)
var itemCorrectAnswer:TextView = itemView.findViewById(R.id.correctAnswer)
}
}
ViewModel:
class MainViewModel(application: Application) : AndroidViewModel(application)
{
val repository: Repository = Repository(application)
val debugLogEntries = MutableLiveData<List<DebugLogEntry>>()
// ========================================================================
fun insertReminderLogEntry(reminderEntry: ReminderEntry)
{
viewModelScope.launch {
repository.insertReminderEntry(reminderEntry)
}
}
fun deleteAllReminderEntries()
{
viewModelScope.launch {
repository.deleteAllReminderEntries()
}
}
}
Repository:
class Repository(application: Application)
{
var reminderEntries: LiveData<List<ReminderEntry>>?
private var reminderEntryDao: ReminderEntryDao?
init
{
val db: MindfulnessDatabase? = MindfulnessDatabase.getDatabase(application)
reminderEntryDao = db?.reminderEntryDao()
reminderEntries = reminderEntryDao?.findAllReminderEntries()
}
// =============================================================================================
suspend fun insertReminderEntry(newDebugLogEntry: ReminderEntry)
{
reminderEntryDao?.insertReminderEntry(newDebugLogEntry)
}
suspend fun findAllReminderEntries(): LiveData<List<ReminderEntry>>?
{
return reminderEntryDao?.findAllReminderEntries()
}
suspend fun deleteAllReminderEntries()
{
reminderEntryDao?.deleteAllReminderEntries()
}
}
Dao:
#Dao
interface ReminderEntryDao
{
#Query("SELECT * FROM bluetooth_event_table")
fun findAllReminderEntries():LiveData<List<ReminderEntry>>
#Insert
#Transaction
suspend fun insertReminderEntry(reminderEntry: ReminderEntry?)
#Query("DELETE FROM bluetooth_event_table")
suspend fun deleteAllReminderEntries()
}
I'm still quite new to stackoverflow so if anything in my question needs clarification please ask in the comments.

How to observe LiveData<PagedList> with android paging in kotlin

I can't observe the LiveData<PagedList> change in activity, but the UI is updated(the list has grown in activty).
I can only observe it once when the livedata is initialized.
when the paging library call loadAfter method, the ui is updated, but didn't call pageList.observe{}
Firstly, I put the process of data request into the Kotlin Coroutines, I can't observe the data change, then I used asynchronous requests instead.It still didn't work.
Here is my code:
PlayActivity main code
private val commentAdapter =
object : BasePagedAdapter(diffCallback, this) {
// just bind recycleview item and corresponding view model. etc.
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_play)
binding.vm = vm
binding.lifecycleOwner = this
val workId = intent.getLongExtra(WORK_ID, 0)
vm.listComment(workId)
play_rv_comment.adapter = commentAdapter
/* herer is the problem*/
vm.commentList.observe(this, Observer {
/*only log once when called loadInitial*/
LogUtils.e("observe", it)
commentAdapter.submitList(it)
})
PlayViewModel
class PlayViewModel : BaseViewModel() {
var workId: Long = 0
// the data which I want to observe
lateinit var commentList: LiveData<PagedList<WorkComment>>
private val commentPageSize = 15
fun listComment(workId: Long) {
// init by DataSource.Factory in android paging library
commentList = BaseDataSourceFactory(workId).toLiveData(commentPageSize)
}
DataSource.Factory in Android paging
class BaseDataSourceFactory(
val workId: Long
) :
DataSource.Factory<Long, WorkComment>() {
override fun create(): DataSource<Long, WorkComment> {
return object : PageKeyedDataSource<Long, WorkComment>() {
override fun loadInitial(
params: LoadInitialParams<Long>,
callback: LoadInitialCallback<Long, WorkComment>
) {
try {
val res = RetrofitUtil.getInstanceWithJwt().create(WorkCommentApi::class.java)
.listComment(
workId, 1, params.requestedLoadSize
)
res.enqueue(object : retrofit2.Callback<TResult> {
override fun onFailure(call: Call<TResult>, t: Throwable) {
}
override fun onResponse(call: Call<TResult>, response: Response<TResult>) {
callback.onResult(
response.body()!!.toList(WorkComment::class.java),
null, 2)
}
})
} catch (e: SocketTimeoutException) {
ToastUtils.showShort("请稍候重试")
} catch (e: Exception) {
LogUtils.e(e.localizedMessage)
}
}
// called many times, but I can't observe the PagedList change
override fun loadAfter(
params: LoadParams<Long>,
callback: LoadCallback<Long, WorkComment>
) {
val res = RetrofitUtil.getInstanceWithJwt().create(WorkCommentApi::class.java)
.listComment(
workId, 1, params.requestedLoadSize
)
res.enqueue(object : retrofit2.Callback<TResult> {
override fun onFailure(call: Call<TResult>, t: Throwable) {
}
override fun onResponse(call: Call<TResult>, response: Response<TResult>) {
callback.onResult(
response.body()!!.toList(WorkComment::class.java),
params.key + 1
)
}
})
}
override fun loadBefore(
params: LoadParams<Long>,
callback: LoadCallback<Long, WorkComment>
) {
}
}
}
}
Retrofit Api
interface WorkCommentApi {
/**
* list comment
*/
#GET("public/work/comment")
fun listComment(#Query("workId") workId: Long, #Query("current") current: Long, #Query("size") size: Int): Call<TResult>
}
I want to know what should I do to observe the LiveData<PagedList> change
This is happening because each time you call vm.listComment(workId), the object you first bound in activity is killed and new object is created.
You can use Transformations with MediatorLiveData.
Activity:
viewModel.logout().observe(this, Observer {
// do here
})
ViewModel:
class RepackViewModel(app: Application) : BaseViewModel(app) {
// IMPORTANT - Mediator
val logout = MediatorLiveData<PagedList<WorkComment>>()
fun logout() : LiveData<PagedList<WorkComment>> = logout
init {
// IMPORTANT - passes repo update to activity
logout.addSource(repo.getLogoutResponse()) { logout.postValue(it) }
}
}
Repository:
class BaseRepository(val app: Application) {
private val logout = MutableLiveData<PagedList<WorkComment>>()
fun getLogoutResponse(): LiveData<PagedList<WorkComment>> = logout
override fun create(): DataSource<Long, WorkComment> {
//when you get your data
logout.value = // your value
}
You need to have your work id be mutable data to be observed by the transformation. so whenever you update your work, id, it will fetch comments. Like Thus...
ViewModel:
val workIdMutableLiveData: MutableLiveData<Int> = MutableLiveData(workId)
//This performs the meat of the work to display the items in the recyclerview
var commentsList = Transformations.switchMap(workIdMutableLiveData) { workId ->
val config = PagedList.Config.Builder()
.setEnablePlaceholders(false)
.setPageSize(pagingLimit)
.build()
val pagedListBuilder = LivePagedListBuilder<Long, WorkComment>(BaseDataSourceFactory(workId), config)
pagedListBuilder.build()
}
Then in your activity, observe
yourViewModel.commentsList.observe(this, Observer { list ->
list ?: return#Observer
adapter.submitList(list)
yourRecyclerView.adapter = adapter
})
Whenever you update the workIdMutableLiveData by doing a
workIdMutableLiveData.postValue(workId)
...The recyclerview will update. Your recyclerview must inherit from PagedListAdapter.
After testing, I knew list couldn't be observed when it has inner data change, like add(), remove(). etc.
It only be observed when its reference has been changed, like create or assignment operation:
list.value = null
So I couldn't observe the data change of LiveData<List>

MutableLiveData won't trigger loadAfter to fetch from Android ROM using PagedList

I have 70 itens stored on my ROM and I would like to fetch a paged amount of 15. I read many posts so far with related issues, however none of them were useful for me.
Some possible causes for loadAfter not being triggered:
Solution 1 : call getItem inside onBindViewHolder
Solution 2 : call submitList to PagedListAdapter
Solution 3 : replace ListAdapter with PagedListAdapter
I assume DataBinding is fine since everything works without trying to paging.
I'm mocking my data source to understand what's happening. Some functions are suspended 'cause they should have data coming from ROM which requires it. My code state be like:
ADAPTER
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
getItem(position).let { wkda ->
with(holder) {
wkda?.apply { bind(createOnClickListener(this)) }
}
}
}
FRAGMENT
vm.manufacturers.observe(viewLifecycleOwner) { manufacturers ->
adapter.submitList(manufacturers)
}
VIEWMODEL
var manufacturers: MutableLiveData<PagedList<WKDA>> = MutableLiveData()
init {
viewModelScope.launch {
repository.getManufacturers(manufacturers)
}
}
REPOSITORY
suspend fun getManufacturers(manufacturers: MutableLiveData<PagedList<WKDA>>) {
withContext(Dispatchers.IO) {
manufacturers.postValue(ManufacturerPagedList.
getInstance().
fetchPage())
}
}
MANUFACTURER PAGED LIST
private val executor = ManufacturerExecutor()
private val paginationConfig: PagedList.Config = PagedList.Config.Builder()
.setPageSize(PAGE_SIZE)
.setPrefetchDistance(FETCH_DISTANCE)
.setEnablePlaceholders(false)
.build()
companion object {
#Volatile
private var instance: ManufacturerPagedList? = null
fun getInstance() = instance ?: synchronized(this) {
ManufacturerPagedList().also {
instance = it
}
}
}
fun fetchPage(): PagedList<WKDA> = PagedList.Builder<Int, WKDA>(
MockDataSource(),
paginationConfig)
.setInitialKey(INITIAL_KEY)
.setFetchExecutor(executor)
.setNotifyExecutor(executor)
.build()
}
DATASOURCE
class MockDataSource : PageKeyedDataSource<Int, WKDA>() {
override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, WKDA>) {
callback.onResult(List(20) { generatePost(params.requestedLoadSize) }.toList(), -1, 1)
}
override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, WKDA>) {
callback.onResult(List(20) { generatePost(params.key) }.toList(), params.key + 1)
}
override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Int, WKDA>) {
callback.onResult(List(20) { generatePost(params.key) }.toList(), params.key - 1)
}
private fun generatePost(key: Int): WKDA {
return WKDA("name", "author $key")
}
}
CONSTANTS
const val INITIAL_KEY: Int = 0
const val PAGE_SIZE: Int = 15
const val FETCH_DISTANCE: Int = 1
What am I missing here?
After check: loadAfter was called properly. The problem was model itself:
wkda.id had always the same "name" value
DiffCallback compared old list of objects with the new one and didn't see differences, so the item "duplicates" weren't added to the adapter

LiveData is not updating the View consistently

The recycleView isn't updating the result from the network on initial loading.
RecycleView:
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
mRecyclerAdapter = MovieListAdapter(context)
rvMovieList.apply {
// Dedicated layouts for Screen Orientation
if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
layoutManager = LinearLayoutManager(context)
} else {
layoutManager = GridLayoutManager(context, 2)
}
adapter = mRecyclerAdapter
}
}
and listening to the network result using LiveData from ViewModel.
LiveData listening snippet the Fragment below:
override fun onResume() {
super.onResume()
// Listen to data change
viewModel.getMovies().observe(this, mMovieListObserver)
}
private val mMovieListObserver: Observer<PagedList<MovieItem>> = Observer { movieItems ->
Log.d(TAG, "MovieItems: ${movieItems.size}")
showEmptyList(movieItems?.size == 0)
mRecyclerAdapter.submitList(movieItems)
}
private fun showEmptyList(isEmpty: Boolean) {
tvEmptyListView.visibility = if (isEmpty) View.VISIBLE else View.GONE
rvMovieList.visibility = if (isEmpty) View.GONE else View.VISIBLE
}
override fun onPause() {
viewModel.getMovies().removeObserver(mMovieListObserver)
super.onPause()
}
The irony is, the result populates the recycleView on subsequent loads. I feel the LiveData isn't working as expected. The expectation while introducing the emptyView was to show/hide the recycleView/EmptyView based on the result from the network.
ViewModel pasted below:
class MovieListViewModel : ViewModel() {
private val PAGE_SIZE = 10
internal var movies: LiveData<PagedList<MovieItem>>
init {
val dataSourceFactory = MovieDataSourceFactory()
val pagedListConfig = PagedList.Config.Builder()
.setInitialLoadSizeHint(PAGE_SIZE)
.setPageSize(PAGE_SIZE)
.setEnablePlaceholders(true)
.build()
movies = LivePagedListBuilder(dataSourceFactory, pagedListConfig)
// .setBoundaryCallback() TODO
.build()
}
fun getMovies(): LiveData<PagedList<MovieItem>> {
return movies
}
}
Thanks for the time, appreciate any inputs to the solution or best practices. Thanks.
Repo: https://gitlab.com/faisalm/MovieDirect
////---
Updated the DataSourceFactory and DataSource.
class MovieDataSourceFactory : DataSource.Factory<Int, MovieItem>() {
private val mutableLiveData = MutableLiveData<MovieDataSource>()
override fun create(): DataSource<Int, MovieItem> {
val dataSource = MovieDataSource()
mutableLiveData.postValue(dataSource)
return dataSource
}
}
class MovieDataSource internal constructor() : PageKeyedDataSource<Int, MovieItem>() {
private val movieDbService: MovieDbService = RetrofitFactory.create()
override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, MovieItem>) {
val moviesListCall = movieDbService.fetchLatestMoviesPaged(Constants.API_KEY, 1)
moviesListCall.enqueue(object : Callback<MoviesList> {
override fun onResponse(call: Call<MoviesList>, response: Response<MoviesList>) {
if (response.isSuccessful) {
val moviesLists = response.body()?.results
callback.onResult(moviesLists!!, 1, 2)
}
}
override fun onFailure(call: Call<MoviesList>, t: Throwable) {}
})
}
override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Int, MovieItem>) {}
override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, MovieItem>) {
val moviesListCall = movieDbService.fetchLatestMoviesPaged(Constants.API_KEY, params.key)
moviesListCall.enqueue(object : Callback<MoviesList> {
override fun onResponse(call: Call<MoviesList>, response: Response<MoviesList>) {
if (response.isSuccessful) {
val moviesLists = response.body()?.results
callback.onResult(moviesLists!!, params.key + 1)
}
}
override fun onFailure(call: Call<MoviesList>, t: Throwable) {}
})
}
}
I think the issue is the way you're adding and removing the observer for the liveData.
Instead of adding in onResume and removing in onPause, just observe it in onActivityCreated in the Fragment. LiveData's observe method takes in a LifeCycleOwner (which is what you're passing with this in the Fragment), and it'll take care of making sure it's observing at the correct time in that lifecycle.
So remove these lines:
viewModel.getMovies().removeObserver(mMovieListObserver) viewModel.getMovies().addObserver(this, mMovieListObserver)
and add this:
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel.getMovies().observe(this, Observer { movieItems ->
Log.d(TAG, "MovieItems: ${movieItems.size}")
showEmptyList(movieItems?.loadedCount == 0)
mRecyclerAdapter.submitList(movieItems)
})
}

Android RxJava Retrofit MVVM RecyclerView does not show up

I am implementing a RecyclerView in a fragment. The XML should be correct since I tried it with my hard-coded data, and the API call does return the correct json data from the server according to the Log in the console. The problem is that the RecyclerView adapter does not get any data from my Observable. Here is my implementation
In PostDataService interface I used Retrofit to get an Observable>
interface PostDataService {
#GET(".")
fun getPosts(
#Query(value = "offset") offset: Long = 0,
#Query(value = "limit") limit: Long = 10,
#Query(value = "subscribedOnly") subscribedOnly: Boolean = false
): Observable<List<Post>>
companion object {
val retrofit: PostDataService = Retrofit.Builder()
.baseUrl("http:aws/api/post/")
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(MoshiConverterFactory.create())
.client(client)
.build()
.create(PostDataService::class.java)
}
}
In PostListRepository, I used RxJava operators to get the LiveData
class PostListRepository {
private val postListLiveData: MutableLiveData<List<Post>> = MutableLiveData()
private val compositeDisposable: CompositeDisposable = CompositeDisposable()
fun getPostListLiveData(): MutableLiveData<List<Post>> {
val postList: MutableList<Post> = ArrayList()
val retrofitInstance = PostDataService.retrofit
val postListObservable = retrofitInstance.getPosts()
compositeDisposable.add(
postListObservable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMapIterable { it }
.subscribeWith(object : DisposableObserver<Post>() {
override fun onError(e: Throwable) {
// if some error happens in our data layer our app will not crash, we will
// get error here
}
override fun onNext(post: Post) {
postList.add(post)
}
override fun onComplete() {
postListLiveData.postValue(postList)
}
})
)
return postListLiveData
}
fun clear() {
compositeDisposable.clear()
}
}
In PostListViewModel, I passed the LiveData from the repository into this ViewModel.
class PostListViewModel : ViewModel() {
private var postListRepository: PostListRepository = PostListRepository()
fun getPostList(): MutableLiveData<List<Post>> {
return postListRepository.getPostListLiveData()
}
fun clear() {
postListRepository.clear()
}
}
Here is the Fragment that contains the RecyclerView. I think the .oberserve function in getPostList() is not called since I tried Log it but got nothing.
class PostListFragment : Fragment() {
private lateinit var recyclerView: RecyclerView
private lateinit var swipeLayout: SwipeRefreshLayout
private lateinit var postListViewModel: PostListViewModel
private val postListAdapter = PostRecyclerViewAdapter()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val rootView = inflater.inflate(R.layout.view_post_list, container, false)
recyclerView = rootView.findViewById(R.id.postRecyclerView)
recyclerView.apply {
setHasFixedSize(true)
addItemDecoration(VerticalSpaceItemDecoration(36))
layoutManager = LinearLayoutManager(context)
adapter = postListAdapter
}
postListViewModel = ViewModelProviders.of(this).get(PostListViewModel::class.java)
getPostList()
swipeLayout = rootView.findViewById(R.id.swipeLayout)
swipeLayout.setColorSchemeResources(R.color.colorPrimary)
swipeLayout.setOnRefreshListener {
getPostList()
swipeLayout.isRefreshing = false
}
return rootView
}
override fun onDestroy() {
super.onDestroy()
postListViewModel.clear() // to avoid memory leak
}
private fun getPostList() {
postListViewModel.getPostList().observe(this, Observer<List<Post>> { resource ->
postListAdapter.setPostList(resource)
postListAdapter.notifyDataSetChanged()
})
}
}
Here is the adapter for the RecyclerView:
class PostRecyclerViewAdapter : RecyclerView.Adapter<PostViewHolder>() {
private var postList: List<Post> = ArrayList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PostViewHolder {
// create a new view
val postView = PostView(parent.context)
// set the view's size, margins, paddings and layout parameters
return PostViewHolder.from(postView)
}
override fun getItemCount(): Int = postList.size
override fun onBindViewHolder(holder: PostViewHolder, position: Int) {
val curPost = postList[position]
holder.postView.apply {
setPostOwnerDisplayName(curPost.content.userDisplayedName)
setPostOwnerRole(curPost.content.role)
setPostOwnerAvatar(R.mipmap.ic_launcher_round)
setPostText(curPost.content.text)
setPostImage(curPost.content.smallMediaPaths[0])
setLikeState(curPost.liked)
setBookmarkState(curPost.bookmarked)
}
}
fun setPostList(postList: List<Post>) {
this.postList = postList
}
}
As I mentioned above, I think the .oberserve function in getPostList() in PostListFragment is not called since I tried Log it but got nothing, so there is no data passed into the RecyclerView. Can anyone help me find the reason why it's not being called, or why it's not getting the data from the ViewModel?
I wouldn't think of this is related to your issue, but your code has potential problems.
To move observe part to onActivityCreated would be better to ensure view is created.
when your fragment view is re-created, a new Observer will be added, while previous one still alive, because your Observer is anonymous. So, you have to manage the observers to prevent it.
I just found out that I forgot to catch the exception in RxJava onNext() in case to get the moshi serialization error. After getting that, I got some moshi conversion errors.
Posted it in case anyone carelessly forgot to catch the moshi error.
Thanks!

Categories

Resources