Skip to content

Hilt와 TMap을 함께 사용할 때 발생했던 Context 이슈

이지민 edited this page Dec 15, 2022 · 1 revision

문제 상황

fragment에서 activityViewModels()로 생성하는 viewModel과 viewModels()로 생성하는 viewModel 두개를 같이 사용할 시 아래와 같은 에러 발생

@AndroidEntryPoint
class MapFragment : Fragment(), MapHandler {
    private var _binding: FragmentMapBinding? = null
    private val binding get() = _binding!!

    private val alarmViewModel: AlarmViewModel by activityViewModels()
    private val placeSearchViewModel: PlaceSearchViewModel by activityViewModels()
    private val mapViewModel: MapViewModel by viewModels()
JNI DETECTED ERROR IN APPLICATION: JNI NewLocalRef called with pending exception java.lang.ClassCastException: 
dagger.hilt.android.internal.managers.ViewComponentManager$FragmentContextWrapper cannot be cast to android.app.Activity

처음에는 viewModels()를 붙이자 에러가 발생해서 activityViewModels()와 viewModels()를 같이 쓰면 안되나? 하고 의심을 했다.

하지만 내부코드를 들여다보며 공부한 결과, 그 문제가 아닌 Hilt와 TMap을 사용하면서 발생한 Context 이슈였다.

원인

Fragment에 @AndroidEntryPoint 를 달아주면 Hilt_XxxFragment 클래스가 생성된다.

이것의 내부코드를 보면 getContext()라는 함수가 존재한다.

@Override
  public Context getContext() {
    if (super.getContext() == null && !disableGetContextFix) {
      return null;
    }
    initializeComponentContext();
    return componentContext;
  }
private void initializeComponentContext() {
    if (componentContext == null) {
      // Note: The LayoutInflater provided by this componentContext may be different from super Fragment's because we getting it from base context instead of cloning from the super Fragment's LayoutInflater.
      componentContext = FragmentComponentManager.createContextWrapper(super.getContext(), this);
      disableGetContextFix = FragmentGetContextFix.isFragmentGetContextFixDisabled(super.getContext());
    }
  }

이곳에서 initializeComponentContext() 를 호출한다.

해당 함수는 creatContextWrapper를 통해 자체적인 context를 만들고 있다.

@AndroidEntryPoint 를 붙이고 아래 세개의 context들을 Log에 찍어보았다.

val context1 = requireContext()  
val context2 = requireActivity()  
val context3 = view?.context 
context 1 : dagger.hilt.android.internal.managers.ViewComponentManager$FragmentContextWrapper@13ad174 
context 2 : com.stop.MainActivity@fb2dde0 
context 3 : dagger.hilt.android.internal.managers.ViewComponentManager$FragmentContextWrapper@ae5af93

requireActivity()만 상위 액티비티의 context를 불러오고, requireContext()view?.contextFragmentContextWrapper를 가져온다.

현재 우리의 프로젝트에서 TMapView를 사용하고 있다.

이를 생성하기 위해선 context가 필요한데, 현재 requireContext를 이용해서 넘겨주고 있다.

TMapView의 내부코드를 보면

OnUpdateMyPosition() 함수에서 TMapView.this.context를 Activity로 캐스팅하고 있다.

public void OnUpdateMyPosition(VSMMapPoint vsmMapPoint) {
                TMapView.this.tMapLayer.setRect();
                ((Activity)TMapView.this.context).runOnUiThread(new Runnable() {
                    public void run() {
                        TMapView.this.tMapLayer.setCallOutPosition();
                    }
                });
            }

MapFragment에서 context를 requireContext 로 넘겨 FragmentContextWrapper가 context로 들어갔기 때문에FragmentContextWrapper cannot be cast to android.app.Activity 라는 에러가 발생한 것이다.

해결 방법

requireContext 가 아닌 requireActivity를 context로 넘겨주면 해결된다!

Clone this wiki locally