private lateinit var imageLauncher: ActivityResultLauncher<Intent>
private fun clickEditImg() {
with(binding) {
btnEditImg.setOnClickListener {
val intent = Intent(Intent.ACTION_PICK)
intent.type = "image/*"
imageLauncher.launch(intent)
}
}
imageLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val img_URI = result.data?.data
binding.ivProfileImg.scaleType = ImageView.ScaleType.CENTER_CROP
binding.ivProfileImg.setImageURI(img_URI)
}
}
} binding.ivProfileImg.setImageURI(img_URI)를 해주었다. onViewCreated안에 그냥 넣어두었다가 복잡해 보여서 빼두려고 할 때 코드의 위치를 잘 못 잡아서 늦은 초기화 오류를 만났다.btnEditImg.setOnClickListener 밖에 있어야 onViewCreated 안에 있는 clickEditImg() 에서 바로 접근이 가능하고, private lateinit var imageLauncher에서 늦은 초기화에 대한 오류 없이 정상 동작한다. 이미지 수정 기능을 만들면서 취소 확인 버튼에 대해서도 겹치는 함수가 너무 많아져서 하나로 리팩토링 할 수 있도록 먼저 로직을 정리하였다.
위에서 정리한 로직을 바탕으로 처음에는 if문을 취소버튼을 눌렀을 때. 확인버튼을 눌렀을 때로 바꾸려고 했는데, 그러면 그 안에 코드가 각각 들어가야하는게 마찬가지라서 공통 함수를 빼서 묶고 그 안에서 true/false로 취소 확인 버튼의 동작을 구분하기로 했다.
private fun clickEditListener() {
with(binding) {
btnEditConfirm.setOnClickListener { handleClickEdit(true) }
btnEditCancel.setOnClickListener { handleClickEdit(false) }
}
}
private fun handleClickEdit(confirm: Boolean) {
with(binding) {
if (confirm) {
Toast.makeText(requireContext(), "데이터베이스로 저장!", Toast.LENGTH_SHORT).show()
//Todo. 설정된 이름, 사진에 대한 변경값을 다시 데이터베이스에 넘겨서 저장
}
// 취소, 확인 버튼을 눌렀을 때 공통으로 적용되는 부분들
initLogin()
btnEditName.visibility = View.GONE
btnEditImg.visibility = View.GONE
llEditConfirm.visibility = View.INVISIBLE
tvProfileName.visibility = View.VISIBLE
}
}
handleClickEdit함수를 생성해서 confirm을 통해 true(=확인)일 때 데이터베이스에 바꾼 이름과, 바꾼 이미지 프로필이 데이터베이스에 저장되는 로직을 넣어줄 것이다. 지금은 임시로 정상 작동 되는지 확인하기 위해 토스트 메시지를 넣어두었다.clickEditListener에서 각 버튼이 눌렸을 때 true인지 false인지 전달하여 handleClickEdit 함수를 알맞게 사용한다.이름 수정의 경우는 카카오톡 이름 수정하는 기능에서 착안하여 EditUserName 프래그먼트를 따로 만들어서 수정이 가능하도록 기능요구서를 작성했다. 필수 MVP는 아니라서 우선순위가 하 이긴 하지만 유저 정보 설정에 대한 코드를 받기 전에 시간이 남기도 하고, 다이얼로그를 걸면서 어렵지 않게 구현할 수 있을 것 같아서 다이얼로그를 통해 바로 구현해보기로 했다.
private fun clickEditName() {
with(binding) {
btnEditName.setOnClickListener {
val builder = AlertDialog.Builder(requireContext())
val editUserNameDialog = layoutInflater.inflate(R.layout.dialog_edit_user_name, null)
builder.setView(editUserNameDialog)
val dialog = builder.create()
//다이얼로그 영역(기본값 화이트) 투명화로 둥근 테두리가 묻히지 않고 보이도록 설정
dialog.window?.setBackgroundDrawableResource(android.R.color.transparent)
dialog.show()
val layoutParams = WindowManager.LayoutParams().apply {
// 다이얼로그의 크기를 화면에 꽉 차게 조절
copyFrom(dialog.window?.attributes)
width = WindowManager.LayoutParams.MATCH_PARENT
height = WindowManager.LayoutParams.MATCH_PARENT
//화면 투명도 설정 (투명0~선명1)
dimAmount = 0.9f
}
dialog.window?.attributes = layoutParams
val etEditUserName = dialog.findViewById<EditText>(R.id.et_edit_user_name)
etEditUserName.setText(tvProfileName.text.toString())
dialog.findViewById<TextView>(R.id.tv_current_length).text = etEditUserName.length().toString()
//키보드 자동활성화
etEditUserName.requestFocus()
val inputMethodManager = requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0)
etEditUserName.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
//입력 전 호출 메서드 (입력 하여 변화가 생기기 전)
}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
//입력 중 호출 메서드 (변화와 동시에 처리)
dialog.findViewById<TextView>(R.id.tv_current_length).text = etEditUserName.length().toString()
}
override fun afterTextChanged(p0: Editable?) {
//입력 후 호출 메서드 (입력이 끝났을 때 처리)
}
})
with(dialog) {
findViewById<ImageView>(R.id.btn_clear_name).setOnClickListener {
etEditUserName.text.clear()
}
findViewById<TextView>(R.id.btn_edit_name_cancel).setOnClickListener {
//화면 취소하면 키보드 없애주기
inputMethodManager.hideSoftInputFromWindow(dialog.window?.decorView?.windowToken, 0)
dialog.dismiss()
}
findViewById<TextView>(R.id.btn_edit_name_confirm).setOnClickListener {
tvProfileName.text = etEditUserName.text.toString().replace("\n", "")
inputMethodManager.hideSoftInputFromWindow(dialog.window?.decorView?.windowToken, 0)
dialog.dismiss()
}
}
}
}
}
당연히, EditUserName에 관한 xml을 만드는 것이 우선이지만, 카카오톡의 이름 수정 화면과 유사하기도하고 복잡하지 않으므로 따로 xml 파일까지는 여기에 올리지 않겠다.
+다만, xml을 만들 때, 문자열을 clear해 줄 버튼을 drawable로 넣을지 그냥 새로 imageView위젯을 넣어줄 지 고민했었는데, drawable에 클릭 이벤트를 넣으려면 넣을 수 있지만, 코드가 너무 복잡해지는 것 같아서 다이얼로그 안에 걸꺼니까 조금이라도 덜복잡하려고 그냥 imageView를 사용해주었다.
여기서는 다이얼로그가 팝업창 형태가 아니고, 화면에 꽉차게 나오기 때문에 화면 조정해주는 코드와, 따로 백그라운드를 두지 않을 것이기 때문에 다이얼로그 연결 시 자동적으로 검게 dimed 처리되는 불투명도를 0.9정도로 높여주는 코드를 작성해주었다.
그리고, 이름 수정 기능에 바로 집중 할 수 있게 기본적으로 키보드를 활성화 시켜주었다. 원래 지금 사용하는 코드에서 toggleSoftInput과 SHOW_FORCED는 자바에서 deprecated 되었기 때문에, 다른 showSoftInput SHOW_IMPLICIT과 같은 함수를 적용해보려고 했으나, 무슨 일인지 전혀 적용이 안되어서 그냥 toggleSoftInput를 사용하기로 했다. 이 코드는 키보드가 없으면 생성해주고, 있으면 없애주는 함수이지만, 어짜피 이름 수정 버튼을 누르기 이전에는 키보드가 활성화될 일이 없기 때문에 그냥 그대로 사용하기로 했다.
+여기에 더불어서 이름 수정이 완료(취소,확인 둘다)되면 키보드를 내리는 함수도 추가해주었다.
[트러블슈팅] 앱 개발 입문 때 이후로 EditText에 텍스트 할당하는게 오랜만이라서..그런가.. 아무생각없이 TextView에 할당하듯이 .toString()을 썼다가 타입 미스 오류를 만났다. EditText에 문자를 할당할 때는 제대로 setText를 사용하여 Editable 타입에 맞출 수 있도록 하는 것을 잊지말아야겠다.
+그리고 EditText를 TextView에 할당할 때도 꼭 .toString()을 사용해야한다. 문자열로 바꿔주지 않으면 editable 타입의 특성 때문에 글자에 밑줄이 들어간 채 TextView에 할당이 된다.
[트러블슈팅] EditText에서 또 트러블 슈팅이 있었는데, 바로 이름 수정 줄에서 엔터 줄바꿈이 먹히는 문제였다. 검색해봤을 때 android:inputType="text|textNoSuggestions"과 android:imeOptions="actionNone" 두개를 같이 사용하라고 나왔는데, 각각 자동완성/제안 비활성화와 입력완료동작 등의 기타 특정 동작을 정의하는 코드들인데, 설명을 듣고 imeOptions만 걸어주면 될 줄 알고 뻘짓을 했다가 안되는 것을 확인하고 코드 두개를 다 걸어주고 나서야 editText에서 줄바꿈이 적용되지 않았다.
=> 근데 editText에서만 줄바꿈이 안보이는거지, TextView에 할당해 줄 때는 줄바꿈이 적용된 채여서, 어떻게 할까 고민하다가 제일 쉬워보이는 replace("\n", "")를 사용하여 그냥 줄바꿈이 설령 입력되더라도 아예 없애는 방향으로 코드를 보완해주었다.
트러블 슈팅에 따로 적을까 하다가 여기에 적는데, 주말에 프로젝트 구조화 변경으로 모든 팀원들의 초반 구현 코드를 PR받아서 에뮬레이터를 켰는데, 로그아웃 다이얼로그가 메모리 부족으로 작동이 되질 않았다. 지도를 가져오시는 과정에서 메모리릭(메모리누수)가 나타난 것 같은데, 임시 방편으로 Manifest에서 메모리를 늘려주는.. 방법으로 고쳐주었다. 에뮬이 아닌 일반 핸드폰 기기에서는 문제 없이 어플이 작동된다는 점에서도 임시방편이기 때문에 따로 트러블슈팅을 여기에 적지는 않지만, 팀원들과의 이슈 공유 기록에는 적어놓았다.
이름 수정하는 부분을 따로 기능 정의서로 적어놓은 만큼 은근히 손이 가는 부분들이 많아서 놀랐다. 그래도 TextWatcher나 키보드 고정/내리기 같은 기능을 맨날 팀원들이 구현한 것만 보다가 직접 작성하게 되서 재미있었다.
오늘 로그인 구현 팀원분과 이야기를 나눴을 때, 데이터베이스에 유저 정보 올리는 것 까지 완성하셨다고 해서 확인해보니까 이름,닉네임,이메일만 딱 업로드 되어있는 것을 확인했다. 그래서 UUID도 넣는 것을 한 번 더 팀원들과 확인하고,
=> 그래서 내일은 로그인 세션 유지와 이 로그인 정보를 통한 마이페이지 정보 할당하기를 할 것이다. 내일 하루..만에.. 끝낼 수 있어야할텐데 싶지만 목표는 내일 완수하기. 그리고 이 다음에는 진짜 게시판 담당 팀원분과 상의해서 작성한 게시글 가져오기를 하고.. 지금 의외로 디테일 캠핑장 정보를 하고 계시는 분이 없어서.. 일단 이게 있어야 북마크한 캠핑장도 가져올 수 있어서.. 이거 두개는 좀 걱정이 되지만, 일단 여태껏 해온게 있기 때문에 어떻게든 할 수 있을 것 같다. 이번주 안에 최대한 MVP 70~80퍼를 구현하고 발표해야하는데, 그러면 일단 수요일까지 기본 사항을 잡아 놓았는데.. 수요일까지 마이페이지에 데이터베이스 정보를 할당하는 것을 마지노선으로 이틀 동안 열심히 해야겠다!@
+추가적으로. 오늘 개발자 계정을 통한 플레이스토어 앱 등록 기본 세팅을 팀원들과 진행하였는데, 개인정보동의서, 사용자등급설정, 회원탈퇴정리, 데이터 관리 정보 등 의외로 신경써야할 부분들이 많아서 놀랐다. 이번에 앱 등록이 더 빡빡해졌다고 듣긴 했는데, 실제로 해보니까 쉽지 않은 것이 느껴진다. 테스터도 20명이나 모집해서 테스트를 2주간 진행해야하고, 테스트 과정에서 앱이 강제종료라도 발생하면 선처가 없다고 해서 꼼꼼한 점검이 필요할 것 같다. 예전에 비해 많이 어려워졌다고는 하는데, 그래도 확실히 기본적으로 챙겨야할 것들이라고 생각해서 단단히 준비해야겠다는 마음이다.!