이번 글에서는 제가 작성한 코드를 팀원에게 설명 후 받은 질문들에 대해서 다루겠습니다.
// HomeFragment
override fun observeData() = with(binding) {
LocationData.locationStateLiveData.observe(viewLifecycleOwner) {
when (it) {
is LocationState.Success -> {
initViewPager()
// smoothScroll이 true
viewPager.currentItem = args.goToTab.ordinal
}
}
}
}
기존의 코드는 위와 같이 viewPager.currentItem = args.goToTab.ordinal로 ViewPager의 현재 아이템을 설정하였습니다. 이렇게 아이템을 설정하게되면 smoothScroll이 기본값인 true로 설정됩니다.
override fun observeData() = with(binding) {
activityViewModel.locationData.observe(viewLifecycleOwner) {
when (it) {
is MainState.Success -> {
initViewPager()
// smoothScroll이 false
viewPager.setCurrentItem(args.goToTab.ordinal, false)
}
}
}
}
ViewPager의 currentItem을 설정할 때 smoothScroll을 false로 설정하여 해결하였습니다.
viewLifecycleOwner
와 this@HomeMainFragment
은 다른 Lifecycle을 가지고 있습니다. [1]
화면이 전환 되어 Fragment의 View가 detach된 경우 Fragment의 View 상태는 DESTROYED로 전환되지만 Fragment는 CREATED가 됩니다.
즉, observe(this@HomeMainFragment)를 하게되면 화면이 전환되더라도 Observer가 제거되지 않고 여전히 LiveData를 observe하게 됩니다.
그리고 해당 프로젝트에서는 Observer를 onViewCreated에서 추가하기 때문에 observe(this@HomeMainFragment)를 사용하면 Observer가 계속 추가되는 문제가 있습니다.
ViewModel contains data-transformers that convert Model types into View types, and it contains Commands the View can use to interact with the Model. [2]
ViewModel은 View에서 사용하는 Model로 View에서 나타낼 데이터와 View가 Model과 상호작용할 수 있는 Method가 존재합니다.
// ComponentActivity
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
// Clear out the available context
mContextAwareHelper.clearAvailableContext();
// And clear the ViewModelStore
if (!isChangingConfigurations()) {
getViewModelStore().clear(); // ViewModelStore를 Clear
}
}
}
});
여기에서 AAC ViewModel은 View의 Lifecycle을 LifecycleEventObserver로 observe하며 View에 ON_DESTROY 이벤트가 발생하면 clear() 자체적으로 호출하여 해당 View의 ViewModelStore를 정리하면서 ViewModel이 clear됩니다.
단, configuration change로 인한 ON_DESTROY의 경우는 예외입니다.
Fragment의 경우 직접 Lifecycle을 observe하는 것이 아닌 FragmentStateManager에서 destroy()의 내부에서 ViewModelStore을 clear하는 method를 호출합니다.
Backing field의 생성 유무의 차이점이 존재합니다. Kotlin은 backing field를 필요할 때만 생성하게 됩니다. [3]
private val _marketData = MutableLiveData<HomeMainState>(HomeMainState.Uninitialized)
val marketData: LiveData<HomeMainState> = _marketData
위와 같은 경우 marketData의 backing field가 생성되어 _marketData의 reference를 저장하고 있습니다.
private final MutableLiveData _marketData;
@NotNull
private final LiveData marketData;
...
@NotNull
public final LiveData getMarketData() {
return this.marketData;
}
위 Kotlin의 코드를 Java로 decompile해보면 marketData의 field가 생성되어 있는 것을 확인할 수 있습니다.
private val _marketData = MutableLiveData<HomeMainState>(HomeMainState.Uninitialized)
val marketData: LiveData<HomeMainState> get() = _marketData
하지만 getter만 설정해주게되면 backing field가 생성되지 않고 getMarketData()만 생성됩니다.
private final MutableLiveData _marketData;
...
@NotNull
public final LiveData getMarketData() {
return (LiveData)this._marketData;
}
getMarketData()에서 _marketData를 LiveData로 변환하여 가져오는 것을 볼 수 있습니다.
이는 OkHttp3의 Cache를 이용하거나 Local Dababase를 이용하여 최적화를 할 수 있습니다.
val urlIterator = cache.urls()
while (urlIterator.hasNext()) {
if (urlIterator.next().startsWith("https://www.google.com/")) {
urlIterator.remove()
}
}
위와 같이 force update가 필요한 경우에 Cache를 삭제 후 network 통신을 하고 그 외의 경우에는 local에 저장된 Cache를 불러오는 방식으로 구현이 가능합니다. [4]
혹은 remote API에서 받아온 데이터를 local database에 caching하여 필요할때 불러오는 방법도 가능합니다.
[1] "Fragment lifecycle," Android Developers, last modified Oct 27, 2021, accessed Feb 22, 2022, https://developer.android.com/guide/fragments/lifecycle#states.
[2] "Introduction to Model/View/ViewModel pattern for building WPF apps," Microsoft technical documentation, last modified n.d., accessed Feb 20, 2022, https://docs.microsoft.com/en-us/archive/blogs/johngossman/introduction-to-modelviewviewmodel-pattern-for-building-wpf-apps.
[3] "Properties," Kotlin Programming Language, last modified Jan 25, 2022, accessed Feb 20, 2022, https://kotlinlang.org/docs/properties.html#backing-fields.
[4] "Caching," OkHttp, last modified n.d., accessed Feb 22, 2022, https://square.github.io/okhttp/features/caching/#pruning-the-cache.