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)
...
}
참고)
반응형