예를 들면 968회차의 응답은 다음과 같은 json 형식으로 돌아옵니다. totSellamnt는 총 판매액, returnValue는 API응답 성공여부, drwNoDate는 추첨일, firstWinamnt는 1등상금, drwNo는 회차, drwtNo1~6이 당첨번호이고, bnusNo가 보너스번호입니다.
자 그러면 drwtNo1~6, bnusNo의 값을 추출해서 나의 값과 비교하면 되겠네요. 당첨번호는 네트워크에서 백그라운드로 받아와야 하기 때문에 코루틴으로 처리하도록 하면 되겠고, 받아온 json으로부터 값을 추출하는데에는 gson 라이브러리를 사용하면 될 것 같습니다. gson의 사용법에 대해서는 SharedPreferences를 json 포맷으로 관리하기 강의에서 다루었으니 그것을 참고하시면 되겠습니다.
그럼 네트워크로 당첨번호를 받아오는 getLottoNumbers 함수와, 두 값을 비교하는 whatIsRank 함수를 만들도록 하겠습니다.
getLottoNumbers는 전달받은 API 주소에 조회를 요청하고 반환값을 처리합니다. 이 함수는 네트워크를 사용해야 하므로 코루틴 안에서 사용하기 위해 suspend 키워드를 붙여줍니다. API는 단순한 GET 요청이므로 소켓을 만들지 않고 간단하게 URL과 readText로 값을 읽어오도록 하였습니다.
API 주소와 결합할 round에는 내 번호와 비교할 당첨번호의 회차를 넣습니다. 읽어온 값의 returnValue가 success이면 drwtNo1~6, bnusNo 값을 추출하여 배열에 모으도록 하였습니다. 마지막에는 당첨번호를 확인할 회차를 추가하였습니다.
다음은 내 번호와 당첨번호를 비교하는 함수를 만듭니다. 로또의 규칙은 표의 내용과 같이 되어 있습니다. 그래서 whatIsRank에서는 내 로또번호와 당첨번호를 전달받아 처음 6개까지의 숫자를 비교해 일치한 갯수를 세고, 일치한 번호의 갯수가 5개이면 추가로 보너스번호인 7번째 번호가 일치하는지 확인하도록 하면 됩니다.
그러면 번호생성버튼을 눌렀을 때 생성된 번호를 당첨번호와 비교하는 코루틴을 추가하겠습니다. CoroutineScope로 스코프를 정의하고, 네트워크에서 값을 가져올 것이기 때문에 Dispatchers는 IO로 설정합니다. 그리고 작업 후에 값을 반환할 필요가 없기 때문에 빌더는 launch를 사용합니다.
작업은 getLottoNumbers에서 얻어진 결과값를 whatIsRank에 사용해야 하기 때문에 winningNumbers는 async로 자식 코루틴을 만듭니다. whatIsRank에 전달할때는 Deferred 반환값을 끝까지 처리하여 ArrayList를 전해주기 위해 await를 사용합니다. 최종적으로 텍스트뷰에 표시할 텍스트를 만들때도 await를 사용해서 처리가 모두 종료될 때까지 기다리도록 합니다.
마지막으로 text를 텍스트뷰에 표시해야 하는데 이 부분은 UI를 건드리는 부분이라 메인스레드에서 작업이 이루어져야 하기 때문에 작업할 스레드를 withContext로 전환한 뒤 당첨결과를 표시하면 되겠죠.
액티비티가 종료되었을 때 실행중인 코루틴이 살아남는것을 방지하기 위해 취소하는 기능을 추가해 보도록 하겠습니다. 알기쉬운 코루틴 이론 강의에서 설명했던 것처럼 코루틴을 취소하기 위해서는 job에 대해 cancel을 수행하면 됩니다. 그런데 generateButton 안에서 수행된 CoroutineScope에 대해서는 반환되는 job을 오브젝트로 만들지 않았습니다.
이런 경우에는 Job 전역변수를 하나 만들어 주고 디스패처와 결합합니다. 코틀린의 코루틴은 자식 코루틴이 취소되면 연결된 부모 코루틴이 모두 취소되는 특징을 가지고 있으므로 onDestroy에서 job에 대해 cancel을 수행하면 연결시킨 코루틴을 취소할 수 있습니다.
실제 예제에서는 뷰모델을 이용하게 되므로 lifecycle 모듈과 결합하여 사용하면 좀 더 앱의 라이프사이클과 연동된 생명주기를 가지게 할 수 있습니다.