StartActivity, StartActivityForResult 우아하게 만들어보기

SSY·2023년 2월 5일
0

Kotlin

목록 보기
3/8
post-thumbnail

시작하며

startActivity나 startActivityForResult를 쓰며 부분적으로 쓰이는 중코드들이 있다. 매번 사용하는데 불편함을 느꼈고, 이를 개선하기 위한 함수를 만들어보았다. 만드는데 있어, 'Kotlin In Action'을 참고하여 만들었고 나름 괜찮은 함수라 생각되어 앞으로 프로젝트할때 요긴하게 써먹을까 한다.

1. startActivity

1.1. 기존의 불편함?

우리가 startActivity를 할땐 보통 아래와 같이 한다.

Extra값이 없을 때

val intent = Intent(this, TestActivity::class.java)
startActivity(intent)

Extra값이 있을 때

val intent = Intent(this, TestActivity::class.java)
intent.putExtra("hello1", "hello1")
intent.putExtra("hello2", "hello2")
intent.putExtra("hello3", "hello3")
startActivity(intent)

하지만 여기서 이들을 좀 더 개선하면 아래의 정도가 될 수 있다.

Extra값이 없을 때

Intent(this, TestActivity::class.java).apply {
    startActivity(intent)
}

Extra값이 있을 때

Intent(this, TestActivity::class.java).apply {
    putExtra("hello1", "hello1")
    putExtra("hello2", "hello2")
    putExtra("hello3", "hello3")
    startActivity(this)
}

바로 위 코드는 내가 종종 사용했었던 코드이다. 하지만 문제는 우리가 액티비티를 시작해줘야할 일이 있을때마다 위와 같은 상용구 코드를 반복해줘야 한다는 점이다.

하지만 다음의 코드로 호출 부분을 아주 간결하게 만들 수 있다.

Extra값이 없을 때

start<TestActivity>()

Extra값이 있을 때

start<TestActivity> {
    putString("hello", "startActivity")
}

개인적으로 매우 깔끔해졌단 생각이 들어 함수를 만들면서도 뿌듯했다. 물론 뭐 앞으로 내가 더 성장하게 되면 더 좋은 함수가 나올 수도 있겠지만 지금의 상태로는 나름 마음에 든다.

1.2. 결과물

그러면 구현부를 살펴보자.

private inline fun <reified T: Activity> Context.start(
    withExtras: Bundle.() -> Unit = {}
) {
    Intent(this, T::class.java).apply {
        putExtras(Bundle().apply(withExtras))
        startActivity(this)
    }
}

구현부를 보면 알다시피, 어떤 형태의 Activity를 받을 수 있도록 Generic타입을 선언해 주었다. 그리고 Extra들이 초기화될 수 있는 경우도 대비하였고 DSL함수로 따로 만들어주었다.

2. startActivityForResult

2.1. 기존의 불편함?

우리가 startActivityForResult를 할땐 보통 아래와 같이 한다.

[참고]
현재는 startActivityForResult는 Deprecated되었다. 그리고 ActivityResultLauncher를 대신 사용한다. 그 부분에 대한 개선 코드를 제시하려 한다.

콜백을 받는 forResult 변수 선언 부분

val testForResult = registerForActivityResult(
    ActivityResultContracts.StartActivityForResult()
) { result ->
    if (result?.resultCode == Activity.RESULT_OK) {
    }
}

액티비티 시작 호출 부분

Intent(this, TestActivity::class.java).apply {
    testForResult.launch(this)
}

보통은 forResult결과값을 받기 위해 activity를 시작하는 로직은 위와 같은 형태를 따른다.

  • forResult부분은 멤버로 선언해둔다.
  • 그리고 forResult를 참조하여 activity를 시작한다.
    (이때 이 부분의 로직은 onCreate부분 한참 밑쪽에 위치해있다.)

위의 구조가 분석하기에 어렵진 않다. 하지만 문제는 프로젝트가 커졌을때다. 프로젝트가 커지면 하나의 액티비티에 몇백줄이나 되는 상황이 올 수도 있다. 그럴땐 위의 forResult멤버 프로퍼티를 하나하나 찾아가기가 어렵고 매우 귀찮아진다.

그리고 startActivityForResult종류의 로직(forResult변수 + activity시작 부분)이 많아졌을땐 더욱 헷갈리게 된다. forResult변수가 어떤놈인지 cmd + b를 클릭해가며 하나하나 확인하다보면 좀 빡이 치게 될 수도 있다.

내가 내린 결론은 마치 Coroutine이나 RxJava처럼 호출하는 부분에서 result결과값도 바로 파악할 수 있으면 좋겠다 싶었다. 그래서 아래는 개선된 코드다.

2.2. 결과물

우선 백문이 불여일견이라고, 호출부의 코드와 선언부의 코드를 한번 봐보자.

Extra 값이 없을 때

start<TestActivity>()
    .forResult { result ->
        Log.i("bundles", "result : ${result.data?.extras?.getString("hello")}")
    }

Extra 값이 있을 때

start<TestActivity> {
    putString("hello", "startActivityForResult")
}.forResult { result ->
    Log.i("bundles", "result : ${result.data?.extras?.getString("hello")}")
}

위 구조는 startActivity로직과 상당히 유사하다. 우선, 시작하고자 하는 activity를 Generic타입으로 받아둔다. 그리고 Extra값들을 초기화하고 싶으면 첫 번째 파라미터 타입(함수형)에 맞게 Extra들을 초기화 해준다.

그렇게 해서 TestActivity를 시작시키고, 이제 다시 돌아왔을 땐 바로 forResult에서 결과를 받을 수 있다.

그럼 구현부쪽의 코드는 어떤지 살펴보자. 이 구현부의 코드는 2가지의 메소드로 이루어져 있다.

activity를 시작하는 부분

private inline fun <reified T: Activity> Context.start(
    withExtras: Bundle.() -> Unit = {}
): Intent {
    return Intent(this, T::class.java).apply {
        putExtras(Bundle().apply(withExtras))
    }
}

결과를 받아 처리하는 forResult부분

private fun Intent.forResult(
    resultBlock: (ActivityResult) -> Unit
) {
    registerForActivityResult(
        ActivityResultContracts.StartActivityForResult()
    ) { result ->
        resultBlock(result)
    }.launch(this)
}

우선 activity시작하는 부분의 코드는 이전 'startActivity'로직과 상당히 유사하다. 하지만 차이가 있다면 바로 'Intent를 반환'해준다는 것이다.

Intent를 반환하는 이유는 그 Intent를 수신객체로 받아 forResult로직부분에서 activity최종적으로 시작해줘야 하기 때문이다.

두 번째 로직을 봐보면 알겠지만 'forResult로직은 Intent를 수신객체고 받고 있음'을 알 수 있다. 그리고 해당 함수에선 수신받은 Intent객체를 launch()에 인자로 넣어주고 있음을 알 수 있다.

이렇게 2개의 메소드를 조합하면 startActivityForResult로직을 완성할 수 있다.

마치며

Kotlin In Action은 Kotlin언어 개발자가 만든 책이라고 한다. 그래서 그만큼 Kotlin의 본질을 더 꿰뚫을 수 있는 책이지 않을까 한다.

그리고 Kotlin의 아래 특징들을 잘 사용하면 Java 언어보다 '추상화를 더 잘 사용해줄 수 있을' 뿐만 아니라 '가독성'도 훨씬 더 좋게 만들 수 있지 않나 싶다.

추상화와 가독성을 극대화할 수 있는 Kotlin만의 도구

  • Generic reified
  • infix fun
  • inline fun
  • DSL fun
  • 수신객체 지정 람다 등...

이전에 만들었던 String타입의 Json을 Data Class로 우아하게 파싱하기의 경우도 위 문법적 지식을 사용하였다. 그리고 그 결과 추상화 수준을 높였을 뿐만 아니라 호출부의 가독성까지 더욱 높일 수 있었다고 생각한다.

profile
불가능보다 가능함에 몰입할 수 있는 개발자가 되기 위해 노력합니다.

0개의 댓글