24

Quindi ho riscontrato un problema con la distruzione (rimozione) di una pagina da ViewPager dopo l'orientamento dello schermo modificato. Proverò a descrivere il problema nelle seguenti righe.Distruggi l'elemento dall'adattatore ViewPager dopo aver modificato l'orientamento dello schermo

Sto usando il FragmentStatePagerAdapter per l'adattatore del ViewPager e una piccola interfaccia che descrive come funziona un cercapersone senza fine. L'idea alla base di questo è che è possibile scorrere verso destra fino a raggiungere la fine del ViewPager. Se è possibile caricare più risultati da una chiamata API, viene visualizzata una pagina di avanzamento fino all'arrivo dei risultati.

Tutto bene fino a qui, ora il problema arriva. Se durante questo processo di caricamento, ho ruotare lo schermo (questo non influirà sulla chiamata API, che è fondamentalmente un AsyncTask), quando la chiamata ritorna, l'applicazione si blocca dandomi questa eccezione:

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) 

Dopo aver scavato un po 'in il codice della libreria sembra che il campo dati mIndex del frammento sia inferiore a 0 in questo caso e ciò solleva quell'eccezione.

ecco il codice dell'adattatore cercapersone:

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; 
     } 
    } 
} 

Il onPageSelected() richiamata al di sotto, che inizia in pratica la chiamata API, se necessario:

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

Ora, vale anche la pena di dire che cosa la chiamata all'API fa dopo aver consegnato i risultati. Ecco il callback:

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

    return true; 
} 

Ok, ora perché il metodo setData() invoca il notifiyDataSetChanged(), questo chiamerà il getItemPosition() per i frammenti che sono attualmente nell'array currentFragments. Ovviamente per l'elemento di avanzamento restituisce POSITION_NONE poiché desidero eliminare questa pagina, quindi invoca sostanzialmente la richiamata destroyItem() dallo PagedSingleDataAdapter. Se non ruoto lo schermo, tutto funziona correttamente, ma come ho detto se lo sto ruotando quando viene visualizzato l'elemento di avanzamento e la chiamata API non è ancora finita, il callback destroyItem() verrà richiamato dopo il riavvio dell'attività .

Forse dovrei anche dire che sto ospitando lo ViewPager in un altro frammento e non in un'attività, quindi lo OutterFragment ospita lo ViewPager. Sto istanziare la pagerAdapter nel onActivityCreated() callback del OutterFragment e utilizzando il setRetainInstance(true) in modo che quando lo schermo ruota la pagerAdapter rimane la stessa (non dovrebbe essere cambiato, giusto?), Il codice qui:

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

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

Riassumendo ora , il PROBLEMA è:

Se si tenta di rimuovere l'elemento intermedia della ViewPager dopo che è stato un'istanza e l'attività è stata distrutta e ricreata (orientamento dello schermo cambiato) ottengo l'eccezione sopra (la pagerAdapter rimane lo stesso, quindi tutto dentro di esso a Rimane lo stesso, i riferimenti ecc ... dal momento che lo che ospita lo pagerAdapter non viene distrutto, viene solo rimosso dall'attività e quindi ricollegato). Probabilmente succede qualcosa con il gestore dei frammenti, ma davvero non so cosa.

Quello che ho già provato:

  1. Cercando di rimuovere il mio frammento progresso usando un'altra tecnica cioè sulla onTaskSuccess() richiamata stavo cercando di rimuovere il frammento dal gestore di frammento, non ha funzionato .

  2. Ho anche cercato di nascondere l'elemento di avanzamento anziché rimuoverlo completamente dal gestore frammenti. Questo ha funzionato al 50%, perché la vista non c'era più, ma avevo una pagina vuota, quindi non è proprio quello che sto cercando.

  3. Ho anche provato a (re) allegare il progressFragment al gestore dei frammenti dopo che l'orientamento dello schermo è cambiato, anche questo non ha funzionato.

  4. Ho anche provato a rimuovere e quindi aggiungere nuovamente il frammento di avanzamento al gestore dei frammenti dopo che l'attività è stata ricreata, non ha funzionato.

  5. Ho provato a chiamare il destroyItem() manualmente dal onTaskSuccess() di richiamata (che è davvero, davvero brutto) ma non ha funzionato.

Scusate ragazzi per un lungo post tale, ma stavo cercando di spiegare il problema nel miglior modo possibile in modo che voi ragazzi può capirlo.

Qualsiasi soluzione, la raccomandazione è molto apprezzata.

Grazie!

UPDATE: SOLUTION FOUND OK, quindi ci è voluto un po '. Il problema era che la chiamata destroyItem() veniva chiamata due volte sul frammento di avanzamento, una volta quando l'orientamento dello schermo cambiava e poi ancora una volta dopo che la chiamata api era finita. Ecco perché l'eccezione. La soluzione che ho trovato è la seguente: Tieni traccia se la chiamata api è finita o meno e distruggi il frammento di progresso in questo caso, codice qui sotto.

@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); 
      } 
     } 

e poi questo apiCallFinished è impostata su false nel costruttore della scheda e al vero nel onTaskSuccess() callback. E funziona davvero!

+0

Forse una domanda stupida, ma in realtà aggiungi nuovamente i tuoi frammenti al viewPager, dopo aver ruotato lo schermo? La tua attività viene distrutta dal cambiamento di orientamento. –

+0

ah, bene, sì, li sto aggiungendo ma ho dimenticato di aggiungere il codice per questo :). Grazie per avermelo segnalato, modificherò un po 'il mio post. Saluti! –

+0

Prova ad aggiungere di nuovo le tue schede con .addTab() su onCreate. Il tuo problema è solo il progresso o qualsiasi? –

risposta

4

AGGIORNAMENTO: SOLUZIONE TROVATA OK, quindi ci è voluto un po '. Il problema era che la chiamata destroyItem() veniva chiamata due volte sul frammento di avanzamento, una volta quando l'orientamento dello schermo cambiava e poi ancora una volta dopo che la chiamata api era finita. Ecco perché l'eccezione. La soluzione che ho trovato è la seguente: Tieni traccia se la chiamata api è finita o meno e distruggi il frammento di progresso in questo caso, il codice qui sotto.

@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); 
      } 
     } 

e poi questo apiCallFinished è impostata su false nel costruttore della scheda e su true nella callback onTaskSuccess(). E funziona davvero!

Problemi correlati