본문 바로가기

[Android]/Coroutines

[Coroutines 마스터] 흔히 하기 쉬운 실수(CountDown Timer)

안녕하세요!

Coroutine을 사용할 때 있어서 흔히 하기 쉬운 실수에 대해서 예제와 함께 설명드리도록 하겠습니다.

 

[예제 화면]

단순하게 TextView 2개, Button 1개로 화면을 구성해보았습니다.

아래 코드를 보면 알겠지만, Button을 클릭하면 5초 동안 타이머를 TextView에 (남은 초) 보여주며 타이머가 끝나면 "Ended" 문구를 다른 TextView에 출력합니다.

 

[코드]

Dispatchers.Main.immediate을 사용하였으며 runTime()이라는 suspend 함수를 만들어, seconds가 0이 될 때까지 1씩 차감하여 화면에 남은 초를 출력합니다.

 

[실행화면]

무언가 이상하지 않나요? 

전 포스트에서 Dispatchers.Main.immediate을 사용하면 순서가 보장된다고 하였습니다. 

그럼 "Started"가 TextView에 출력되고 Button이 disabled 되며, runTime() suspend 함수의 실행이 끝나고 나면 "Ended" 문구가 출력되고, Button이 enabled 되어야 합니다.

하지만 실행화면을 보면, 바로 "Ended"가 출력되고, Button은 계속 enabled 상태여서 연속적으로 클릭이 됩니다.

 

이유는 runTime이 suspend 함수이며, 함수 내부에는 delay(1000) suspend 함수가 불립니다. 즉 1초씩 delay가 되므로, coroutineScope 내부에 있는 runTime은 suspend 되어 계속 실행을 하고 있는 와중에 button과 textview의 작업은 끝마쳐진 것입니다.

 

대부분 안드로이드 개발을 할 때 ViewModel을 사용하며, ViewModel 내에서 Repository 패턴을 사용하여 네트워크 작업을 하곤 합니다. 이럴 때 coroutineScope 외부에서 isLoading=true 형태를 사용하고 coroutineScope 외부에서 네트워크 작업이 끝나면 isLoading=false로 값을 변경해주곤 하는 데, 이런 경우 네트워크 작업이 오래 걸리게 되면, isLoading은 CoroutineScope와는 별개로 계속 false를 유지하게 됩니다. (위와 같이)

 

[해결방법]

단순히 TextView 출력 코드와, Button Enable / Disable 코드를 runTime() suspend 함수가 호출되는 CoroutineScope 내부에 작성해주면 됩니다. 아주 쉽죠?

 

[실행화면]

CoroutineScope 내부에 작성한 결과, 의도한 대로 TextView에 "Started"가 출력되며, Countdown이 끝날 때까지 Button은 Disabled 되어 있습니다.

 

[결론]

어렵게 생각하지 마시고 순서 보장을 위해 CoroutineScope 내부에서 순서에 맞게 실행되도록 코드를 작성해줍시다.

코루틴은 Structured Concurrency를 보장하기 때문에 어렵게 느껴지면서도 쉽게 작성할 수 있는 유용한 Concurrency 디자인 패턴입니다.

계속해서 유용한 포스트를 올리도록 하겠으며, Structured Concurrency에 대해서도 계속 말씀드리도록 하겠습니다.

 

[Github]

https://github.com/DJDrama/Coroutines/tree/Mistakes

 

GitHub - DJDrama/Coroutines

Contribute to DJDrama/Coroutines development by creating an account on GitHub.

github.com

위 실행 코드를 받아서 실행해보시기 바랍니다.

 

 

공감구독해주시면 감사하겠습니다.