본문 바로가기

내일배움캠프 안드로이드 3기

[TIL] 24.04.02 팀 프로젝트

1. 팀 프로젝트

 오늘은 거의 하루종일 팀 프로젝트만 진행한 것 같다. 다른 팀원들의 코드를 봐줄 일이 잦았는데, 나는 의도가 옳았다면 나쁜 코드는 아니라고 생각하는 편이라, 되도록 의도가 맞지 않은 부분 위주로 피드백하려고 했다. 조금 더 나아가서, 개선할 수 있겠다 싶으면 개선점을 첨언하기도 했다. 안드로이드 자체가 상대적으로 러닝커브가 완만하지는 않은 느낌이라, 초심자들이 고생하는 것도 이해는 간다. 그래도 잘 설명해서 팀원들이 이해하면 그거대로 기분 좋은 경험인 것 같다.

 그리고 형상 관리 이슈부터, 코틀린 문법, 안드로이드 구현에 이르기까지 다양한 질문을 받고 답을 하다보면, 내 베이스 지식도 한결 단단해지는 느낌이고..

 

 그러는 동시에, 내가 맡은 부분의 코드도 작성해야 했는데 일단 과제의 제약사항이 문제였다. RecyclerView를 비롯해 Adapter 조차 되도록 사용하지 않도록 했기 때문에, 사실상 ListView도 쓸 수 없는 상황에서 리스트 형태의 UI를 제공해야 했다. 썩 내키지는 않는 방법이었지만, ScrollView 내에 LinearLayout을 집어넣고 Activity 코드 상에서 addView로 아이템들을 다 집어넣는 방법을 쓰기로 했다.

 

 

 먼저 대강, 각각의 아이템들이 바인딩 될 뷰부터 만들었다. CircleImageView 라이브러리를 써서, 둥근 프로필 사진을 구현한 것 외에는 특별할 것 없는 디자인이다. CardView 내부가 FrameLayout이라, 내부에서 ConstraintLayout으로 한 번 감싸준 게 아쉽다면 아쉬운 점이다. 그냥 루트 뷰로 CardView를 써보기도 했는데, 그렇게 하면 layout_margin 속성이 의도대로 동작하지 않아서 문제가 생겼다. ConstraintLayout은 중첩해서 쓰지 말자 주의인데, 너무 아쉽다.

 

 

 부가적인 기능이 많이 필요하지는 않기 때문에, MainActivity의 UI 또한 일단 심플하게 작성했다. 우측 상단의 아이콘을 이용해 마이페이지로 이동할 수 있도록 할 예정이다. 해당 아이콘과 텍스트는 Flow로(widget.Flow) 감싸서, 리스너 등을 처리하기로 했다.

 

    private fun cardBinder(post: Post, writer: User): View {
        val itemCard = LayoutInflater.from(this).inflate(
            R.layout.item_post, null
        )
        itemCard.findViewById<TextView>(R.id.nameTextView).text = writer.userId
        itemCard.findViewById<CircleImageView>(R.id.profileImageView).setImageFromID(writer.profileImageId)
        itemCard.findViewById<ImageView>(R.id.contentImageView).setImageFromID(post.imageId)
        itemCard.findViewById<TextView>(R.id.titleTextView).text = post.title

        itemCard.setOnClickListener {
            startActivity(Intent(this, DetailActivity::class.java).apply {
                putExtra(USER_DATA, writer)
                putExtra(POST_DATA, post)
            }
            )
        }
        return itemCard
    }

 

 로직 또한 심플한데, 일단 유저와 글 정보를 받아서 아까 커스텀한 아이템 뷰에 바인딩 해 반환하도록 메소드를 만들었다. 각 아이템뷰들을 눌렀을 때 상세페이지를 나타내는 DetailActivity로 이동해야 하므로, Intent를 적절하게 생성해 실행하도록 했다. 

 

    private fun ImageView.setImageFromID(imageId: Int) {
        this.setImageDrawable(AppCompatResources.getDrawable(this@MainActivity, imageId))
    }

 

 그리고 이미지 뷰에 대해 drawable id값으로부터 이미지를 로드하기까지의 코드가 지저분해 보여서 확장함수로 빼버렸다.

 

val innerLayout = findViewById<LinearLayoutCompat>(R.id.contentLayout)
        val posts = DummyServer.loadPosts()
        val users = DummyServer.loadUsers()
        val uidMap = users.associateBy { it.uid }

        setListener()

        posts.forEach {
            val writer = uidMap[it.uid]!!
            innerLayout.addView(
                cardBinder(it, writer)
            )
        }

 

 onCreate() 내에서 이번 과제를 위해 만들어둔 더미데이터를 받아와 뿌려주도록 했고, 모두 정상적으로 동작했다. 그리고 User와 Post 클래스는 Serializable을 상속해 Intent로 주고받을 수 있게 했고, 팀원들과 함께 사용할 더미데이터 또한 직접 생성해 뒀다. 

 

 

 이런식인데, 서버로부터 받아온 데이터를 파싱 및 사용하기 좋게 매핑까지 끝낸 객체 리스트를 받는 상황을 상정한 것이라 List<User>, List<Post>를 바로 받을 수 있게 했다. 이미지 id값들은 편의상 내부 리소스를 이용하도록 했다. 대략 이정도로 해서 내일 모두 함께 PR 및 코드 리뷰 진행 후, 병합을 할 것 같고 이후에 디테일을 잡아나갈 예정이다.

 

 

2. 면접 연습

 오늘 담당 튜터님과 함께 면접 연습도 간략하게 진행했는데, Activity의 생명주기 및 다양한 상황에서의 적용과 설명, val, var, lateinit, by lazy에 대한 설명, Intent(명시적/암시적)에 대한 설명 및 예시와 같은 질문과 답변들이 오고 갔다. 

 긴장되는 와중에 아는 것을 설명한다는 것이 쉬운 일은 아니었지만, 평소에 친구에게 설명하듯이 답변 하니까 한결 나은 느낌이었다. 대체로 좋은 답변을 낼 수 있었던 것 같고, 튜터님이 조금 더 보완해 볼 점을 여럿 알려주셨다. 특히나 by lazy와 관련해서 delegate 키워드, 그리고 코틀린이 원칙적으로 초기화되지 않은 변수를 허용하지 않기 때문에 지연 초기화 등이 도입되었다는 점 등이 주목할만했다.

 그리고 해당 튜터님이 내 본 커리큘럼 과제를 피드백해주시는 튜터님이라, 그간 과제에 대한 피드백에서 생겼던 또 다른 질문들을 여쭤보았다. 좋은 답변도 많이 받았고, 그와 관련해서 개발할 때의 방향성 등에 많은 조언을 해주셨다.

 이제 대부분의 튜터님과 말을 섞어본 것 같은데, 확실히 튜터님들이 다들 경험도 풍부하고 좋으신 분들 같다.