마스터Q&A 안드로이드는 안드로이드 개발자들의 질문과 답변을 위한 지식 커뮤니티 사이트입니다. 안드로이드펍에서 운영하고 있습니다. [사용법, 운영진]

종속성을 위한 코드를 추가했는데 데이터가 왜 유지될까요?

0 추천

정확하게는 모르겠지만.. codelabs의 코드를 보고 따라서 코드를 변경하고 있는데

실행은 됩니다만 다른점이 데이터를 추가할때 리스트의 이전의 데이터가 계속 유지됩니다.

왜 그런걸까요?

뷰를 사용한 Android Room - Kotlin  |  Android 개발자  |  Android Developers

해당 페이지의 코드를 보고 비슷하게 변경하고 코드를 추가햇습니다.

변경점은 코드내에 주석으로 표시해두겠습니다.

흐름은 내비게이션 그래프로 A -> B -> C 화면을 반복하고

C화면에서 setInfo 를 List에 추가하고 A로 되돌아가서 반복합니다.

그런데 C에서 데이터를 추가할때 이전의 데이터가 지속적으로 남아있습니다.

예를들어 첫번째 추가에서 List<SetInfo>에 3개를 추가했다치면 size가 3이겠지요.

두번째로 추가할때에는 이 List<SetInfo>가 빈 리스트여야하는데 size가 3인채로 유지가 되어있습니다.

뭐가 문제일까요

--

WriteDetail

class WriteDetailFragment : Fragment() {
    private val vm : DetailViewModel by viewModels {

        // 이전 코드
//        DetailViewModelFactory(requireActivity().application, workout)

        // 변경 코드
        DetailViewModelFactory(
            (requireActivity().application as WorkoutApplication).repository,
            workout
        )
    }


    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = FragmentWriteDetailBinding.inflate(inflater, container, false)
        
        binding.apply {
         
            add.setOnClickListener {
                vm.addSet()
            }
            
            save.setOnClickListener {
                val action = WriteDetailFragmentDirections.actionWriteDetailFragmentToAddRoutine()
                findNavController().navigate(action)
            }
        }
        return binding.root
    }
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        vm.items.observe(viewLifecycleOwner) { newList ->
            adapter.submitList(newList)
        }
    }
}

 

WorkoutApplication // 새로추가함

class WorkoutApplication : Application() {
    val database by lazy { WorkoutDatabase.getDatabase(this) }
    val repository by lazy { WorkoutRepository(database.workoutDao(),"") }
}

 

DetailViewModelFactory

class DetailViewModelFactory(
    // 변경 전
//    val application: Application
    // 변경 후
    private val repository: WorkoutRepository,
    private val title: String) : ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(DetailViewModel::class.java))
            return DetailViewModel(
                // Previous
//                application,

                // Current
                repository,
                title) as T
        throw IllegalAccessException("Unknown ViewModel Class")
    }
}

 

ViewModel

class DetailViewModel(
    // 변경 전
//    application: Application,
    // 변경 후
    val repository: WorkoutRepository,
    title: String
) : ViewModel() {

    // 변경 전
//    private val workoutDao = WorkoutDatabase.getDatabase(application).workoutDao()
//    private val repository: WorkoutRepository = WorkoutRepository(workoutDao, title)


    private var _items: MutableLiveData<List<WorkoutSetInfo>> = MutableLiveData()
    val items: LiveData<List<WorkoutSetInfo>>
        get() = _items


    fun addSet() {
        viewModelScope.launch(Dispatchers.IO){
            repository.add()
            _items.postValue(repository.getList())
        }
    }
}

 

WorkoutRepo

class WorkoutRepository(private val workoutDao : WorkoutDao, title: String) {
    private val workout = Workout(title = title)
    private var setInfoList = ArrayList<WorkoutSetInfo>()
    lateinit var updatedList : List<WorkoutSetInfo>

    fun changeUnit(unit: WorkoutUnit) {
       updatedList = setInfoList.map { setInfo ->
            setInfo.copy(unit = unit)
        }
    }

    fun add() {
        setInfoList.let { list ->
            val item = WorkoutSetInfo(set = setInfoList.size + 1)
            list.add(item)
            updatedList = setInfoList.toList()
        }
    }

    fun getList() : List<WorkoutSetInfo> = updatedList
}

 

 

codeslave (3,940 포인트) 님이 2022년 11월 9일 질문

1개의 답변

0 추천
안드로이드 Application 클래스는 인스턴스가 앱애 하나만 존재하는 singleton입니다. 따라서 database와 repository 멤버 객체도 singleton입니다. 이런 이유로 DetailViewModel에 inject된 WorkoutRepository도 같은 객체이기 때문에, workout, setInfoList, updateList들도 이미 저장했던 값을 가지고 있겠죠.

Repository롤 ViewModel가 같은 scope이 되게 하거나, Repository 안의 리스트를 명시적으로 초기화하거나 리스트의 관리를 ViewModel 안에서 하시거나 하셔야 할 것 같네요.
spark (227,530 포인트) 님이 2022년 11월 9일 답변
spark님이 2022년 11월 9일 수정
아 그렇군요.. 추후에 hilt를 사용하긴할건데.. hilt를 사용해도 똑같을까요?
네. 생성할 때 hilt를 사용하느냐 아니냐의 차이가 아닙니다. Repository 안의 list가 singleton이 돼면서 생기는문제입니다. Repository의 scope(앱/액티비티/viewmodel 등) 을 변경하거나 list를 사용 전 명시적으로 초기화하거나 아니면 리스트는 viewmodel쪽에서 관리하거나 해야겠죠?
scope는 시도했다가 잘안돼서(아마 제가 못하는것이겠죠)
repository에서 리스트를 추가할때 마지막에 list.clear()하는 방식으로 빈 리스트로 만들어주는 방식으로 변경했습니다..
hilt의 scope annoatation을 굳이 말하는게 아닙니다. 알반적인 클래스의 생명주기를 말하는 겁니다. application 클래스에 인스턴스를 만드는 것과 Activity/Fragment나 ViewModel에서 하는 것 모두가다른 생명주기를 갖게 됩니다. hilt같은 라이브러리 도움없이 DI를 구현해 보는 게 DI를 이해하는데 더 도움이 됩니다. 현재 코드에서 정확하게 객체 생성 시점을 이해하고 가시면 좋을 것 같습니다.
일반 클래스의 생명주기군요. 그런데 viewmodel이 fragment에서 사용되고 있고
이 fragment는 navination 컴포넌트에 사용되고 있습니다.
 제 허접한 지식으로는 데이터 유지되는 현상이 해결되려면 viewmodel이 생명주기에서 벗어나 파괴?된후 재생성되어야 할것같습니다
viewmodel은 프래그먼트에서 사용되고 있고 그래서 프래그먼트 생명주기를 따라갈테고(정확하게는 모르겠네요) 프래그먼트는 내비게이션으로 왔다갔다해서 onstop까지만 호출되고 restart였나? 이것만 반복되는것 같은데..어떻게 생명주기를 파괴? 시킨후 재생성 할수있을까요 내비게이션에서
프레그먼트와 안드로이드 ViewModel의 lifecycle에 대해서 문서를 다시 살펴보시면 좋을 것 겉습니다. 프레그먼트의 경우 다른 프래그먼트 이동시에 프레그먼트 내의 뷰는 파괴되지만 프레그먼트 자체는 살아있습니다. 다시 돌아올 경우onCreate가 호출되면서 뷰를 재생성합니다. ViewModel의 생명주기는 fragment보다 깁니다. Fragment보다 깁니다. 이런 이유로 configuration changes에도 살아 남을 수 있습니다. 따라서 ViewModel을 프레그먼트 내에서 다시 생선하는 것은 할 수는 있지만, ViewModel을 사용하는 이유가 없어지게 됩니다. 제가 보기에 님의 경우는 화면단위 임시 입력은ViewModel 에서, db같은 곳에 영구저장하는 건 repository에서 하시는게 적합해 보입니다.
A, B, C를 nested graph로 만들고 ViewModel을 이  graph 내에서 공유하는 방법도 검토해 보세요.
...