Android/Kotlin

<정리> WorkManager를 이용한 주기적인 UI 업데이트2

re트 2024. 4. 26. 14:41
728x90

생각보다 빠르게 어제의 마지막 고민이 해결되어서 이렇게 글을 남긴다.

물론 완벽한 방법은 아닌 것 같지만 이렇게도 할 수 있다는 걸 남겨놓는게 좋을 거 같다.

 

1. 가장 처음 방식

그냥 뷰모델에서 코루틴을 이용해 데이터를 가져와 조건을 체크하고 그 결과를 라이브데이터에 넘겨줌

그냥 함수이기 때문에 호출할 때 딱 한번 실행됨

더 실행되게 하고 싶다면 원하는 위치마다 호출 코드를 넣어줘야함

fun updateNotificationSign() {
    if (savedPrefRepository.getFlag().not()) {
        savedPrefRepository.setFlag(true)
        viewModelScope.launch(Dispatchers.IO) {
            val datePattern = DateTimeFormatter.ofPattern("yyyy-MM-dd")
            val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.S")

            val savedServiceList = savedPrefRepository.getSvcidList().map {
                reservationRepository.getService(it)
            }

            // 예약 시작까지 하루 남은 서비스의 개수
            val list = savedServiceList.filter {
                datePattern.format(
                    LocalDateTime.parse(
                        it.RCPTBGNDT,
                        formatter
                    )
                ) > datePattern.format(
                    LocalDateTime.now()
                ) && datePattern.format(
                    LocalDateTime.parse(
                        it.RCPTBGNDT,
                        formatter
                    )
                ) < datePattern.format(
                    LocalDateTime.now().plusDays(2)
                )
            }.size

            // 예약 마감까지 하루 남은 서비스의 개수
            val list2 = savedServiceList.filter {
                datePattern.format(
                    LocalDateTime.parse(
                        it.RCPTENDDT,
                        formatter
                    )
                ) < datePattern.format(
                    LocalDateTime.now()
                ) && datePattern.format(
                    LocalDateTime.parse(
                        it.RCPTENDDT,
                        formatter
                    )
                ) > datePattern.format(
                    LocalDateTime.now().minusDays(2)
                )
            }.size

            // 예약 가능한 서비스의 개수
            val list3 = savedServiceList.filter {
                datePattern.format(
                    LocalDateTime.parse(
                        it.RCPTBGNDT,
                        formatter
                    )
                ) == datePattern.format(
                    LocalDateTime.now()
                )
            }.size

            _notificationSign.postValue(list != 0 || list2 != 0 || list3 != 0)
        }
    }
}

 

2. 어제 도달한 방식

이거는 어제 포스팅 봐주시면 감사합니다~

(https://retry-thinksubox.tistory.com/283)

여기서 아쉬웠던 건 이 비즈니스 로직이 뷰에 있다는 거였다.

그래서 뷰모델로 옮기고 싶은게 어제 마지막 생각이었다.

사실 옮기려다가 실패한 건 여러 방법을 둘러봤을 때 라이브데이터를 평소 쓰듯이 WorkManager한테서 받아 사용하는 게 어려웠다.

그리고 뷰모델에서 옵저빙을 할 수 없다는 것도 큰 문제였다. 왜냐하면 lifeCycleOwner가 없기 때문이다...

// NotificationWorker.kt
import android.content.Context
import android.util.Log
import androidx.work.CoroutineWorker
import androidx.work.Data
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import com.google.gson.Gson
import com.wannabeinseoul.seoulpublicservice.SeoulPublicServiceApplication
import kotlinx.coroutines.delay
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter

class NotificationWorker(
    appContext: Context,
    params: WorkerParameters
) : CoroutineWorker(appContext, params) {

    override suspend fun doWork(): Result {
        return try {
            val container = (applicationContext as SeoulPublicServiceApplication).container
            val savedList = getSvcIdList()
            val database = container.dbMemoryRepository
            val savedServiceList = savedList.map { database.findBySvcid(it) }

            val datePattern = DateTimeFormatter.ofPattern("yyyy-MM-dd")
            val datePattern2 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH")
            val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.S")

            // 예약 시작까지 하루 남은 서비스의 개수
            val list = savedServiceList.filter {
                datePattern2.format(
                    LocalDateTime.parse(
                        it?.RCPTBGNDT,
                        formatter
                    )
                ) >= datePattern2.format(
                    LocalDateTime.now().plusDays(1)
                ) && datePattern.format(
                    LocalDateTime.parse(
                        it?.RCPTBGNDT,
                        formatter
                    )
                ) == datePattern.format(
                    LocalDateTime.now().plusDays(1)
                )
            }.size

            // 예약 마감까지 하루 남은 서비스의 개수
            val list2 = savedServiceList.filter {
                datePattern2.format(
                    LocalDateTime.parse(
                        it?.RCPTENDDT,
                        formatter
                    )
                ) >= datePattern2.format(
                    LocalDateTime.now().plusDays(1)
                ) && datePattern.format(
                    LocalDateTime.parse(
                        it?.RCPTENDDT,
                        formatter
                    )
                ) == datePattern.format(
                    LocalDateTime.now().plusDays(1)
                )
            }.size

            // 예약 시작한 서비스의 개수
            val list3 = savedServiceList.filter {
                datePattern.format(
                    LocalDateTime.parse(
                        it?.RCPTBGNDT,
                        formatter
                    )
                ) == datePattern.format(
                    LocalDateTime.now()
                )
            }.size

            val outputData: Data = Data.Builder()
                .putBoolean("result", (list != 0 || list2 != 0 || list3 != 0))
                .build()

            Log.d("dkj", "$list")
            Log.d("dkj", "$list2")
            Log.d("dkj", "$list3")

            setProgress(outputData)
            delay(1000)
            Result.success()
        } catch (throwable: Throwable) {
            Result.failure()
        }
    }

    private val pref =
        appContext.getSharedPreferences("SavedPrefRepository", Context.MODE_PRIVATE)
    private val gson = Gson()

    private val keySvcidList = "keySvcidList"
    private fun getSvcIdList(): List<String> {
        val json = pref.getString(keySvcidList, null) ?: return emptyList()
        return gson.fromJson(json, Array<String>::class.java).toList()
    }
}
// HomeFragment.kt
private fun setupUIComponents() {
    ...
    startWorkRequests()
    
    ...
}

private fun startWorkRequests() {
    workManager.cancelAllWork()
    val workRequest = PeriodicWorkRequestBuilder<NotificationWorker>(1, TimeUnit.HOURS).build()
    workManager.enqueueUniquePeriodicWork("reservation_alarm", ExistingPeriodicWorkPolicy.KEEP, workRequest)
    workManager.getWorkInfoByIdLiveData(workRequest.id).observe(viewLifecycleOwner) { workInfo ->
        if (workInfo.state == WorkInfo.State.RUNNING) {
            val result = workInfo.progress.getBoolean("result", false)
            if (result) binding.ivHomeNotificationCountBackground.isVisible = true
        }
    }
}

 

 

3. 오늘 해결한 방식

그런데 한 스택오버플로우 글을 보고 알았다.

없으면 함수에 넣어서 주면 되는구나..!

물론 뭔가 컨텍스트를 넘기면 안 되는 것처럼 이것도 꺼림칙한 방법인 거 같지만 먼저는 구현해보는게 중요한게 아닐까라고 생각하며 그냥 시도해봤고 성공했다.

다 동일하고 달라진 거는 뷰모델에서 작업에 대한 옵저빙을 하고 뷰에서는 작업 결과에 대한 값을 옵저빙하는 것이다.

Worker 클래스는 그대로다.

테스트 좀만 더 해보고 마무리를 짓고 좀 더 좋은 방법을 찾아봐야겠다.

// HomeViewModel.kt
private var _repeatWork: MutableLiveData<Boolean> = MutableLiveData()
val repeatWork: LiveData<Boolean> get() = _repeatWork
fun startWorkRequests(workManager: WorkManager, lifecycleOwner: LifecycleOwner) {
    workManager.cancelAllWork()

    val workRequest = PeriodicWorkRequestBuilder<NotificationWorker>(1, TimeUnit.HOURS).build()
    workManager.enqueueUniquePeriodicWork("reservation_alarm", ExistingPeriodicWorkPolicy.KEEP, workRequest)

    workManager.getWorkInfoByIdLiveData(workRequest.id).observe(lifecycleOwner) {
        if (it.state == WorkInfo.State.RUNNING) {
            _repeatWork.value = it.progress.getBoolean("result", false)
        }
    }
}
// HomeFragment.kt
private fun initViewModel() {
    ...
    repeatWork.observe(viewLifecycleOwner) {
        if (it) binding.ivHomeNotificationCountBackground.isVisible = true
    }
}

private fun setupUIComponents() {
    ...
    homeViewModel.startWorkRequests(workManager, viewLifecycleOwner)
    
    ...
}

 

참고)

https://stackoverflow.com/questions/50892220/how-to-get-a-viewmodel-from-a-worker-class/53205578#53205578

반응형