본문 바로가기

안드로이드/트러블 슈팅

[안드로이드 트러블 슈팅] 코틀린에서 가변인자(vararg)를 사용할 때 주의할 점


개발을 하다 보면 여러 가지 트러블 슈팅을 겪게 되는데 이번 포스팅에서는 안드로이드 코틀린에서 가변인자(vararg)를 사용할 때 발생한 문제와 주의할 점에 대해 공유하고자 합니다. 코틀린의 vararg 키워드는 함수에 가변 개수의 인자를 전달할 수 있어서 큰 유연성을 제공합니다. 그러나 제대로 알고 사용하지 않는다면, 예상치 못한 결과에 직면할 때가 있는데 vararg를 사용하다가 발생한 문제와 해결 과정에 대해 포스팅하겠습니다.

1. 문제 상황

코틀린에서 vararg를 사용해서 문자열 리소스 ID와 포맷 인자를 받아 로컬라이즈 된 문자열을 반환하는 함수를 따로 구현했었습니다. 제가 개발했던 서비스는 다국어를 지원하고 있었고 앱 내에 언어를 선택하는 기능이 있었기 때문에 Context.getString()을 그대로 사용하지 않고, 래핑 해준 형태로 사용했기 때문입니다. 아래는 예시 코드입니다.

// MainActivity.kt
@AndroidEntryPoint
class MainActivity : ComponentActivity() {

    fun getStringWrapper(resId: Int, vararg formatArgs: Any?): String {
        return getString(resId, formatArgs)
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
		...
        
        val text = getStringWrapper(R.string.blog_name_format, "개발자가 들려주는 코딩 이야기")
        Log.d("test", "text $text")
    }
}

// strings.xml
<resources>
    <string name="blog_name_format">제 블로그 이름은 %s 입니다.</string>
</resources>


위 함수의 결과로 반환된 text를 로그로 출력했을 때, 문자열이 예상과 다르게 포맷되지 않고 아래와 같이 메모리 주소 형태로 출력되는 것을 볼 수 있었습니다.

제 블로그 이름은 [Ljava.lang.Object;@81582a2 입니다.

2. 문제 원인

안드로이드 Context 내에 있는 getString() 함수를 살펴보면 가변 인자를 받도록 되어있습니다. 위 문제의 원인은 getString() 함수에 formatArgs를 그대로 전달했기 때문입니다. 이를 이해하려면 코틀린의 vararg 작동방식에 대해 알고 있어야 합니다.

안드로이드 Context 내의 getString() 함수
안드로이드 Context 내의 getString() 함수


코틀린에서는 vararg 키워드로 받은 인자를 실제로는 배열로 처리하며, 이 배열을 다른 vararg 매개변수를 받는 함수에 직접 전달하면 배열 자체가 하나의 인자로 전달됩니다. 그래서 formatArgs의 내용이 문자열 포맷의 인자로 사용되지 않고 배열의 toString() 결과가 출력된 것이었습니다.

3. 해결 방법

이 문제를 해결하기 위해서는 가변 인자를 다른 함수에 전달할 때 * 연산자를 사용해야 합니다. 코틀린에서는 포인터 개념이 없기 때문에 * 연산자는 spread operator의 기능을 합니다. *는 배열 앞에 위치하며 배열의 각 요소를 함수의 가변 인자로 전달합니다. 배열을 하나의 객체로 받는 대신 각 요소를 개별 인자로 받아야 할 때 사용할 수 있습니다. 수정된 getStringWrapper 함수는 다음과 같습니다.

@AndroidEntryPoint
class MainActivity : ComponentActivity() {

    fun getStringWrapper(resId: Int, vararg formatArgs: Any?): String {
        // formatArgs 앞에 * 연산자 추가
        return getString(resId, *formatArgs)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        
        val text = getStringWrapper(R.string.blog_name_format, "개발자가 들려주는 코딩 이야기")
        Log.d("test", "text $text")
    }
}


위 코드처럼 formatArgs 앞에 스프레드 연산자 *를 사용해서 가변 인자로 전달을 할 수 있게 되었고 배열의 toString() 결과가 출력되던 로그는 아래와 같이 정상적으로 출력됐습니다.

제 블로그 이름은 개발자가 들려주는 코딩 이야기 입니다.

4. 결론

이번 포스팅에서는 안드로이드 앱 개발 중에 문자열 리소스 ID와 포맷 인자를 받는 함수를 만들면서 발생한 문제와 해결과정 대해 살펴보았습니다. vararg를 받는 함수에 데이터를 전달할 때, 배열이 아닌 가변 인자로써 데이터를 전달하려면 * 연산자를 사용해야 한다는 것을 알게 되었습니다. 이 글이 도움이 되었으면 좋겠습니다.