안녕하세요. 지난번에 이어 이번에는 연락처를 추가/수정하는 기능을 구현해보려고 합니다.
지난 글과 이어지기 때문에 연락처의 권한 획득과 연락처를 불러오기 글을 먼저 확인해주세요.
그럼 연락처 추가/수정 방법에 대해 알아보도록 하겠습니다.
ContactsAdapter.kt
class ContactsAdapter(private val contactsList: ArrayList<ContactsData>, private val onItemClickListener: OnItemClickListener) : RecyclerView.Adapter<ContactsAdapter.ViewHolder>() {
interface OnItemClickListener {
fun onItemClickListener(position: Int)
}
...
inner class ViewHolder(private val binding: ViewContactsListBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind() {
binding.txtName.text = contactsList[adapterPosition].name
binding.txtPhoneNumber.text = PhoneNumberUtils.formatNumber(contactsList[adapterPosition].number, Locale.getDefault().country)
itemView.setOnClickListener {
onItemClickListener.onItemClickListener(adapterPosition)
}
}
}
}
먼저 ContactsAdapter에 MainActivity로 Callback을 전달하기 위한 Interface를 생성해줍니다. 그리고 이 인터페이스를 매개 변수로 추가하여 MainActivity로부터 전달받도록 합니다.
MainActivity로 부터 전달받은 Interface를 itemView의 clickListener와 연결하여 Click이 될 때마다 adapterPosition을 넘겨주도록 합니다.
MainActivity.kt
class MainActivity : AppCompatActivity(), View.OnClickListener {
...
private val onItemClickListener = object : ContactsAdapter.OnItemClickListener {
override fun onItemClickListener(position: Int) {
val intent = Intent(this@MainActivity, ContactsAddEditActivity::class.java)
.putExtra("contactsData", contactsList[position])
startActivity(intent)
}
}
...
override fun onClick(v: View?) {
when (v) {
binding.btnAddContacts -> {
startActivity(Intent(this, ContactsAddEditActivity::class.java))
}
...
}
}
...
private fun initListener() {
binding.btnPermission.setOnClickListener(this)
binding.btnAddContacts.setOnClickListener(this)
}
...
private fun setContacts() {
contactsAdapter = ContactsAdapter(contactsList, onItemClickListener, checkedList)
binding.contactsList.adapter = contactsAdapter
}
}
MainActivity입니다. Adapter에서 만들었던 Interface 변수를 만들어주고, 이 변수를 setContacts 메서드의 Adapter의 인자로 넣어줍니다. Interface 변수에는 ContactsAddEditActivity에 putExtra를 통해 연락처 정보를 넣어준 후 실행하도록 합니다.
다음 만들어 놓았던 btnAddContacts 버튼의 ClickListener를 지정해 주고 클릭되었을 때 ContactsAddEditActivity를 실행하도록 합니다.
이제 실행될 ContactsAddEditActivity를 만들어보겠습니다.
activity_contacts_add_edit.xml
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include
android:id="@+id/layoutTitle"
layout="@layout/view_title_bar"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:id="@+id/layoutName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="12dp"
app:layout_constraintTop_toBottomOf="@id/layoutTitle">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="이름 :"
android:textSize="16dp" />
<EditText
android:id="@+id/editName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_weight="1"
android:hint="이름을 입력하세요"
android:maxLines="1" />
</LinearLayout>
<LinearLayout
android:id="@+id/layoutPhone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="12dp"
app:layout_constraintTop_toBottomOf="@id/layoutName">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="번호 :"
android:textSize="16dp" />
<EditText
android:id="@+id/editNumber"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_weight="1"
android:hint="번호를 입력하세요"
android:inputType="phone"
android:maxLines="1" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent">
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnCancel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="취소" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnSave"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:enabled="false"
android:layout_weight="1"
android:text="저장" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
이전에 만들었던 titleBar를 include해주고 이름과 번호를 입력할 레이아웃, 저장과 취소 버튼을 생성했습니다.
ContactsAddEditActivity.kt
class ContactsAddEditActivity : AppCompatActivity(), View.OnClickListener, TextWatcher {
private val binding by lazy { ActivityContactsAddEditBinding.inflate(layoutInflater) }
private var isEditMode = false
private var contacts: ContactsData? = null
override fun onClick(v: View?) {
when (v) {
binding.btnSave -> {
if (isEditMode) {
setEditContacts()
} else {
setAddContacts()
}
}
binding.btnCancel -> {
finish()
}
}
}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
if (isEditMode) {
binding.btnSave.isEnabled = binding.editName.text.isNotBlank() && binding.editNumber.text.isNotBlank()
&& !(contacts?.number == binding.editNumber.text.toString() && contacts?.name == binding.editName.text.toString())
} else {
binding.btnSave.isEnabled = binding.editName.text.isNotBlank() && binding.editNumber.text.isNotBlank()
}
}
override fun afterTextChanged(p0: Editable?) {}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
initData()
initLayout()
initListener()
}
private fun initData() {
intent.getParcelableExtra<ContactsData>("contactsData")?.let {
contacts = it
isEditMode = true
}
}
private fun initLayout() {
setContentView(binding.root)
if (isEditMode) {
binding.layoutTitle.txtTitle.text = "연락처 편집"
binding.editName.setText(contacts?.name)
binding.editNumber.setText(contacts?.number)
} else {
binding.layoutTitle.txtTitle.text = "연락처 추가"
}
}
private fun initListener() {
binding.btnSave.setOnClickListener(this)
binding.btnCancel.setOnClickListener(this)
binding.editNumber.addTextChangedListener(PhoneNumberFormattingTextWatcher())
binding.editNumber.addTextChangedListener(this)
binding.editName.addTextChangedListener(this)
}
private fun setEditContacts() {
val list = ArrayList<ContentProviderOperation>()
var where = "${ContactsContract.Data.CONTACT_ID}=? AND ${ContactsContract.Data.MIMETYPE}='${ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE}'"
var whereArgs = arrayOf(contacts?.contactsId.toString())
list.add(ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI).apply {
withSelection(where, whereArgs)
withValue(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, "")
withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, binding.editName.text.toString())
}.build())
where = "${ContactsContract.Data.CONTACT_ID}=? AND ${ContactsContract.Data.MIMETYPE}='${ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE}' AND ${
ContactsContract.CommonDataKinds
.Phone.NUMBER
}=?"
whereArgs = arrayOf(contacts?.contactsId.toString(), contacts?.number.toString())
list.add(ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI).apply {
withSelection(where, whereArgs)
withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, binding.editNumber.text.toString())
}.build())
try {
contentResolver.applyBatch(ContactsContract.AUTHORITY, list)
Toast.makeText(this, "연락처가 수정되었습니다.", Toast.LENGTH_SHORT).show()
} catch (e: Exception) {
e.printStackTrace()
Toast.makeText(this, "연락처 수정에 실패하였습니다.", Toast.LENGTH_SHORT).show()
}
finish()
}
private fun setAddContacts() {
val list = ArrayList<ContentProviderOperation>()
list.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI).apply {
withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null)
withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null)
}.build())
list.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply {
withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, binding.editName.text.toString())
}.build())
list.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply {
withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, binding.editNumber.text.toString())
withValue(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE)
}.build())
try {
contentResolver.applyBatch(ContactsContract.AUTHORITY, list)
Toast.makeText(this, "연락처가 생성되었습니다.", Toast.LENGTH_SHORT).show()
} catch (e: Exception) {
e.printStackTrace()
Toast.makeText(this, "연락처 생성에 실패하였습니다.", Toast.LENGTH_SHORT).show()
}
finish()
}
}
먼저 initData부터 살펴보겠습니다. initData에서는 getParcelableExtra를 통해 넘겨받은 연락처를 받아옵니다. 이때 null이 아니라면 연락처 수정으로 생각해 contacts 데이터를 저장하고, isEditMode를 true로 변경해줍니다. 그리고 initLayout에서는 editMode라면 넘겨받은 연락처의 이름과 연락처를 각 editText에 넣어주고 타이틀의 Text를 연락처 편집으로 표시하고. 아니라면 타이틀은 연락처 추가가 되고, editText들은 비어있게 됩니다!
다음 initListener 메서드에서는 btnSave, btnCancel의 ClickListener를 지정하고 editNumber, editName의 TextChangedListener를 지정해줍니다. 이 때 editNumber는 TextChangedListener가 2개가 지정되어 있는데 PhoneNumberFormattingTextWatcher는 editText의 변화를 감지하여 자동으로 전화번호 형식으로 Formatting을 해주게 됩니다. 예를 들어 내가 01012345678을 입력했을 때 자동으로 010-1234-5678로 변하게 되는 것입니다.
그러면 이제 editText의 리스너 부터 동작을 확인하겠습니다. editText의 Text가 변화할 때마다 TextWatcher가 콜백을 받게 됩니다. 이때 isEditMode일 경우 기존 연락처의 이름과 저장번호가 동일하지 않고 editText들이 비어있지 않을 때 btnSave 버튼이 활성화 되게 됩니다. 아닐 경우에는 editText들이 비어있지 않을 경우에 활성화가 되게 됩니다.
다음은 버튼의 리스너입니다. btnCancel을 클릭할 경우에는 finish()를 실행하여 현재의 액티비티가 종료되게 됩니다. 그리고 btnSave를 선택할 경우 editMode일 경우에는 setEditContacts를 아닐 경우에는 setAddContacts 메서드를 실행하게 됩니다.
setEditContacts 메소드는 저장된 연락처의 아이디로 조건문을 생성하고 이 조건문으로 기존 연락처의 정보를 선택하여 수정하게 됩니다. 연락처의 이름과 연락처의 전화번호는 다른 데이터베이스에서 관리가 되기 때문에 따로 호출하여 수정을 해주어야 합니다. 물론 생성할 때에도 따로 해야 합니다. 이렇게 수정한 데이터를 contentResolver를 통해 저장해주고 finish를 사용하여 현재 액티비티를 종료합니다.
setAddContacts 메소드도 비슷한 방식으로 진행합니다.
AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.jdroid.contactsex">
...
<application
...
<activity
android:name=".ui.ContactsAddEditActivity"
android:exported="false"
android:windowSoftInputMode="adjustResize"/>
</application>
</manifest>
그리고 마지막으로 AndroidManifest에 방금 만든 Activity를 추가해주면 됩니다.
이제 결과를 한번 보도록 하겠습니다.
위와 같이 연락처 추가/수정이 기본 주소록에도 잘 반영이 되는 것을 확인할 수 있습니다.
이번에는 연락처를 추가/수정하는 방법에 대해 알아보았습니다.
연락처 삭제 게시글은 아래에서 보실 수 있습니다.
문제가 있거나 이해가 되지 않는 부분이 있으시면 댓글을 남겨주시면 답변드리겠습니다!
'Android > Libraries' 카테고리의 다른 글
[Android] 안드로이드 Permission(권한) 예제 - 연락처/주소록 만들기 (연락처 삭제) (0) | 2022.08.08 |
---|---|
[Android] 안드로이드 Permission(권한) 예제 - 연락처/주소록 만들기 (권한 획득, 연락처 불러오기) (0) | 2022.07.24 |
[Android] Parcelable 생성 Parcelize로 편하게 생성하기(with Serializable) (0) | 2022.07.24 |
[Android] 안드로이드 Permission(권한) 종류 및 권한 획득 예제(지문인증 / 생체 인증) (0) | 2022.07.11 |
[Android] 안드로이드12 대응 SplashScreen(스플래시 스크린) 만들기 (1) | 2022.07.09 |