악어떼의 여정 (21년 11월)
협업 노션 링크 https://url.kr/o3gm4v
시작
나를 포함한 세 명의 개발 starters, 그리고 한 분의 디자이너님을 모셔서 총 4명으로 이루어진 악어떼(🐊❣️)가 결성되었다.
개발 starters라고 한 건, 우리 모두 21년 9월에 스파르타코딩클럽의 내일배움단으로 첫 코딩을 시작한 사람들이었기 때문..!ㅎㅎ
함께 15일 프로젝트로 파이썬 웹개발을 진행한 뒤, 용감하게 외부 공모전에 도전해보기로 했다.
도전
10월 26일부터 시작해 11월을 불태웠던 공모전은 ICT CoC과 서울시 사회서비스원에서 주관한 '피우다 프로젝트'였고,
사회적 약자의 생활개선 및 복지향상이라는 주제였다.
우리는 발달장애인을 비롯한 의사소통의 어려움을 겪는 사람들을 위한 그림카드 방식의 의사소통 애플리케이션을 만들기로 했고,
그렇게 "악어톡(AAC Language Talk)"을 만들게 되었다⭐️ (AAC란 Augmentative and Alternative Communication으로 보완대체의사소통)
처음 앱을 만들어보며 열정적으로 불태운 악어떼와의 연말..! 감사하게 우리는 장려상을 받았다.
- 아래는 피우다 출품작 자료집에 실린 악어톡 소개 장표, 그리고 발표에서 사용했던 시연영상
What I Learned
ViewModel 로 데이터 다루기
- UI 컨트롤러에서 데이터를 다루는 로직까지 담당하게 되면 너무 과도한 책임.
- 또한 UI에 변화가 생길 때마다(화면 가로 세로 전환 등) 데이터를 저장하고 다시 불러와야 하는 작업이 필요한데, 이를 수행하려면 onSaveInstanceState() 메서드를 사용해야 한다. 이는 번거로울 뿐만 아니라, 데이터가 커지면 적합치 않다고 한다.
- 그래서 Activity의 생명주기와 상관없이 데이터를 관리할 수 있는 친구로서 viewModel이 유용하다.
- Problem
- AddCateActivity(카테고리를 추가하는 액티비티)에서 중복 카테고리명인지 아닌지 검사하기 위해
viewmodel.getCateByCateName(catename)으로 room database를 조회했다.
- 중복 카테고리명이 있다면 적절히 toast로 중복카테고리가 있음을 알리고, 저장을 막았다.
- 하지만, 중복 카테고리명이 없어서 성공적으로 카테고리가 새로 등록되고 나서, MainActivity로 돌아왔는데
이때까지도 중복된 카테고리명을 감시하는 observer가 살아있어서(?) "중복된 카테고리명이 존재합니다"라는 toast를 띄웠다.
- Try
- viewmodel의 lifecycle 및 여기서 다뤄지는 livedata로서의 categoryEntity들의 수명이, 액티비티(AddCateActivity)보다 수명주기가 길기 때문에 발생한 일이라고 짐작. 특히 아래 그림처럼 그저 finish()라는 함수호출시점에 viewmodel의 scope가 곧장 맞물려 종료되지 않는 것 같다.
- 적용해본 해결방식은 단순하다. 액티비티의 생명주기에 의존하지 않고, 수행할 작업이 끝났다면 viewmodel로 불러온 livedata의 observer를 remove해주었다.
val cateByCateName = aacViewModel.getCateByCateName(cateName)
cateByCateName.observe(this) { categoryEntity ->
if (categoryEntity == null) {
val bitmap = (addImage.getDrawable() as BitmapDrawable).getBitmap()
val stream = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 90, stream)
val cateIcon = stream.toByteArray()
replyIntent.putExtra("cateName", cateName)
replyIntent.putExtra("cateIcon", cateIcon)
aacViewModel.insertCate(CategoryEntity(cateName, bitmap))
setResult(Activity.RESULT_OK, replyIntent)
Toast.makeText(this, "카테고리가 등록되었습니다.", Toast.LENGTH_SHORT).show()
cateByCateName.removeObservers(this) // 이부분.
startActivity(Intent(this, MainActivity::class.java))
finish()
}
else {
Toast.makeText(applicationContext, "이미 존재하는 카테고리명입니다.", Toast.LENGTH_SHORT).show()
}
cateByCateName.removeObservers(this) // 그리고 이부분.
}