24

Mam problem z zniszczeniem (usunięciem) jednej strony z ViewPager po zmianie orientacji ekranu. Spróbuję opisać problem w poniższych linijkach.Destroy element z adaptera ViewPager po zmianie orientacji ekranu

Używam FragmentStatePagerAdapter dla adaptera ViewPager i małego interfejsu opisującego sposób działania niedziałającego pagera widoku. Ideą tego jest to, że możesz przewijać w prawo, aż dotrzesz do końca ViewPager. Jeśli możesz załadować więcej wyników z połączenia API, strona postępu jest wyświetlana do momentu pojawienia się wyników.

Wszystko dobrze, aż tutaj, teraz pojawia się problem. Jeśli w trakcie tego procesu ładowania, obrócić ekran (to nie będzie miało wpływu na wywołanie API, które jest w zasadzie AsyncTask), kiedy wraca połączenia, awarii aplikacji daje mi ten wyjątek:

E/AndroidRuntime(13471): java.lang.IllegalStateException: Fragment ProgressFragment{42b08548} is not currently in the FragmentManager 
E/AndroidRuntime(13471): at android.support.v4.app.FragmentManagerImpl.saveFragmentInstanceState(FragmentManager.java:573) 
E/AndroidRuntime(13471): at android.support.v4.app.FragmentStatePagerAdapter.destroyItem(FragmentStatePagerAdapter.java:136) 
E/AndroidRuntime(13471): at mypackage.OutterFragment$PagedSingleDataAdapter.destroyItem(OutterFragment.java:609) 

Po wykopaniu trochę w kod biblioteki wydaje się, że pole danych tego fragmentu jest w tym przypadku mniejsze niż 0, co podnosi ten wyjątek.

Oto kod adaptera pagera:

static class PagedSingleDataAdapter extends FragmentStatePagerAdapter implements 
     IEndlessPagerAdapter { 

    private WeakReference<OutterFragment> fragment; 
    private List<DataItem> data; 
    private SparseArray<WeakReference<Fragment>> currentFragments = new SparseArray<WeakReference<Fragment>>(); 

    private ProgressFragment progressElement; 

    private boolean isLoadingData; 

    public PagedSingleDataAdapter(SherlockFragment fragment, List<DataItem> data) { 
     super(fragment.getChildFragmentManager()); 
     this.fragment = new WeakReference<OutterFragment>(
       (OutterFragment) fragment); 
     this.data = data; 
    } 

    @Override 
    public Object instantiateItem(ViewGroup container, int position) { 
     Object item = super.instantiateItem(container, position); 
     currentFragments.append(position, new WeakReference<Fragment>(
       (Fragment) item)); 
     return item; 
    } 

    @Override 
    public void destroyItem(ViewGroup container, int position, Object object) { 
     currentFragments.put(position, null); 
     super.destroyItem(container, position, object); 
    } 

    @Override 
    public Fragment getItem(int position) { 
     if (isPositionOfProgressElement(position)) { 
      return getProgessElement(); 
     } 

     WeakReference<Fragment> fragmentRef = currentFragments.get(position); 
     if (fragmentRef == null) { 
      return PageFragment.newInstance(args); // here I'm putting some info 
               // in the args, just deleted 
               // them now, not important 
     } 

     return fragmentRef.get(); 
    } 

    @Override 
    public int getCount() { 
     int size = data.size(); 
     return isLoadingData ? ++size : size; 
    } 

    @Override 
    public int getItemPosition(Object item) { 
     if (item.equals(progressElement) && !isLoadingData) { 
      return PagerAdapter.POSITION_NONE; 
     } 
     return PagerAdapter.POSITION_UNCHANGED; 
    } 

    public void setData(List<DataItem> data) { 
     this.data = data; 
     notifyDataSetChanged(); 
    } 

    @Override 
    public boolean isPositionOfProgressElement(int position) { 
     return isLoadingData && position == data.size(); 
    } 

    @Override 
    public void setLoadingData(boolean isLoadingData) { 
     this.isLoadingData = isLoadingData; 
    } 

    @Override 
    public boolean isLoadingData() { 
     return isLoadingData; 
    } 

    @Override 
    public Fragment getProgessElement() { 
     if (progressElement == null) { 
      progressElement = new ProgressFragment(); 
     } 
     return progressElement; 
    } 

    public static class ProgressFragment extends SherlockFragment { 

     public ProgressFragment() { 
     } 

     @Override 
     public View onCreateView(LayoutInflater inflater, ViewGroup container, 
       Bundle savedInstanceState) { 

      TextView progressView = new TextView(container.getContext()); 
      progressView.setGravity(Gravity.CENTER_HORIZONTAL 
        | Gravity.CENTER_VERTICAL); 
      progressView.setText(R.string.loading_more_data); 
      LayoutParams params = new LayoutParams(LayoutParams.FILL_PARENT, 
        LayoutParams.FILL_PARENT); 
      progressView.setLayoutParams(params); 

      return progressView; 
     } 
    } 
} 

onPageSelected() zwrotna poniżej, które zasadniczo rozpoczyna wywołanie API czy potrzebne:

@Override 
    public void onPageSelected(int currentPosition) { 
     updatePagerIndicator(currentPosition); 
     activity.invalidateOptionsMenu(); 
     if (requestNextApiPage(currentPosition)) { 
      pagerAdapter.setLoadingData(true); 
      requestNextPageController.requestNextPageOfData(this); 
     } 

Teraz warto też powiedzieć, co wywołanie API wykonuje się po dostarczeniu wyników. Oto zwrotna:

@Override 
public boolean onTaskSuccess(Context arg0, List<DataItem> result) { 
    data = result; 
    pagerAdapter.setLoadingData(false); 
    pagerAdapter.setData(result); 
    activity.invalidateOptionsMenu(); 

    return true; 
} 

Ok, teraz, ponieważ metoda setData() wywołuje notifiyDataSetChanged(), to wywoła getItemPosition() do fragmentów, które są aktualnie w tablicy currentFragments. Oczywiście dla elementu postępu zwraca on POSITION_NONE, ponieważ chcę usunąć tę stronę, więc to zasadniczo wywołuje wywołanie zwrotne od destroyItem() z . Jeśli nie obracam ekranu, wszystko działa poprawnie, ale jak już powiedziałem, jeśli obracam go, gdy wyświetlany jest element postępu, a wywołanie API jeszcze się nie zakończyło, wywołanie zwrotne destroyItem() zostanie wywołane po ponownym uruchomieniu działania .

Może powinienem również powiedzieć, że hostuję ViewPager w innym Fragmentie, a nie w działaniu, więc OutterFragment jest hostem ViewPager. Ja uruchamianiu tego pagerAdapter w onActivityCreated() zwrotnego z OutterFragment i używając setRetainInstance(true) tak, że gdy ekran obraca pagerAdapter pozostaje taka sama (nie powinny być zmieniane, prawda?), Kod tutaj:

if (pagerAdapter == null) { 
    pagerAdapter = new PagedSingleDataAdapter(this, data); 
} 
pager.setAdapter(pagerAdapter); 

if (savedInstanceState == null) { 
    pager.setOnPageChangeListener(this); 
    pager.setCurrentItem(currentPosition); 
} 

Podsumowując teraz The PROBLEM jest:

gdy próbuję usunąć element postępu od ViewPager po jego wystąpienia oraz działalność została zniszczona i odtworzone (orientacja ekranu zmienione) otrzymuję powyższy wyjątek (the pagerAdapter pozostaje taka sama, więc wszystko w środku Pozostają takie same, odniesienia itp. ... ponieważ OutterFragment, który jest hostem pagerAdapter, nie jest zniszczony, jest oderwany od aktywności, a następnie ponownie dołączony). Prawdopodobnie dzieje się coś z menedżerem fragmentów, ale naprawdę nie wiem co.

Co ja już próbowałem:

  1. Próbując usunąć fragment mojego postępu przy użyciu innej techniki czyli na onTaskSuccess() zwrotnego starałem się usunąć fragment z menedżera fragmentu, nie działa .

  2. Próbowałem również ukryć element postępu, zamiast go całkowicie usunąć z menedżera fragmentów. To działało 50%, ponieważ widoku już nie było, ale miałem pustą stronę, więc nie jest to tak naprawdę to, czego szukam.

  3. Próbowałem również (ponownie) dołączyć progressFragment do menedżera fragmentów po zmianie orientacji ekranu, to też nie zadziałało.

  4. Próbowałem również usunąć, a następnie dodać ponownie fragment postępu do menedżera fragmentów po odtworzeniu działania, nie działał.

  5. Próbowałem wywołać destroyItem() ręcznie z wywołania zwrotnego onTaskSuccess() (co jest naprawdę, naprawdę brzydkie), ale nie działało.

Przykro mi z powodu tak długiego postu, ale starałem się wyjaśnić problem najlepiej jak potrafię, abyście mogli go zrozumieć.

Każde rozwiązanie, zalecenie jest bardzo doceniane.

Dzięki!

AKTUALIZACJA: ZNALEZIONO ROZWIĄZANIE OK, więc zajęło to trochę czasu. Problem polegał na tym, że wywołanie zwrotne destroyItem() zostało wywołane dwukrotnie w fragmencie postępu, jeden raz, gdy zmieniła się orientacja ekranu, a następnie ponownie po zakończeniu wywołania api. Właśnie dlatego wyjątek. Rozwiązanie, które znalazłem, jest następujące: Śledzenie, czy api api się zakończyło, czy nie, i zniszcz fragment postępu właśnie w tym przypadku, kod poniżej.

@Override 
     public void destroyItem(ViewGroup container, int position, Object object) { 
      if (object.equals(progressElement) && apiCallFinished == true) { 
       apiCallFinished = false; 
       currentFragments.put(position, currentFragments.get(position + 1)); 
       super.destroyItem(container, position, object); 
      } else if (!(object.equals(progressElement))) { 
       currentFragments.put(position, null); 
       super.destroyItem(container, position, object); 
      } 
     } 

a następnie ten apiCallFinished jest ustawiona na false w konstruktora adaptera i prawdziwe w onTaskSuccess() zwrotnego. I to naprawdę działa!

+0

Może to głupie pytanie, ale czy po obróceniu ekranu ponownie dodajesz swoje fragmenty do widoku ViewPager? Twoja aktywność zostaje zniszczona podczas zmiany orientacji. –

+0

ah, dobry, tak, dodaję je, ale zapomniałem dodać kod do niego :). Dzięki za wskazanie tego, trochę edytuję swój post. Twoje zdrowie! –

+0

Spróbuj ponownie dodać karty z .addTab() w onCreate. Czy Twój problem dotyczy tylko funkcji progressFragment? –

Odpowiedz

4

AKTUALIZACJA: ZNALEZIONO ROZWIĄZANIE OK, więc zajęło to trochę czasu. Problem polegał na tym, że wywołanie zwrotne destroyItem() zostało wywołane dwukrotnie w fragmencie postępu, jeden raz, gdy zmieniła się orientacja ekranu, a następnie ponownie po zakończeniu wywołania api. Właśnie dlatego wyjątek. Rozwiązanie, które znalazłem, jest następujące: Śledź śledzenie, jeśli połączenie API zostało zakończone lub nie, i zniszcz fragment postępu właśnie w tym przypadku, kod poniżej.

@Override 
     public void destroyItem(ViewGroup container, int position, Object object) { 
      if (object.equals(progressElement) && apiCallFinished == true) { 
       apiCallFinished = false; 
       currentFragments.put(position, currentFragments.get(position + 1)); 
       super.destroyItem(container, position, object); 
      } else if (!(object.equals(progressElement))) { 
       currentFragments.put(position, null); 
       super.destroyItem(container, position, object); 
      } 
     } 

a następnie ten apiCallFinished jest ustawiona na false w konstruktora adaptera i prawdziwe w onTaskSuccess() zwrotnego. I to naprawdę działa!

Powiązane problemy