Fragmento MyFragment no adjunto a la actividad

Resuelto nhaarman asked hace 54 años • 14 respuestas

Creé una pequeña aplicación de prueba que representa mi problema. Estoy usando ActionBarSherlock para implementar pestañas con Fragmentos (Sherlock).

Mi código: TestActivity.java

public class TestActivity extends SherlockFragmentActivity {
    private ActionBar actionBar;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setupTabs(savedInstanceState);
    }

    private void setupTabs(Bundle savedInstanceState) {
        actionBar = getSupportActionBar();
        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

        addTab1();
        addTab2();
    }

    private void addTab1() {
        Tab tab1 = actionBar.newTab();
        tab1.setTag("1");
        String tabText = "1";
        tab1.setText(tabText);
        tab1.setTabListener(new TabListener<MyFragment>(TestActivity.this, "1", MyFragment.class));

        actionBar.addTab(tab1);
    }

    private void addTab2() {
        Tab tab1 = actionBar.newTab();
        tab1.setTag("2");
        String tabText = "2";
        tab1.setText(tabText);
        tab1.setTabListener(new TabListener<MyFragment>(TestActivity.this, "2", MyFragment.class));

        actionBar.addTab(tab1);
    }
}

TabListener.java

public class TabListener<T extends SherlockFragment> implements com.actionbarsherlock.app.ActionBar.TabListener {
    private final SherlockFragmentActivity mActivity;
    private final String mTag;
    private final Class<T> mClass;

    public TabListener(SherlockFragmentActivity activity, String tag, Class<T> clz) {
        mActivity = activity;
        mTag = tag;
        mClass = clz;
    }

    /* The following are each of the ActionBar.TabListener callbacks */

    public void onTabSelected(Tab tab, FragmentTransaction ft) {
        SherlockFragment preInitializedFragment = (SherlockFragment) mActivity.getSupportFragmentManager().findFragmentByTag(mTag);

        // Check if the fragment is already initialized
        if (preInitializedFragment == null) {
            // If not, instantiate and add it to the activity
            SherlockFragment mFragment = (SherlockFragment) SherlockFragment.instantiate(mActivity, mClass.getName());
            ft.add(android.R.id.content, mFragment, mTag);
        } else {
            ft.attach(preInitializedFragment);
        }
    }

    public void onTabUnselected(Tab tab, FragmentTransaction ft) {
        SherlockFragment preInitializedFragment = (SherlockFragment) mActivity.getSupportFragmentManager().findFragmentByTag(mTag);

        if (preInitializedFragment != null) {
            // Detach the fragment, because another one is being attached
            ft.detach(preInitializedFragment);
        }
    }

    public void onTabReselected(Tab tab, FragmentTransaction ft) {
        // User selected the already selected tab. Usually do nothing.
    }
}

MyFragment.java

public class MyFragment extends SherlockFragment {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        new AsyncTask<Void, Void, Void>() {

            @Override
            protected Void doInBackground(Void... params) {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException ex) {
                }
                return null;
            }

            @Override
            protected void onPostExecute(Void result){
                getResources().getString(R.string.app_name);
            }

        }.execute();
    }
}

Agregué la Thread.sleepparte para simular la descarga de datos. El código en onPostExecutees para simular el uso de Fragment.

Cuando giro la pantalla muy rápido entre horizontal y vertical, aparece una excepción en el onPostExecutecódigo:

java.lang.IllegalStateException: Fragmento MyFragment{410f6060} no adjunto a la actividad

Creo que se debe a que MyFragmentmientras tanto se creó una nueva y se adjuntó a la Actividad antes de AsyncTaskfinalizar. El código in onPostExecutellama a un archivo MyFragment.

¿Pero cómo puedo solucionar esto?

nhaarman avatar Jan 01 '70 08:01 nhaarman
Aceptado

He encontrado la respuesta muy simple: isAdded():

Devuelve truesi el fragmento está actualmente agregado a su actividad.

@Override
protected void onPostExecute(Void result){
    if(isAdded()){
        getResources().getString(R.string.app_name);
    }
}

Para evitar onPostExecuteser llamado cuando el Fragmentno está conectado al Activityes cancelar el AsyncTaskal pausar o detener el Fragment. Entonces ya isAdded()no sería necesario. Sin embargo, es aconsejable mantener este control.

nhaarman avatar Jun 07 '2012 23:06 nhaarman

El problema es que estás intentando acceder a recursos (en este caso, cadenas) usando getResources().getString(), que intentará obtener los recursos de la Actividad. Vea este código fuente de la clase Fragment:

 /**
  * Return <code>getActivity().getResources()</code>.
  */
 final public Resources getResources() {
     if (mHost == null) {
         throw new IllegalStateException("Fragment " + this + " not attached to Activity");
     }
     return mHost.getContext().getResources();
 }

mHostes el objeto que contiene su actividad.

Debido a que es posible que la actividad no esté adjunta, su llamada a getResources() generará una excepción.

En mi humilde opinión, la solución aceptada no es el camino a seguir, ya que simplemente estás ocultando el problema. La forma correcta es simplemente obtener los recursos de otro lugar cuya existencia siempre esté garantizada, como el contexto de la aplicación:

youApplicationObject.getResources().getString(...)

Me he enfrentado a dos escenarios diferentes aquí:

1) Cuando quiero que la tarea asincrónica finalice de todos modos: imagine que mi onPostExecute almacena los datos recibidos y luego llama a un oyente para actualizar las vistas, por lo que, para ser más eficiente, quiero que la tarea finalice de todos modos para tener los datos listos cuando llegue el usuario. atrás. En este caso suelo hacer esto:

@Override
protected void onPostExecute(void result) {
    // do whatever you do to save data
    if (this.getView() != null) {
        // update views
    }
}

2) Cuando quiero que la tarea asincrónica finalice solo cuando se puedan actualizar las vistas: en el caso que estás proponiendo aquí, la tarea solo actualiza las vistas, no necesita almacenamiento de datos, por lo que no tiene idea de si la tarea finalizará si las vistas son ya no se muestra. Hago esto:

@Override
protected void onStop() {
    // notice here that I keep a reference to the task being executed as a class member:
    if (this.myTask != null && this.myTask.getStatus() == Status.RUNNING) this.myTask.cancel(true);
    super.onStop();
}

No he encontrado ningún problema con esto, aunque también uso una forma (quizás) más compleja que incluye iniciar tareas desde la actividad en lugar de los fragmentos.

¡Ojalá esto ayude a alguien! :)

luixal avatar Apr 24 '2013 10:04 luixal