Intro

  • 이번주에 팀프로젝트를 진행중인데 프로젝트 주제가 연락처 앱이다. 처음에는 더미데이터로 시작했지만 결국 실제 전화번호부에서 정보를 가져와 화면에 보여줘야 하므로 여기에 대한 공부를 집중적으로 하는 시간이었다.

권한 요청

  • 사용자에게 권한을 요청하는 작업을 진행하는데 흐름을 이해하는것에 어려움이 있었다. 만들면서 이해하게된 흐름은 아래와 같았다.
    1. 권한이 있는지 없는지 체크한다.
    2. 있는경우 실제 연락처 데이터를 Recyclerview에 적용함.
    3. 없는경우 사용자에게 권한 허용을 요청함.
      1. 사용자가 권한을 허용한경우 실제 연락처 데이터를 적용함.
      2. 사용자가 권한을 거부한경우 Toast메세지를 띄운 후 더미데이터 적용함.
    4. 만약 사용자가 권한을 거부한 후 앱을 종료 -> 다시 앱 실행시 권한 묻기
      • 이건 최대2번까지만 가능하다고 함.(안드로이드 11이후)
      • 요부분은 구현하지 않음.
  • 위의 로직을 따라 작성한 코드가 아래 내용이다.
    [권한 묻기]
    val checkPermission =
             ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_CONTACTS)
         if (checkPermission == PackageManager.PERMISSION_GRANTED) {
             val list = getContacts()
             initView(list)
         } else {
             requestContactPermissionLauncher.launch(Manifest.permission.READ_CONTACTS)
         }
    [사용자에게 권한 받기]
     private val requestContactPermissionLauncher =
         registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->
             val list: MutableList<Contact>
             if (isGranted) {
                 // 권한을 허용한 경우 처리
                 list = getContacts()
             } else {
                 // 권한을 거절한 경우 처리
                 list = DataManager.getContacts().toMutableList()
                 Toast.makeText(
                     requireContext(),
                     getString(R.string.permission_contact_denied_msg),
                     Toast.LENGTH_LONG
                 ).show()
             }
             initView(list)
         }

기기 연락처 받아오기

  • 기기 연락처에 접근하고 받아오는 개념이 생소해 시간이 좀 걸렸다. 일단 기본 뼈대는 공식문서를 보며 공부했다.
    연락처 제공자 | Android 개발자

  • 아래 그림에 나온 단어들을 살펴보면 알 수 있듯이 기기 내부의 db가 있고 3개의 테이블이 나뉘어있어 여기에서 내가 필요한 정보를 query에 담아 뽑아오는 식으로 구현한다.

  • 내가 구현한 코드는 아래와 같다.

private fun getContacts(): MutableList<Contact> {
        val contacts = requireContext().contentResolver.query(
            ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
            null,
            null,
            null,
            null
        )
        val list = mutableListOf<Contact>()

        contacts?.let {
            if (contacts.count > 0) {
                while (it.moveToNext()) {
                    val id = it.getInt(it.getColumnIndexOrThrow(ContactsContract.Data._ID))
                    val name =
                        it.getString(contacts.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME))
                    val phoneNumber =
                        it.getString(contacts.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.NUMBER))

                    val emailCursor = requireContext().contentResolver.query(
                        ContactsContract.CommonDataKinds.Email.CONTENT_URI,
                        null,
                        ContactsContract.CommonDataKinds.Email.CONTACT_ID + " = ?",
                        arrayOf("$id"),
                        null
                    )

                    emailCursor?.let {
                        if(it != null && it.moveToFirst()) {
                            val emailIndex: Int =
                                emailCursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.DATA)
                            val email: String = emailCursor.getString(emailIndex)
                            emailCursor.close()

                            val model = Contact(
                                Img = R.drawable.dialog_profile,
                                name = name,
                                phonenumber = phoneNumber.toString(),
                                email = email,
                                birth = "",
                                nickname = "",
                            )
                            list.add(model)
                        }
                    }
                }
            }
        }

        return list
    }
  • 우선은 contentResolver를 사용하여 쿼리를 날리는데 contentResolver는 다른 앱의 정보를 가져올때 사용한다고 한다. 해보진않았지만 WHERE절도 사용가능하고, 데이터 sort도 가능하다.
  • 쿼리를 만들면 Cursor 형태로 반환되는데 왜 커서인가하니 테이블의 필요한 부분을 찍어서 가져온다고해서 커서라고 한다. 어쨋든 이렇게 가져온 데이터 내부를 순회하며 이름과 전화번호는 순조롭게 가져왔으나 이메일을 가져오는것에서 막혔었다.
  • 구글과 stackOverflow와 ChatGPT님들이 도와주셔서 이메일 주소를 가져올 수 있었다.
    1. Cursor를 만드는데 Email형태 + 전체목록에서 가져온 id를 조건으로 검색한다.
    2. 커서를 이동하는데 Email.DATA의 Int값을 가져와 ColumnIndex에 넣어주면 그 위치의 값을 가져와주는 식이다.
  • 이정도 만들다보니 연락처에 어떤식으로 접근해서 값을 가져와야하는것인지가 감이 잡혔다. 그러나 여기에서의 고민은 코드를 더 깔끔하게 정리할 수는 없을까였다. 다 만들어놓고보니 가독성이 떨어진다는 생각이 들었다. 그리고 db를 쓰는거여서 그런지 비동기로 돌아간다는 느낌이었다(정확하진않다 그냥 추측이다..)

Outro

  • 연락처 가져오는게 이렇게 빡센일이될줄은 몰랐다.. 연락처를 처리하는일이 이렇게 방대한 것인지 이번에 개발하면서 느꼈다.
  • 공식문서 정독을 생활화하자!! 찾다찾다 결국 공식문서로 돌아왔다. 공식문서에 나온 컨셉을 이해해야만 사용할 수 있다는것을 깨닫는 시간이었다.
profile
잘부탁드립니다!

0개의 댓글

Powered by GraphCDN, the GraphQL CDN