I am trying to get contacts' data (name, number and photo URI), and it's too slow and showing a black screen for seconds before viewing the UI. When trying to do it in a separate thread, the error below appears to move the process into the main thread. If anyone can help with this, Thank you...
- android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views
private fun checkPermission() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) !=
PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_CONTACTS), 100)
} else {
getContactList()
}
}
private fun getContactList() {
val uri: Uri = ContactsContract.Contacts.CONTENT_URI
// Sort by ascending
val sort: String = ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME+" ASC"
// Initialize Cursor
val cursor: Cursor = this.contentResolver.query(uri, null, null, null, sort)!!
// Check condition
if (cursor.count > 0) {
while (cursor.moveToNext()) {
val name: String = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Data.DISPLAY_NAME))
val img = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.PHOTO_URI))
val phoneUri: Uri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI
// Initialize phone cursor
val phoneCursor: Cursor = this.contentResolver.query(phoneUri,
null, null, null, sort)!!
// Check condition
if (phoneCursor.moveToNext()) {
val number: String = phoneCursor.getString(phoneCursor
.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.NUMBER))
// Initialize Contact Model
val model = ContactModel(name, number, img)
contactArr.add(model)
groupListAdapter.notifyDataSetChanged()
// Close Phone Cursor
phoneCursor.close()
}
}
// Close Cursor
cursor.close()
}
}
lifecycleScope.launch { checkPermission() }
class ContactModel(var name: String?, var num: String?, var img: String?)
RecyclerView adapter
class GroupsListAdapter(private var groupList: ArrayList<ContactModel>): RecyclerView.Adapter<GroupsListAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.group_list_items_rec, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val currentContact = groupList[position]
holder.name.text = currentContact.name
holder.num.text = currentContact.num
if (currentContact.img != null) {
Glide.with(holder.itemView.context).load(currentContact.img).into(holder.img)
} else {
Glide.with(holder.itemView.context).load(R.drawable.placeholder_p).into(holder.img)
}
}
override fun getItemCount(): Int {
return groupList.size
}
fun createList(addedList: ArrayList<ContactModel>){
this.groupList = addedList
}
class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView){
private val binding = GroupListItemsRecBinding.bind(itemView)
val img: CircleImageView = binding.contactThumbnail
val name: TextView = binding.contactName
val num: TextView = binding.contactNum
}
}
You're trying to update the whole adapter on each item. It would make better sense to collect everything into the list and then update the adapter.
You can run most of your work on the IO dispatcher in case it takes a while to load the contacts, and touch the views using the Main dispatcher. You can switch dispatchers using
withContext.You're also failing to close the cursors under all conditions--you're doing it inside an if statement, so if the condition is false, you are leaking the cursor.
Version of your function with the above problems fixed: