Skip to content

Instantly share code, notes, and snippets.

@decodeandroid
Last active December 28, 2024 03:42
Show Gist options
  • Save decodeandroid/829ec97b7e35f9b5fe461e32032afd7b to your computer and use it in GitHub Desktop.
Save decodeandroid/829ec97b7e35f9b5fe461e32032afd7b to your computer and use it in GitHub Desktop.
Read & Write Contacts in Android using Jetpack Compose
import android.Manifest
import android.app.Activity
import android.content.ContentProviderOperation
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.provider.ContactsContract
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
// Data class for Contact
data class Contact(
val id: String,
val name: String,
val phoneNumber: String
)
// Contact Manager Class
class ContactManager(private val context: Context) {
private val contentResolver: ContentResolver = context.contentResolver
fun readContacts(): List<Contact> {
val contacts = mutableListOf<Contact>()
val projection = arrayOf(
ContactsContract.CommonDataKinds.Phone.CONTACT_ID,
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Phone.NUMBER
)
contentResolver.query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
projection,
null,
null,
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME
)?.use { cursor ->
while (cursor.moveToNext()) {
val id = cursor.getString(0)
val name = cursor.getString(1)
val number = cursor.getString(2)
contacts.add(Contact(id, name, number))
}
}
return contacts
}
fun insertContact(name: String, phoneNumber: String) {
val operations = ArrayList<ContentProviderOperation>()
operations.add(
ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null)
.build()
)
// Insert name
operations.add(
ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0).withValue(
ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE
).withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name).build()
);
// Insert mobile
operations.add(
ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0).withValue(
ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE
).withValue(
ContactsContract.CommonDataKinds.Phone.NUMBER,phoneNumber
).withValue(
ContactsContract.CommonDataKinds.Phone.TYPE,
ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE
).build()
);
try {
contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
// Compose UI
@Composable
fun ContactScreen(contactManager: ContactManager, context: Activity) {
val contacts = contactManager.readContacts()
var name by remember { mutableStateOf("") }
var phoneNumber by remember { mutableStateOf("") }
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
// Input Fields
OutlinedTextField(
value = name,
onValueChange = { name = it },
label = { Text("Name") },
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp)
)
OutlinedTextField(
value = phoneNumber,
onValueChange = { phoneNumber = it },
label = { Text("Phone Number") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone),
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp)
)
Button(
onClick = {
if (name.isNotBlank() && phoneNumber.isNotBlank()) {
contactManager.insertContact(name, phoneNumber)
name = ""
phoneNumber = ""
contactManager.readContacts()
}
},
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)
) {
Text("Add Contact")
}
Button(
onClick = {
val intent = Intent(ContactsContract.Intents.Insert.ACTION).apply {
type = ContactsContract.RawContacts.CONTENT_TYPE
putExtra(ContactsContract.Intents.Insert.NAME, name)
putExtra(ContactsContract.Intents.Insert.PHONE, phoneNumber)
}
context.startActivity(intent)
},
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)
) {
Text("Add Contact in Contacts")
}
// Contacts List
LazyColumn(
modifier = Modifier.fillMaxWidth()
) {
items(contacts) { contact ->
Card(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp)
) {
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
) {
Text(text = contact.name, style = MaterialTheme.typography.titleMedium)
Text(
text = contact.phoneNumber,
style = MaterialTheme.typography.bodyMedium
)
}
}
}
}
}
}
// Permission checker
fun checkContactsPermission(context: Context): Boolean {
return ContextCompat.checkSelfPermission(
context,
Manifest.permission.READ_CONTACTS
) == PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(
context,
Manifest.permission.WRITE_CONTACTS
) == PackageManager.PERMISSION_GRANTED
}
class MainActivity : ComponentActivity() {
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val permissionManager = PermissionManager(this@MainActivity)
// Initialize ContactManager
val contactManager = ContactManager(this)
val permissions = listOf(
android.Manifest.permission.READ_CONTACTS,
android.Manifest.permission.WRITE_CONTACTS,
)
setContent {
// Check permissions before showing the UI
if (checkContactsPermission(this)) {
ContactScreen(contactManager,this)
} else {
Button(onClick = {
permissionManager.requestPermissions(permissions) { results ->
}
}) {
Text("Read Contacts")
}
}
}
}
}
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
import android.content.Context
import android.content.Intent
import android.provider.Settings
import androidx.activity.ComponentActivity
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
class PermissionManager(
private val activity: ComponentActivity
) {
private var permissionResults: (Map<String, Boolean>) -> Unit = {}
private val permanentlyDeniedPermissions = mutableListOf<String>()
private val permissionLauncher =
activity.registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { results ->
val permanentlyDenied =
results.filter { !it.value && !activity.shouldShowRequestPermissionRationale(it.key) }
permanentlyDeniedPermissions.clear()
permanentlyDeniedPermissions.addAll(permanentlyDenied.keys)
permissionResults(results)
}
fun requestPermissions(
permissions: List<String>,
onResults: (Map<String, Boolean>) -> Unit
) {
permissionResults = onResults
permissionLauncher.launch(permissions.toTypedArray())
}
fun getPermanentlyDeniedPermissions(): List<String> = permanentlyDeniedPermissions
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment