1

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.

Soroush Lotfi
  • 452
  • 4
  • 14

0 Answers0