https://github.com/LESSERAFIM-Aespa/TripMate/issues/126
가계부 컨탠츠(BudgetContentActivity) 가계부 수정시 버그 · Issue #126 · LESSERAFIM-Aespa/TripMate
“가계부 컨탠츠(BudgetContentActivity) 가계부 수정시 버그” Describe the bug 가계부 수정시 카테고리를 추가하거나 삭제할경우 해당 카테고리가 추가되거나 삭제되지 않는 이슈가 발생 카테고리 삭재
github.com
개발을 하면서 액티비티에서 ViewModel의 함수를 사용 중,
contentViewModel.updateBudgetAndCategories(budget.copy(num = budgetNum), categories)
finish()
// ContentViewModel.kt updateBudgetAndCategories
fun updateBudgetAndCategories(budget: Budget, categories: List<Category>) =
viewModelScope.launch {
Log.d(TAG, "updateBudgetAndCategories: start")
repository.updateBudgets(budget)
val beforeCategories = budgetCategories.value.orEmpty().first().categories.orEmpty()
val startTime = budget.startDate + " 00:00"
val endTime = budget.endDate + " 23:59"
val beforeProcedures = repository.getAllProceuduresWithCategoryNums(beforeCategories.map { it.num })
.map {
when{
it.time >= endTime -> {
it.copy(time = endTime)
}
it.time <= startTime -> {
it.copy(time = startTime)
}
else -> {
it
}
}
}.toTypedArray()
repository.updateProcedures(*beforeProcedures)
val checkedArr = BooleanArray(beforeCategories.size) { false }
after@ for (category in categories) {
for ((idx, beforeCategory) in beforeCategories.withIndex()) {
if (!checkedArr[idx] && category.num == beforeCategory.num) {
if (category == beforeCategory) {
checkedArr[idx] = true
continue@after
}
repository.updateCategories(category)
checkedArr[idx] = true
continue@after
}
}
repository.insertCategories(category)
}
val etcNum = beforeCategories[2].num // 기타 카테고리
checkedArr.forEachIndexed { index, b ->
if (!b){
val currentCategory = beforeCategories[index]
val procedures = repository.getPrcedouresWithCategoryNum(currentCategory.num)
.map { it.copy(categoryNum = etcNum) }.toTypedArray()
repository.updateProcedures(*procedures)
repository.deleteCategories(currentCategory)
}
}
}
이런 식으로 ViewModelScope로 돌아가는 함수를 사용하던 중 finish로 액티비티가 먼저 꺼지게 되는 상황이 생겼습니다.
해당 상황은 삭제할 것도 많고 카테고리를 많이 추가한 상황이었습니다. 이때 액티비티가 꺼지게 되니 로그를 찍어 함수가 어디까지 진행을 하는지 찾았고 매번 끝까지 실행을 못하고 액티비티가 죽게 되니 뷰모델에서의 함수도 동작을 멈추고 죽어버렸습니다.
사실 ViewModel 같은 경우 액티비티가 죽더라도 수명주기와 독립적으로 운영되기 때문에 모든 동작은 정상작동해야 됩니다. 그렇지만 이때저는 우선 이유를 찾기보다는 동작을 우선시 해야 했기 때문에 액티비티를 모드동작을 끝날 때까지 살아있도록 코드를 작성하였습니다.
lifecycleScope.launch사용
lifecycleScope.launch {
contentViewModel.updateBudgetAndCategories(budget.copy(num = budgetNum), categories)
finish()
}
액티비티에서 이런 식으로 lifecycleScope를 사용하게 되면 viewModel의 해당작업을 모두 끝내고 액티비가 종료되기 때문에 뷰모델의 함수는 모두 돌아가게 되었습니다.
참고
https://www.charlezz.com/?p=46044
생명주기에 맞춰 안전하게 코루틴 사용하기 | 찰스의 안드로이드
생명주기에 안전한 코루틴 lifecycle 컴포넌트를 사용한다면, 생명주기를 인식하는 코루틴을 만들 수 있다. LifecycleOwner로써 취급되는 AppCompatActivity(ComponentActivity) 또는 Fragment를 일반적으로 사용할
www.charlezz.com
[CoroutineScope] 2. Activity에서 lifecycleScope을 사용하는 것을 주의해야 하는 이유
lifecycleScope 사용의 한계점 lifecycleScope만을 사용해 Coroutine Job을 사용하는 것은 한계점이 있다. 바로 onDestroy 시 Job이 cancel된다는 것이다. onDestroy 시 job이 cancel 된다는 것은 백그라운드로 내려가는
kotlinworld.com
그렇지만 위에도 말했듯이 ViewModel은 액티비티와 다른 생명주기를 가지기 때문에 ViewModel이 죽는다는 것은 액티비티와 연결된 무엇인가가 있다는 말입니다. 찾아보니 ViewModel에서 사용하는 Repository가 액티비티의 Context를 사용하고 있었다는 것입니다. 그래서 액티비티가 죽는다면 Repository에서는 context를 찾을 수 없어서 ViewModel이 죽는 것처럼 보이는 것입니다. 그렇다면 우리는 Activity의 Context가아니 다른 context를 가져올 필요가 있습니다.
Application 사용
class TripMateApp : Application(){
companion object {
@Volatile
private lateinit var app: TripMateApp
@JvmStatic
fun getApp() : TripMateApp {
return app
}
}
override fun onCreate() {
app = this
super.onCreate()
}
}
class BudgetProcedureFactory(): ViewModelProvider.Factory {
private val repository by lazy {
BudgetRepositoryImpl(TripMateApp.getApp().applicationContext)
}
override fun<T : ViewModel> create(modelClass:Class<T>):T{
if(modelClass.isAssignableFrom(BudgetProcedureViewModel::class.java)) {
return BudgetProcedureViewModel(repository) as T
} else {
throw IllegalArgumentException("Not found ViewModel class.")
}
}
그래서 저는 이런 식으로 Application을 만들어 ViewModel에 사용되는 Repository에 넣어주었습니다. 이렇게 해주니 액티비티가 죽어도 ViewModel의 함수는 성공적으로 돌아갔습니다.
'안드로이드 > 안드로이드' 카테고리의 다른 글
Planfit onBoarding 페이지 클론 코딩 회고 (1) | 2024.09.13 |
---|---|
[Android/Kotlin] flatMapLatest 로 리팩토링 (2) | 2023.11.10 |
[Android/Kotlin] 알림(Notification) - 2, 알림의 속성 (1) | 2023.10.29 |
[Android/UI] BackGroundTint 값 Hex값으로 설정 (0) | 2023.10.16 |
[Android/Kotlin] Room 외래키 적용 (1) | 2023.10.14 |
https://github.com/LESSERAFIM-Aespa/TripMate/issues/126
가계부 컨탠츠(BudgetContentActivity) 가계부 수정시 버그 · Issue #126 · LESSERAFIM-Aespa/TripMate
“가계부 컨탠츠(BudgetContentActivity) 가계부 수정시 버그” Describe the bug 가계부 수정시 카테고리를 추가하거나 삭제할경우 해당 카테고리가 추가되거나 삭제되지 않는 이슈가 발생 카테고리 삭재
github.com
개발을 하면서 액티비티에서 ViewModel의 함수를 사용 중,
contentViewModel.updateBudgetAndCategories(budget.copy(num = budgetNum), categories)
finish()
// ContentViewModel.kt updateBudgetAndCategories
fun updateBudgetAndCategories(budget: Budget, categories: List<Category>) =
viewModelScope.launch {
Log.d(TAG, "updateBudgetAndCategories: start")
repository.updateBudgets(budget)
val beforeCategories = budgetCategories.value.orEmpty().first().categories.orEmpty()
val startTime = budget.startDate + " 00:00"
val endTime = budget.endDate + " 23:59"
val beforeProcedures = repository.getAllProceuduresWithCategoryNums(beforeCategories.map { it.num })
.map {
when{
it.time >= endTime -> {
it.copy(time = endTime)
}
it.time <= startTime -> {
it.copy(time = startTime)
}
else -> {
it
}
}
}.toTypedArray()
repository.updateProcedures(*beforeProcedures)
val checkedArr = BooleanArray(beforeCategories.size) { false }
after@ for (category in categories) {
for ((idx, beforeCategory) in beforeCategories.withIndex()) {
if (!checkedArr[idx] && category.num == beforeCategory.num) {
if (category == beforeCategory) {
checkedArr[idx] = true
continue@after
}
repository.updateCategories(category)
checkedArr[idx] = true
continue@after
}
}
repository.insertCategories(category)
}
val etcNum = beforeCategories[2].num // 기타 카테고리
checkedArr.forEachIndexed { index, b ->
if (!b){
val currentCategory = beforeCategories[index]
val procedures = repository.getPrcedouresWithCategoryNum(currentCategory.num)
.map { it.copy(categoryNum = etcNum) }.toTypedArray()
repository.updateProcedures(*procedures)
repository.deleteCategories(currentCategory)
}
}
}
이런 식으로 ViewModelScope로 돌아가는 함수를 사용하던 중 finish로 액티비티가 먼저 꺼지게 되는 상황이 생겼습니다.
해당 상황은 삭제할 것도 많고 카테고리를 많이 추가한 상황이었습니다. 이때 액티비티가 꺼지게 되니 로그를 찍어 함수가 어디까지 진행을 하는지 찾았고 매번 끝까지 실행을 못하고 액티비티가 죽게 되니 뷰모델에서의 함수도 동작을 멈추고 죽어버렸습니다.
사실 ViewModel 같은 경우 액티비티가 죽더라도 수명주기와 독립적으로 운영되기 때문에 모든 동작은 정상작동해야 됩니다. 그렇지만 이때저는 우선 이유를 찾기보다는 동작을 우선시 해야 했기 때문에 액티비티를 모드동작을 끝날 때까지 살아있도록 코드를 작성하였습니다.
lifecycleScope.launch사용
lifecycleScope.launch {
contentViewModel.updateBudgetAndCategories(budget.copy(num = budgetNum), categories)
finish()
}
액티비티에서 이런 식으로 lifecycleScope를 사용하게 되면 viewModel의 해당작업을 모두 끝내고 액티비가 종료되기 때문에 뷰모델의 함수는 모두 돌아가게 되었습니다.
참고
https://www.charlezz.com/?p=46044
생명주기에 맞춰 안전하게 코루틴 사용하기 | 찰스의 안드로이드
생명주기에 안전한 코루틴 lifecycle 컴포넌트를 사용한다면, 생명주기를 인식하는 코루틴을 만들 수 있다. LifecycleOwner로써 취급되는 AppCompatActivity(ComponentActivity) 또는 Fragment를 일반적으로 사용할
www.charlezz.com
[CoroutineScope] 2. Activity에서 lifecycleScope을 사용하는 것을 주의해야 하는 이유
lifecycleScope 사용의 한계점 lifecycleScope만을 사용해 Coroutine Job을 사용하는 것은 한계점이 있다. 바로 onDestroy 시 Job이 cancel된다는 것이다. onDestroy 시 job이 cancel 된다는 것은 백그라운드로 내려가는
kotlinworld.com
그렇지만 위에도 말했듯이 ViewModel은 액티비티와 다른 생명주기를 가지기 때문에 ViewModel이 죽는다는 것은 액티비티와 연결된 무엇인가가 있다는 말입니다. 찾아보니 ViewModel에서 사용하는 Repository가 액티비티의 Context를 사용하고 있었다는 것입니다. 그래서 액티비티가 죽는다면 Repository에서는 context를 찾을 수 없어서 ViewModel이 죽는 것처럼 보이는 것입니다. 그렇다면 우리는 Activity의 Context가아니 다른 context를 가져올 필요가 있습니다.
Application 사용
class TripMateApp : Application(){
companion object {
@Volatile
private lateinit var app: TripMateApp
@JvmStatic
fun getApp() : TripMateApp {
return app
}
}
override fun onCreate() {
app = this
super.onCreate()
}
}
class BudgetProcedureFactory(): ViewModelProvider.Factory {
private val repository by lazy {
BudgetRepositoryImpl(TripMateApp.getApp().applicationContext)
}
override fun<T : ViewModel> create(modelClass:Class<T>):T{
if(modelClass.isAssignableFrom(BudgetProcedureViewModel::class.java)) {
return BudgetProcedureViewModel(repository) as T
} else {
throw IllegalArgumentException("Not found ViewModel class.")
}
}
그래서 저는 이런 식으로 Application을 만들어 ViewModel에 사용되는 Repository에 넣어주었습니다. 이렇게 해주니 액티비티가 죽어도 ViewModel의 함수는 성공적으로 돌아갔습니다.
'안드로이드 > 안드로이드' 카테고리의 다른 글
Planfit onBoarding 페이지 클론 코딩 회고 (1) | 2024.09.13 |
---|---|
[Android/Kotlin] flatMapLatest 로 리팩토링 (2) | 2023.11.10 |
[Android/Kotlin] 알림(Notification) - 2, 알림의 속성 (1) | 2023.10.29 |
[Android/UI] BackGroundTint 값 Hex값으로 설정 (0) | 2023.10.16 |
[Android/Kotlin] Room 외래키 적용 (1) | 2023.10.14 |