카테고리 보관물: Android

Android 앱 개발 전 확인사항

개요

얼마전 개발 중인 android 앱을 올리고 검토가 완료되기를 기다렸습니다. 알림 메일이 도착했는데 앱이 거부되었다는 내용이었습니다. 이전에 앱들은 거부된 적이 한번도 없었는데 어떻게 된 것인지 확인해 보았습니다. 앱의 주요기능을 구현하기 위해 민감한 정보로 분류된 권한을 manifest에 추가했고 그에 따른 필수 기능이 구현되지 않아 발생한 문제였습니다.

앱 등록 거부알림
앱 등록 거부알림

참고 정보

알림 메일 내에는 어떤 정보를 참조하라고 안내되어 있습니다. 대략 무엇이 문제인지 짐작은 할 수 있습니다. 그러나 정확하게 어떤 부분이 문제라고 꼭 집어 알려주지 않습니다. 메일내에 참고 링크가 있는데 다음과 같습니다.

개발자 정책 센터

권한

SMS 또는 통화 기록 권한 그룹 사용

필자의 경우 SMS와 통화 기록 권한 관련한 권한이 문제였습니다. 앱을 의도한 대로 구동하려면 기본 SMS 핸들러 기능을 모두 구현해야 했습니다. 그리고 앱 정보 등록 시 앱 콘텐츠 > 민감한 앱 권한 화면에서 권한에 따른 앱 기능을 선택해야 합니다. 구글에서 이 정보와 앱이 구현된 것이 일치하지 않는다고 판단되면 거부됩니다.

SMS 및 통화 기록 권한 핵심 기능 선택
SMS 및 통화 기록 권한 핵심 기능 선택

요청한 권한에 따라 이것을 달라질 수 있습니다. 필자의 경우 개발을 거의 마친 상태에서 이런 사실을 알게 되었습니다. 앱 사용자 입장에서는 나의 정보가 최소 범위로 제공되고 사용되어야 하는 것이 맞습니다. 개발하는 입장에서는 구글에서 검토와 승인이 잘 되도록 하는 것이 상황에 따라서는 매우 어려울 것 같다는 생각이 들었습니다. 위 이미지에 있는 핵심 기능 이라고 나열되어 있는 항목 중 딱 이것이라고 생각되는 것이 없었습니다. 그래서 비슷한 것으로 선택을 했는데 결국 거부되었습니다. 이러저리 바꾸어 보고 앱의 설명도 첨부해 보았지만 소용없었습니다.

앱 개발 전 사전 검토

기획 단계에서 필요한 권한을 예상해 보고 나열해 봅니다. 혹시 그것이 민감한 정보에 관계된 것인지 확인합니다. 권한 요청 없이 구현할 수 있다면 최대한 그렇게 합니다(ex:SMS 전송 권한 대신 intent 사용). 구글의 승인을 받아야 하는 권한을 최소화 합니다. 아예 없게 만드는 것이 가장 이상적입니다. 이것을 개발이 진행된 상태에서 알게된다면 이전 작업내용을 전혀 다른 형태로 다시 만들어야 할 수도 있습니다. 대체되는 방법으로 구현했으나 초기 기획 의도와는 맞지 않게 될 수도 있습니다. 앱이 거부되었는데 그것을 해결하는데 매우 많은 시간과 노력이 든다면 바람직하지 않을 것입니다. 처음부터 문제가 될 부분을 인식하고 최대한 피해가는 것이 합리적입니다.

독자분들은 제가 했던 실수를 반복하지 않기를 바랍니다.

Android RecyclerView 체크박스 상태 관리

개요

Android RecyclerView 는 화면에 보여지는 영역에서만 정의된 항목들이 유효합니다. 스크롤되어 다른 자료가 보여지게 되면 이전에 적용된 속성이 사라지게 됩니다. 대표적인 예가 체크박스 상태입니다. 화면에 보여질 때 체크하고 스크롤 후 다시 그 자료로 돌아와 보면 체크되지 않는 상태로 됩니다. 그래서 체크된 자료를 대상으로 처리하거나 전체선택/해제 기능을 구현할 때 이런 특성을 알고 있어야 합니다. 이번글에서는 전체선택/해제, 체크박스의 상태 유지 기능을 구현하는 방법을 알아보도록 하겠습니다.

체크박스 상태 저장

어디에선가는 체크박스 상태를 저장하고 있어야 합니다. 몇번째 체크박스가 체크되어 있는지에 관한 정보가 될 것입니다. 이글에서는 해시맵(Hashmap)을 이용해서 체크박스 상태를 저장해 보도록 하겠습니다. 다음과 같이 해시맵을 선언합니다. 리스트자료의 고유값을 키(Key)로 하고 값(Value)은 위치(Position) 값으로 저장해 보겠습니다.

companion object {
    lateinit var selectCheckBoxPosition:HashMap<Int, Int>
    private set
}

전체선택/해제

먼저 체크박스를 전체 선택하는 체크박스가 정의되어 있고 그것을 클릭했을 때 전체선택/해제 정보를 해시맵에 할당해 보겠습니다.

viewBinding.checkAllCheckBox.setOnClickListener {
    selectCheckBoxPosition.clear()
    if (viewBinding.checkAllCheckBox.isChecked) {
        for ((index, listData:yourClass) in yourList.withIndex()) {
            selectCheckBoxPosition[listData.Seq] = index
        }
    }
    listAdapter.notifyDataSetChanged()
}

체크박스를 클릭하면 해시맵을 비웁니다. 체크가 되었다면 연결된 리스트의 자료를 대상으로 고유값을 키로해서 위치 정보를 저장합니다. 그 다음 어댑터에 자료가 변경되었다는 것을 알립니다.

체크된 항목 정보 저장

전체선택/해제와 방식은 비슷합니다. 리스트내의 체크박스를 체크하면 해시맵에 저장하고 해제하면 제외합니다.

override fun onBindViewHolder(holder: Holder, position: Int) {
    holder.bind(yourList[position])
    val seqCheckBox = holder.itemView.findViewById<CheckBox>(R.id.seq)
    seqCheckBox.setOnCheckedChangeListener(null)
    seqCheckBox.isChecked = yourActivity.selectCheckBoxPosition.containsValue(position)
    seqCheckBox.setOnClickListener {
        val seq = seqCheckBox.tag.toString().toInt()
        if (seqCheckBox.isChecked) {
            yourActivity.selectCheckBoxPosition[seq] = position
        } else {
            yourActivity.selectCheckBoxPosition.remove(seq)
        }
    }
}

onBindViewHolder 메소드에 체크박스의 정보를 가져와서 해시맵에 위치 값이 존재하면 체크상태로 변경합니다. 그 내부에 체크박스 클릭 리스너를 정의합니다. 체크하면 해시맵에 정보를 저장하고 해제하면 제외합니다.

직접 구현한 코드와 예제 코드의 스타일이 다를 수 있습니다. 그렇지만 체크박스의 정보를 저장하고 관리해야 한다는 것만 염두에 두시면 문제없이 기능을 구현하실 수 있을겁니다.