I'm trying to get device contacts using paging library (3.0.0-alpha02 version). But when I try to invalidate the data source I get this exception:
java.lang.IllegalArgumentException: List size + position too large, last item in list beyond totalCount.
Here's my code.
Data Class:
data class Contact(
val id: Long,
val lookupUri: Uri,
val name: String?,
val photoUri: Uri?
)
DataSource:
class ContactsDataSource(private val contentResolver: ContentResolver) : PositionalDataSource<Contact>() {
override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<Contact>) {
val totalSize = getRowsNumbers()
val firstLoadPosition = computeInitialLoadPosition(params, totalSize)
val firstLoadSize = computeInitialLoadSize(params, firstLoadPosition, totalSize)
val list = contentResolver.query(
ContactsContract.Contacts.CONTENT_URI,
CONTACT_PROJECTION,
null,
null,
"${CONTACT_PROJECTION[2]} ASC LIMIT $firstLoadSize OFFSET $firstLoadPosition"
)?.use {
getRows(it)
}!!
callback.onResult(list, firstLoadPosition, firstLoadSize)
}
override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<Contact>) {
val list = contentResolver.query(
ContactsContract.Contacts.CONTENT_URI,
CONTACT_PROJECTION,
null,
null,
"${CONTACT_PROJECTION[2]} ASC LIMIT ${params.loadSize} OFFSET ${params.startPosition}"
)?.use {
getRows(it)
}!!
callback.onResult(list)
}
private fun getRowsNumbers() = contentResolver.query(
ContactsContract.Contacts.CONTENT_URI,
arrayOf(BaseColumns._ID),
null,
null,
null
)?.use {
it.count
} ?: 0
private fun getRows(cursor: Cursor) = arrayListOf<Contact>().apply {
cursor.apply {
while (moveToNext()) {
val id = getLong(getColumnIndex(CONTACT_PROJECTION[0]))
add(
Contact(
id,
ContactsContract.Contacts.getLookupUri(
id,
getString(getColumnIndex(CONTACT_PROJECTION[1]))
),
getString(getColumnIndex(CONTACT_PROJECTION[2])),
getString(getColumnIndex(CONTACT_PROJECTION[3]))?.toUri()
)
)
}
}
}.toList()
companion object {
private val CONTACT_PROJECTION = arrayOf(
ContactsContract.Contacts._ID,
ContactsContract.Contacts.LOOKUP_KEY,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.Contacts.PHOTO_URI
)
}
}
DataSourceFactory:
class ContactsDataSourceFactory(private val contentResolver: ContentResolver) :
DataSource.Factory<Int, Contact>() {
override fun create(): DataSource<Int, Contact> {
return ContactsDataSource(contentResolver)
}
}
ViewModel:
class ContactsViewModel(app: Application) : AndroidViewModel(app) {
lateinit var currentDataSource: PagingSource<Int, Contact>
val contacts = Pager(
PagingConfig(
pageSize = 20
)
) {
ContactsDataSourceFactory(app.contentResolver).asPagingSourceFactory().invoke().also {
currentDataSource = it
}
}
.flow
.cachedIn(viewModelScope)
}
RecyclerView Adapter:
private val CONTACT_DIFF_UTIL = object : DiffUtil.ItemCallback<Contact>() {
override fun areItemsTheSame(oldItem: Contact, newItem: Contact): Boolean =
oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: Contact, newItem: Contact): Boolean =
oldItem == newItem
}
class ContactsRvAdapter(private val onLongClick: () -> Unit) :
PagingDataAdapter<Contact, ContactsRvAdapter.Holder>(CONTACT_DIFF_UTIL) {
inner class Holder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val ivContactPhoto = itemView.findViewById<ImageView>(R.id.ivContactPhoto)
private val tvContactName = itemView.findViewById<TextView>(R.id.tvContactName)
fun bind(contact: Contact?) {
contact ?: return
val photoUri = contact.photoUri
if (photoUri != null) {
ivContactPhoto.setImageURI(photoUri)
} else {
ivContactPhoto.setImageResource(R.drawable.ic_baseline_account_circle_60)
}
tvContactName.text = contact.name
itemView.setOnLongClickListener {
onLongClick()
return@setOnLongClickListener true
}
}
}
override fun onBindViewHolder(holder: Holder, position: Int) {
holder.bind(getItem(position))
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.contacts_rv_row_item, parent, false)
return Holder(view)
}
}
Activity:
@ExperimentalCoroutinesApi
class MainActivity : AppCompatActivity() {
private val contactsViewModel: ContactsViewModel by viewModels()
private val contactsRvAdapter = ContactsRvAdapter {
contactsViewModel.currentDataSource.invalidate()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<RecyclerView>(R.id.rvContacts).apply {
adapter = contactsRvAdapter
addItemDecoration(
DividerItemDecoration(
this@MainActivity,
DividerItemDecoration.VERTICAL
)
)
}
lifecycleScope.launch {
contactsViewModel.contacts.collectLatest {
contactsRvAdapter.submitData(it)
}
}
}
}
I read generated code by room database and source code of LimitOffsetDataSource
class and tried to follow the exact steps to build my own data source. Am I missing something? Any help or solution is appreciated.