I have a button to open and select a contact for some of it's specific data - name and number using ActivityResultContracts.PickContact. Using the guide from this page and some modifications, I was able to retrieve the contact name successfully.
I have this in my manifest file for permission
<uses-permission android:name="android.permission.READ_CONTACTS" />
Issue is my app crashes whenever I select the contact to get it's number.
Exception thrown is:
java.lang.IllegalArgumentException: Invalid column data1
Can I have more knowledge about what is going on and how to make it work as intended?
///THE COMMENTED LINES ARE FOR CONTACT'S NAME AND IT WORKED PERFECTLY (COMMENT THE NUMBER LINES FOR TEST)
#Composable
#Preview
fun openAndSelectContact() {
val context = LocalContext.current
val launchContact = rememberLauncherForActivityResult(
contract = ActivityResultContracts.PickContact()
) {
val projection: Array<String> = arrayOf(
// ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Phone.NUMBER
)
context.contentResolver.query(
it!!,
projection,
null,
null,
null
)
.use { cursor ->
if (cursor!!.moveToFirst()) {
val numberIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)
val number = cursor.getString(numberIndex)
Toast.makeText(context, "Number is $number!", Toast.LENGTH_SHORT).show()
// val nameIndex =
// cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)
// val name = cursor.getString(nameIndex)
// Toast.makeText(context, "Name is $name!", Toast.LENGTH_SHORT)
// .show()
}
}
}
val launchContactPermission = rememberLauncherForActivityResult(
contract = ActivityResultContracts.RequestPermission()
) { isGranted ->
if (isGranted) {
launchContact.launch()
} else {
Toast.makeText(context, "Permission Denied!", Toast.LENGTH_SHORT)
.show()
}
}
Button(
content = { Text("IMPORT FROM CONTACT") },
onClick = {
when (PackageManager.PERMISSION_GRANTED) {
//First time asking for permission ... to be granted by user
ContextCompat.checkSelfPermission(
context,
Manifest.permission.READ_CONTACTS
) -> {
launchContact.launch()
}
else -> {
//If permission has been already granted
launchContactPermission.launch(Manifest.permission.READ_CONTACTS)
}
}
}
)
}
It seems using ActivityResultContracts.PickContact() is the issue.
I had to modify the code using Intent and ActivityResultContracts.StartActivityForResult() to get my desired result. Here is the new code
#Composable
#Preview
fun openAndSelectContact() {
val context = LocalContext.current
//create a intent variable
val contactIntent = Intent(Intent.ACTION_PICK).apply {
type = ContactsContract.CommonDataKinds.Phone.CONTENT_TYPE
}
val launchContactForResult = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val contactUri: Uri? = result?.data?.data
val projection: Array<String> = arrayOf(
ContactsContract.CommonDataKinds.Phone.NUMBER,
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME
)
contactUri?.let {
context.contentResolver.query(it, projection, null, null, null).use { cursor ->
// If the cursor returned is valid, get the phone number and (or) name
if (cursor!!.moveToFirst()) {
val numberIndex =
cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)
val number = cursor.getString(numberIndex)
val nameIndex =
cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)
val name = cursor.getString(nameIndex)
// Do something with the phone number
Toast.makeText(
context,
"Number is $number & Name is $name",
Toast.LENGTH_SHORT
).show()
}
}
}
}
}
val launchContactPermission = rememberLauncherForActivityResult(
contract = ActivityResultContracts.RequestPermission()
) { isGranted ->
if (isGranted) {
launchContactForResult.launch(contactIntent)
} else {
Toast.makeText(context, "Permission Denied!", Toast.LENGTH_SHORT)
.show()
}
}
Button(
content = { Text("IMPORT FROM CONTACT") },
onClick = {
when (PackageManager.PERMISSION_GRANTED) {
//First time asking for permission ... to be granted by user
ContextCompat.checkSelfPermission(
context,
Manifest.permission.READ_CONTACTS
) -> {
launchContactForResult.launch(contactIntent)
}
else -> {
//If permission has been already granted
launchContactPermission.launch(Manifest.permission.READ_CONTACTS)
}
}
}
)
}
Related
So... Yesterday my code was working just fine, but today, I don't know what happend it stoped working.
The cursor is null and returns both "". I don't know what to do.
Is the cursor even working?
#SuppressLint("Range", "Recycle")
#Composable
fun ContactPickerTwinTurbo(
done: (String, String) -> Unit
) {
val context = LocalContext.current
val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.PickContact(),
onResult = {
val contentResolver: ContentResolver = context.contentResolver
var name = ""
var number = ""
val cursor: Cursor? = contentResolver.query(it!!, null, null, null, null)
if (cursor != null) {
if (cursor.moveToFirst()) {
name =
cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME))
Log.d("Name", name)
val id = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID))
val phones: Cursor? = contentResolver.query(
Phone.CONTENT_URI, null,
Phone.CONTACT_ID + " = " + id, null, null
)
if (phones != null) {
while (phones.moveToNext()) {
number = phones.getString(phones.getColumnIndex(Phone.NUMBER))
Log.d("Number", number)
}
phones.close()
}
}
}
done(name, number)
}
)
Button(
onClick = {
launcher.launch()
},
modifier = Modifier
.fillMaxWidth()
.padding(10.dp)
) {
Text(text = "Pick Contact")
}
}
How does the cursor works?
Do I have to wait?
am I supposed to ask permission to access the contacts?
Apparently Yes you need to ask for permission in runtime to get access the contact. For References check this link
[check link][1]https://www.geeksforgeeks.org/contact-picker-in-android-using-jetpack-compose/
Replace: var name = "" -> var name = remember{ mutableStateOf("") }
var number = "" -> var number = remember{ mutableStateOf("") }
and while accessing, use name.value and number.value
I am working with camera permissions. when the user clicks on take photo button, user will be shown with run time permissions menu and lets say user deny's it and then clicks on take photo button, run time permissions will be shown second time.
after this clicking the take photo button nothing happens.
What I want to do is, after the second attempt, i want to show a popup telling the user to go to settings to change the permissions.
How can I know if the user has denied the permission twice.
this is what I have coded so far
takePhotoBtn.setOnClickListener {
takePhoto()
}
private fun takePhoto() {
activity?.let {
if (isCameraPermissionsAllowed()) {
capturePhoto()
} else {
permReqLauncher.launch(
CAMERA_PERMISSION
)
}
}
}
private val permReqLauncher =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
val granted = permissions.entries.all {
it.value == true
}
if (granted) {
capturePhoto()
}
}
private fun capturePhoto() {
onUtilityBillTypeListener.onUtilityBillTypePhotoLink(true)
}
where is the right place to add this dinielDialog
private fun showPermissionDeniedDialog() {
AlertDialog.Builder(this.requireContext())
.setTitle("Permission Denied")
.setMessage("Permission is denied, Please allow permissions from App Settings.")
.setPositiveButton("Settings",
DialogInterface.OnClickListener { dialogInterface, i ->
// send to app settings if permission is denied permanently
val intent = Intent()
intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
val uri = Uri.fromParts("package", getPackageName(this.requireContext()), null)
intent.data = uri
startActivity(intent)
})
.setNegativeButton("Cancel", null)
.show()
}
You could write something like this
if (isCameraPermissionsAllowed()) {
capturePhoto()
} else {
if (permissionDeniedFlag) {
showPermissionDeniedDialog()
} else {
permissionDeniedFlag = True
permReqLauncher.launch(
CAMERA_PERMISSION
)
}
With an initial declaration of var permissionDeniedFlag = False.
On your code where you're getting permission result you can do it like this :
private val requestPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) {
isGranted: Boolean ->
if (isGranted) //perform action
else {
val builder = AlertDialog.Builder(requireContext())
builder.setTitle("Permission Required!")
builder.setMessage("We need permission in order to perform this action.")
builder.setPositiveButton("OK") { dialog, _ ->
showPermRationale()
dialog.cancel()
}
builder.setNegativeButton("CANCEL") { dialog, _ ->
dialog.cancel()
}
builder.show()
}
}
private fun showPermRationale() {
val intent = Intent()
intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
val uri = Uri.fromParts(
"package", requireActivity().packageName, null)
intent.data = uri
requireActivity().startActivity(intent)
}
resolved it by adding this code
private fun takePhoto() {
activity?.let {
if (isCameraPermissionsAllowed()) {
capturePhoto()
} else {
if (ActivityCompat.shouldShowRequestPermissionRationale(
this.requireActivity(),
Manifest.permission.CAMERA
)
) {
showPermissionDeniedDialog()
}
else{
permReqLauncher.launch(
CAMERA_PERMISSION
)
}
}
}
}
I want to delete an image file from scoped storage
the images that are showing from the other directories.
I have successfully shown images but now I am unable to delete those images in android 11 the code for deleting images is working fine for android 10 or less.
private void delSysMedia(ImageModel mi) {
ContentResolver cr = context.getContentResolver();
cr.delete(Images.Media.EXTERNAL_CONTENT_URI, Images.Media._ID + "=?", new String[]{String.valueOf(mi.getId())});
cr.delete(Images.Thumbnails.EXTERNAL_CONTENT_URI, Images.Thumbnails.IMAGE_ID + "=?", new String[]{String.valueOf(mi.getId())});
}
here is the code that is being used by me in my image service class
For deleting files on Android 11 onwards you need MediaStore.createDeleteRequest and pass a list of Uri you want to delete it will show a system default chooser to the user asking to Allow or Deny the deletion of file.
You can use below code for deleting an Image file.
val uris = arrayListOf<Uri?>()
val uriOfCurrentFile= getImgUri(fileObject.absolutePath)
if (uriOfCurrentFile!= null) {
uris.add(uriOfCurrentFile)
}
val intentSenderLauncher =
registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) {
if (it.resultCode == RESULT_OK) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
Toast.makeText(context, "Deleted Successfully", Toast.LENGTH_SHORT).show()
}
}
}
//This function gets Uri of file for deletion
fun getImgUri(
path: String,
): Uri? {
try {
val checkFile = File(path)
Timber.e("checkDelete- $checkFile")
if (checkFile.exists()) {
var id: Long = 0
val cr: ContentResolver = activity?.contentResolver!!
val selection = MediaStore.Images.Media.DATA
val selectionArgs = arrayOf<String>(checkFile.absolutePath)
val projection = arrayOf(MediaStore.Images.Media._ID, MediaStore.Images.Media.DATA)
val sortOrder = MediaStore.Images.Media.TITLE + " ASC"
val cursor = cr.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection,
"$selection=?", selectionArgs, null
)
if (cursor != null) {
while (cursor.moveToNext()) {
val idIndex = cursor.getColumnIndex(MediaStore.Images.Media._ID)
id = cursor.getString(idIndex).toLong()
Timber.e("checkFileID- $id")
try {
val photoUri: Uri = ContentUris.withAppendedId(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
id
)
return photoUri
} catch (securityException: SecurityException) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val recoverableSecurityException =
securityException as? RecoverableSecurityException
?: throw securityException
recoverableSecurityException.userAction.actionIntent.intentSender
} else {
throw securityException
}
}
}
}
}
} catch (ex: Exception) {
ex.printStackTrace()
}
return null
}
//now you have list of Uri you want to delete
if (uris != null && uris.size > 0) {
var intentSender = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
MediaStore.createDeleteRequest(
activity!!.contentResolver,
uris
).intentSender
}
else -> null
}
intentSender?.let { sender ->
intentSenderLauncher.launch(
IntentSenderRequest.Builder(sender).build()
)
}
}
Please note if not an Image File, you will have to replace
MediaStore.Images with MediaStore.Video, MediaStore.Audio, MediaStore.Files etc.
So I have this composable which tries to read data from storage,
#Composable
private fun Screen() {
val launcher = rememberLauncherForActivityResult(contract = ActivityResultContracts.StartActivityForResult()) { result ->
val uri = result.data?.data.toString()
if(uri !== null) {
val file = File(uri)
val bytes = file.readBytes()
println(bytes)
}
}
Column() {
Button(onClick = {
val intent = Intent().setType("*/*").setAction(Intent.ACTION_OPEN_DOCUMENT)
launcher.launch(intent)
}) {
Text("Open file")
}
}
}
However, it gives me this error: content:/com.android.providers.downloads.documents/document/raw%3A%2Fstorage%2Femulated%2F0%2FDownload%2FIMG_CEFEFF486A8C-1.jpeg: open failed: ENOENT (No such file or directory). What am I doing wrong here? Please help.
Figured it out,
#Composable
private fun Screen() {
val context = LocalContext.current
val launcher = rememberLauncherForActivityResult(contract = ActivityResultContracts.GetContent()) { result ->
val item = context.contentResolver.openInputStream(result)
val bytes = item?.readBytes()
println(bytes)
item?.close()
}
return Column {
Button(onClick = {
launcher.launch("*/*")
}) {
Text("Open file")
}
}
}
I wrote utility functions to request/check permissions in Composables (using CompositionLocal).
data class PermissionHandlerValue(
val hasPermission: (String) -> Boolean,
val hasPermissions: (Array<out String>) -> Array<Boolean>,
val requestPermission: (String) -> Unit,
val requestPermissions: (Array<out String>) -> Unit
)
val LocalPermissionHandler = compositionLocalOf<PermissionHandlerValue> { error("No implementation provided!") }
#Composable
fun ProvidePermissionHandler(content: #Composable () -> Unit) {
CompositionLocalProvider(LocalPermissionHandler provides permissionHandlerImpl()) {
content()
}
}
#Composable
fun permissionHandlerImpl(): PermissionHandlerValue {
val context = LocalContext.current
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {}
val hasPermission: (String) -> Boolean = { ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED }
val hasPermissions: (Array<out String>) -> Array<Boolean> = { it.map { permission -> hasPermission(permission) }.toTypedArray() }
val requestPermission: (String) -> Unit = { launcher.launch(arrayOf(it)) }
val requestPermissions: (Array<out String>) -> Unit = { launcher.launch(it) }
return PermissionHandlerValue(hasPermission, hasPermissions, requestPermission, requestPermissions)
}
#Composable
fun RequirePermission(permission: String, fallback: (#Composable () -> Unit)? = null, content: #Composable () -> Unit) {
val permissionHandler = LocalPermissionHandler.current
if (permissionHandler.hasPermission(permission))
content()
else if (fallback != null)
fallback()
}
It works fine, I can request and check permissions. The problem is that its not reactive, here's an example:
setContent {
ProvidePermissionHandler {
val permissionHandler = LocalPermissionHandler.current
RequirePermission(
permission = Manifest.permission.READ_CONTACTS,
fallback = {
Button(onClick = { permissionHandler.requestPermission(Manifest.permission.READ_CONTACTS)
}) {
Text("Request permission")
}
}
) {
ContactsList()
}
}
}
This composable(RequirePermission) will only render ContactsList if the Manifest.permission.READ_CONTACTS was granted, Otherwise the fallback component is rendered with a button that when clicked will request the permission.
After permissionHandler.requestPermission() is called and I grant the permission on the screen the fallback still shows, instead of the ContactsList (I have to re-open the app to show it).
Basically the condition in RequirePermission() is not checked again because there is no recomposition. How can I force RequirePermission() to recompose?