본문 바로가기
TellingUs: TellingMe(텔링미)

[TellingMe/Android] 텔링미 안드로이드 개발 🔥4차 스프린트🔥

by JongSeok 2024. 3. 13.

4차 스프린트 리뷰는 살짝 늦었습니다.

개인적으로 이런저런 이유로 바쁜 기간이었고, 팀에서도 백엔드 인수인계 이슈로 서버가 오랜 기간 내려가 있어서 작업을 할 수가 없었어요..흑

이번 스프린트에서 진행한 내용은 화면 전환에 사용되는 Navigation 설계를 개선했고, 소셜로그인 이후 사용자의 상세정보를 기입하는 화면의 UI를 구성하는 작업을 했습니다.

 

상제정보를 기입하는 화면은 아직 배포되지 않은 관계로 온전히 보여드릴 수는 없지만 대략적인 UI는 다음과 같습니다.

상세정보 기입 화면

크게 복잡한 화면은 아니지만 Compose로 UI를 구성하면서 xml과는 다른 방식으로 Graph에 접근하기 때문에 조금 까다로웠던 부분들이 있었는데 마지막에서 좀 더 작성하겠습니다.

 

NavGraphBuilder 패턴으로 분리

NavHost(
    navController = navController,
    startDestination = TellingMeScreenRoute.HOME.route
) {
    composable(route = TellingMeScreenRoute.OTHER_SPACE.route) {
        OtherSpaceScreen()
    }
    composable(route = TellingMeScreenRoute.MY_PAGE.route) {
        MyPageScreen(navigateToAlarmScreen = {
            navController.navigate(TellingMeScreenRoute.ALARM.route)
        })
    }
    composable(route = TellingMeScreenRoute.ALARM.route) {
        AlarmScreen(navigateToPreviousScreen = {
            navController.popBackStack()
        })
    }
    ...
}

기존에는 위와 같이 NavHost 내부에 화면 전환이 발생하는 모든 경우를 composable 함수를 통해 각각 정의하도록 개발했습니다.

아직은 개발 초기라 전환되는 화면이 많지 않아 composable 함수로 나열해도 크게 불편하지 않지만 개발이 진행되고 전환되는 화면이 많아질 텐데 "이걸 NavHost 안에 계속 다 넣는다고?" 라는 생각이 들었습니다.

그래서 앞으로 점점 많아질 화면 전환에 대비하기 위해, 어떤 라우터에서 어떤 화면으로 이동하는지 쉽게 찾을 수 있고 편리하게 관리할 수 있도록  NavGraphBuilder 패턴으로 개선하기로 결정했습니다!

NavHost(
    navController = navController,
    startDestination = startDestination,
) {
    authGraph(
        navController = navController
    )
    homeGraph(
        navController = navController
    )
    mySpaceGraph(
        navController = navController
    )
    otherSpaceGraph(
        navController = navController
    )
    myPageGraph(
        navController = navController
    )
    ...
}

NavHost 내부가 훨씬 깔끔해졌습니다.

그 많던 composable 함수는 기능 단위에 따라 각 Graph에서 정의됩니다.

fun NavGraphBuilder.homeGraph(
    navController: NavController
) {
    navigation(
        route = HomeDestinations.ROUTE,
        startDestination = HomeDestinations.HOME
    ) {
        composable(route = HomeDestinations.HOME) {
            HomeScreen(navController = navController)
        }

        composable(route = HomeDestinations.RECORD) {
            RecordScreen(
                navController = navController
            )
        }
		...
    }
}
object HomeDestinations {
    const val ROUTE = "homeRoute"

    const val HOME = "home"
    const val RECORD = "record"
}

homeGraph의 일부입니다.

각각의 Graph에서는 Destination에 정의된 라우터에 따라 화면전환 즉, 컴포저블 함수를 호출합니다.

이제 기능 단위로 컴포저블 함수가 분리되었기 때문에 어디서 어디로 화면전환이 발생하는지 더 쉽게 찾을 수 있습니다.


📝 4차 스프린트 리뷰

고민 1. 회원가입 화면에서 모든 흐름의 데이터 공유

- 예를 들어, 닉네임을 입력하는 화면이 있고, 다음 화면에서 생년월일을 입력하는 화면이 있을 때 생년월일을 입력하는 화면에서 뒤로가기를 눌러 닉네임을 입력하는 화면으로 돌아오더라도 이전에 입력했던 닉네임이 유지되어야 합니다.

각 스크린에서 ViewModel을 공유해 데이터를 유지할 수 있을 것이라고 생각했고 Compose의 Graph에서 ViewModel을 공유하는 방법에 대해 학습할 수 있었습니다.

...
composable(route = AuthDestinations.Signup.SIGNUP_BIRTH_GENDER) {
    val parentEntry = remember(it) {
        navController.getBackStackEntry("AuthDestinations.Signup.SIGNUP_NICKNAME")
    }
    SignupBirthGenderScreen(
        navController = navController,
        viewModel = hiltViewModel(parentEntry)
    )
}

특정 스크린의 NavBackStackEntry를 ViewModel을 생성하는 hiltViewModel의 생성자로 전달해 회원가입 관련 스크린에서 하나의 ViewModel을 사용할 수 있도록 유지했습니다.

 

고민 2. 매우 빠른 속도로 닉네임을 입력할 때 유효성 검사

- 사용자가 만약 '텔링미'라는 닉네임을 사용하기 위해 텔링미를 입력한다면

[ㅌ → 테 → 텔 → 텔ㄹ → 텔리 → 텔링 → 텔링ㅁ → 텔링미] 순서로 입력하게 됩니다.

팀에서 사용자가 입력한 닉네임에 대해서 즉각적인 유효성 검사 결과를 경고 문구처럼 보여주기를 원하기 때문에 'ㅌ'에서 '텔링미'까지 입력되는 모든 문자열에 대해서 유효성을 검사해야 합니다.

그렇지만 이렇게 되면 검사가 불필요한 '텔ㄹ' 같은 입력도 확인해야 하기 때문에 별도의 처리가 필요합니다.

 

이런 경우처럼 마지막 입력이 있고 특정 시간 동안 더 이상 입력이 없는 경우 마지막 입력으로 이벤트를 처리하는 기법을 "디바운싱" 라고 하는데 Compose UI에서 LaunchedEffect 블럭을 통해 간단하게 구현할 수 있었습니다.

LaunchedEffect(key1 = nickname) {
    if (nickname.isNotBlank()) {
        delay(1000)    // 1초 동안 입력이 없는 경우
        viewModel.updateNickname(nickname)    // 유효성 검사 호출
    }
}

사용자가 닉네임을 입력하면 nickname의 상태가 변경되고, 해당 상태가 변경되더라도 updateNickname 메소드를 바로 호출하지 않고 1초 동안 상태 변경이 없는 경우에 유효성 검사 API를 호출할 수 있도록 변경했습니다.

Kotlin에서 제공하는 Flow에서는 확장함수를 통해 디바운싱 기법을 지원하는데 다음에 기회가 된다면 Flow를 활용해서 적용해 봐야겠습니다!

 

고민 3. xml과는 다른 UI 컴포넌트(BasicTextField)

- xml에서는 텍스트를 입력하는 입력 박스를 EditText를 통해 구성할 수 있었는데 Compose에서는 TextField를 통해 구성합니다.

EditText에서는 전체 지우기 역할을 하는 endIcon 속성을 제공해서 Compose의 TextField에서도 동일하게 지원할 줄 알았는데 TextField에서는 endIcon을 자체적으로 지원하지 않습니다.

Compose에서 TextField를 입맛대로 커스텀하기 위해서는 BasicTextField 컴포저블을 사용해야 했고, decorationBox를 컴포저블 블럭으로 정의해 수정사항이 많은 경우 오히려 커스텀하기 편리하다고 느꼈습니다.

728x90
반응형