현재
FragmentStatePagerAdapter 를 기반으로 페이지마다 다른 화면들을 보여주도록 구현을 하고 있는데
간혹 내부 메서드 instantiateItem() 에서 NullException이 떨어지는데 원인을 딱히 모르겠네요.
아래 메서드들을 오버라이딩해서 사용하고 있고요.
Fragment getItem(int position),
void destroyItem(View container, int position, Object object),
int
getCount(),
void restoreState(Parcelable arg0, ClassLoader arg1),
Parcelable saveState()
Fragement들을 유지해주기 위해서 destroyItem메서드 내부에는 아무것도 구현하지 않았습니다.
매번 에러가 발생하는것은 아니고 가끔 발생합니다.
에러내용을 보면 제가 작성한 코드 기반을 타지않고 내부에서 바로 발생하는데
혹시 원인을 아시는분 계신가요?
아래는 에러메시지 입니다. E/AndroidRuntime(30862): java.lang.NullPointerException E/AndroidRuntime(30862): at android.support.v4.app.FragmentStatePagerAdapter.instantiateItem(FragmentStatePagerAdapter.java:79) E/AndroidRuntime(30862): at android.support.v4.view.ViewPager.addNewItem(ViewPager.java:548) E/AndroidRuntime(30862): at android.support.v4.view.ViewPager.populate(ViewPager.java:671) E/AndroidRuntime(30862): at android.support.v4.view.ViewPager.onMeasure(ViewPager.java:851)
각 페이지들을 Fragement들로 관리하기 위해 쓰고 있고 View들은
각 Fragment 내부에서 View를 생성하여 사용하고 있습니다.
저같은 경우는 View로 구성하는것보다 Fragment로 구성하기에 더 적합한 어플이라고 생각하여 이렇게 쓰고 있고요..
말씀하신대로 instantiateItem() 에서 View를 구성해서 return해주는 예제들도 있는데
저는 Fragment로 사용중이므로 instantiateItem()는 따로 오버라이딩하지 않았습니다.
직접 만든 메서드로 동적으로 Fragment들을 리스트로 구성하여 Fragment getItem(int position)에서 리스트에 담겨있는
Fragment 들을 뽑아서 사용하고 있는데 평상시 대부분은 문제가 없는데 어쩌다 가끔 instantiateItem()에서 Null이 발생하게
되네요.
"Fragement들을 유지해주기 위해서 (오버라이드한) destroyItem메서드 내부에는 아무것도 구현하지 않았습니다.
...
직접 만든 메서드로 동적으로 Fragment들을 리스트로 구성하여 Fragment getItem(int position)에서 리스트에 담겨있는 Fragment 들을 뽑아서 사용하고 있는데..."
FragmentStatePagerAdapter 나 FragmentPagerAdapter 를 사용할 때, Fragment의 생성은 오버라이드한 getItem(position)에서 하지 않으면 여러가지로 꼬이는 상황이 생긴다.
getItem()은 '처음' 생성이 필요한 시점에만 호출되는데, 여기서 '처음'의 의미는 말그대로 진짜 처음이다.
예를 들어, Activity가 뜬 다음 디바이스가 회전하거나 하면 onDestroy()-onCreate() 를 다시 탄다. 이때는 Activity인스턴스가 새로 생성되지만 실제로 처음은 아닌 셈이다.
어떤 이유건 한번 만들어진 인스턴스는 FragmentManager나 PagerAdapter가 알아서 저장했다가 다시 복원시키기 때문에 이 것과 정확히 일치시키려면 getItem()만이 유일하게 직접Fragment를 생성하는 곳이다.
그럼 질문의 글에서 두 가지 오류를 발견할 수 있다. 하나는 직접 만든 리스트에 Fragment가 이미 생성되어 있다는 것이고, 또 하나는 destroyItem()을 오버라이드하면서 super를 호출하지 않는다는 것이다.
FragmentStatePagerAdapter는 destroyItem()에서 FragmentManager에 붙어있는 Fragment를 remove()하면서 자신의 관리목록에서도 그 Fragment를 제거하고 단지 State만 남겨놓는다.
처음 getItem(0)으로 생성된 Fragment는 FragmentManager에 add()되고, FragmentStatePagerAdapter의 관리목록에도 들어간다.
이것이 다른 페이지로 전환되면서 destroyItem()이 호출되는데, 원래대로라면 정말 destroy(될 상태가) 되면서 State만 남겨놓아야 하는데, 이 과정이 생략되어 버렸다.
이 상태로 Activity가 onDestroy()-onCreate() 를 타면 재미난 상황이 벌어진다.
동일한 position의 Fragment 객체가 세 군데 생겨난다.
1. 직접 만든 리스트(destroy 되기 전 것이든, 새로 만든 것이든)
2. FragmentManager(remove()되지 않았으니, FM이 알아서 다시 만들었다)
3. FragmentStatePagerAdapter(관리목록에 여전히 남아 있으므로 restore하면서 다시 만든다)
이제 어떤 인스턴스가 정말 우리가 원하는 인스턴스 일까?
이런 문제를 방지하려면 getItem()에서만 Fragment를 생성해야 하고, destroyItem() 혹은 instantiateItem() 같은 메소드를 오버라이드 했다면 super를 호출해주어야 한다. (물론 이 모든 사정을 다 알고도 호출하지 않을 이유가 있다면 그렇게 해야..)
FragmentStatePagerAdapter <- 요걸 쓰시는 이유가 있나요??
전 일반 PagerAdaper밖에 안써봤지만 답변드립니다..
Pager에 들어가는 뷰는 어디서 생성하시나요?
말씀하신거중에 instantiateItem() <- 요거
객체 생성자아닌가요?? 여기서 뷰생성해서 Pager에 넣어주는걸로 알고 있는데요.
리턴타입이 Object이고, 인자값이 instantiateItem(View view, int position) 요런 형태아닌가요??