Androide. Fragment getActivity () a veces devuelve null


En los informes de error de la consola del desarrollador a veces veo informes con problema de NPE. No entiendo lo que está mal con mi código. En emulador y mi aplicación de dispositivo funciona bien sin forcecloses, sin embargo, algunos usuarios obtienen NullPointerException en la clase fragment cuando se llama al método getActivity ().

Actividad

pulic class MyActivity extends FragmentActivity{

    private ViewPager pager; 
    private TitlePageIndicator indicator;
    private TabsAdapter adapter;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        pager = (ViewPager) findViewById(R.id.pager);
        indicator = (TitlePageIndicator) findViewById(R.id.indicator);
        adapter = new TabsAdapter(getSupportFragmentManager(), false);

        adapter.addFragment(new FirstFragment());
        adapter.addFragment(new SecondFragment());
        indicator.notifyDataSetChanged();
        adapter.notifyDataSetChanged();

        // push first task
        FirstTask firstTask = new FirstTask(MyActivity.this);
        // set first fragment as listener
        firstTask.setTaskListener((TaskListener) adapter.getItem(0));
        firstTask.execute();
    }

    indicator.setOnPageChangeListener(new ViewPager.OnPageChangeListener()  {
        @Override
        public void onPageSelected(int position) {
            Fragment currentFragment = adapter.getItem(position);
            ((Taskable) currentFragment).executeTask();
        }

        @Override
        public void onPageScrolled(int i, float v, int i1) {}

        @Override
        public void onPageScrollStateChanged(int i) {}
    });
}

Clase AsyncTask

public class FirstTask extends AsyncTask{

    private TaskListener taskListener;

    ...

    @Override
    protected void onPostExecute(T result) {
        ... 
        taskListener.onTaskComplete(result);
    }   
}

Clase de fragmentos

public class FirstFragment extends Fragment immplements Taskable, TaskListener{

    public FirstFragment() {
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.first_view, container, false);
    }

    @Override
    public void executeTask() {
        FirstTask firstTask = new FirstTask(MyActivity.this);
        firstTask.setTaskListener(this);
        firstTask.execute();
    }

    @Override
    public void onTaskComplete(T result) {
        // NPE is here 
        Resources res = getActivity().getResources();
        ...
    }
}

Tal vez este error ocurre cuando las aplicaciones se reanudan desde el fondo. En este caso, cómo ¿debería manejar esta situación adecuadamente?

Author: scohe001, 2012-07-24

7 answers

Parece que encontré una solución a mi problema. Se dan muy buenas explicaciones aquí y aquí . Aquí está mi ejemplo:

pulic class MyActivity extends FragmentActivity{

private ViewPager pager; 
private TitlePageIndicator indicator;
private TabsAdapter adapter;
private Bundle savedInstanceState;

 @Override
public void onCreate(Bundle savedInstanceState) {

    .... 
    this.savedInstanceState = savedInstanceState;
    pager = (ViewPager) findViewById(R.id.pager);;
    indicator = (TitlePageIndicator) findViewById(R.id.indicator);
    adapter = new TabsAdapter(getSupportFragmentManager(), false);

    if (savedInstanceState == null){    
        adapter.addFragment(new FirstFragment());
        adapter.addFragment(new SecondFragment());
    }else{
        Integer  count  = savedInstanceState.getInt("tabsCount");
        String[] titles = savedInstanceState.getStringArray("titles");
        for (int i = 0; i < count; i++){
            adapter.addFragment(getFragment(i), titles[i]);
        }
    }


    indicator.notifyDataSetChanged();
    adapter.notifyDataSetChanged();

    // push first task
    FirstTask firstTask = new FirstTask(MyActivity.this);
    // set first fragment as listener
    firstTask.setTaskListener((TaskListener) getFragment(0));
    firstTask.execute();

}

private Fragment getFragment(int position){
     return savedInstanceState == null ? adapter.getItem(position) : getSupportFragmentManager().findFragmentByTag(getFragmentTag(position));
}

private String getFragmentTag(int position) {
    return "android:switcher:" + R.id.pager + ":" + position;
}

 @Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt("tabsCount",      adapter.getCount());
    outState.putStringArray("titles", adapter.getTitles().toArray(new String[0]));
}

 indicator.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
        @Override
        public void onPageSelected(int position) {
            Fragment currentFragment = adapter.getItem(position);
            ((Taskable) currentFragment).executeTask();
        }

        @Override
        public void onPageScrolled(int i, float v, int i1) {}

        @Override
        public void onPageScrollStateChanged(int i) {}
 });

La idea principal de este código es que, mientras ejecuta su aplicación normalmente, cree nuevos fragmentos y los pase al adaptador. Cuando está reanudando su application fragment manager ya tiene la instancia de este fragment y necesita obtenerla de fragment manager y pasarla al adaptador.

ACTUALIZACIÓN

También es una buena práctica cuando se usan fragmentos para comprobar isAdded antes de llamar a getActivity (). Esto ayuda a evitar una excepción de puntero nulo cuando el fragmento se separa de la actividad. Por ejemplo, una actividad podría contener un fragmento que empuja una tarea asíncrona. Cuando la tarea ha finalizado, se llama al oyente onTaskComplete.

@Override
public void onTaskComplete(List<Feed> result) {

    progress.setVisibility(View.GONE);
    progress.setIndeterminate(false);
    list.setVisibility(View.VISIBLE);

    if (isAdded()) {

        adapter = new FeedAdapter(getActivity(), R.layout.feed_item, result);
        list.setAdapter(adapter);
        adapter.notifyDataSetChanged();
    }

}

Si abrimos el fragmento, empujamos una tarea y luego presionamos rápidamente atrás para volver a una anterior activity, cuando la tarea esté terminada, intentará acceder a la actividad en onPostExecute() llamando al método getActivity (). Si la actividad ya está separada y esta comprobación no está allí:

if (isAdded()) 

Entonces la aplicación se bloquea.

 110
Author: Georgy Gobozov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:54:47

Ok, sé que esta pregunta está realmente resuelta, pero decidí compartir mi solución para esto. He creado la clase padre abstracta para mi Fragment:

public abstract class ABaseFragment extends Fragment{

    protected IActivityEnabledListener aeListener;

    protected interface IActivityEnabledListener{
        void onActivityEnabled(FragmentActivity activity);
    }

    protected void getAvailableActivity(IActivityEnabledListener listener){
        if (getActivity() == null){
            aeListener = listener;

        } else {
            listener.onActivityEnabled(getActivity());
        }
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);

        if (aeListener != null){
            aeListener.onActivityEnabled((FragmentActivity) activity);
            aeListener = null;
        }
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);

        if (aeListener != null){
            aeListener.onActivityEnabled((FragmentActivity) context);
            aeListener = null;
        }
    }
}

Como se puede ver, he añadido un oyente por lo que, cada vez que voy a necesitar para obtener Fragments Activity en lugar del estándar getActivity(), tendré que llamar a

 getAvailableActivity(new IActivityEnabledListener() {
        @Override
        public void onActivityEnabled(FragmentActivity activity) {
            // Do manipulations with your activity
        }
    });
 17
Author: Paul Freez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-24 09:16:40

Lo mejor para deshacerse de esto es mantener la referencia de actividad cuando se llama onAttach y usar la referencia de actividad donde sea necesario, por ejemplo,

@Override
public void onAttach(Context context) {
    super.onAttach(context);
    mContext = context;
}

@Override
public void onDetach() {
    super.onDetach();
    mContext = null;
}

Editado, ya que onAttach(Activity) está depreciado y ahora onAttach(Context) se está utilizando

 14
Author: Pawan Maheshwari,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-30 18:09:00

No llame a métodos dentro del fragmento que requieren getActivity() hasta que se inicie en la actividad principal.

private MyFragment myFragment;


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

    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    myFragment = new MyFragment();

    ft.add(android.R.id.content, youtubeListFragment).commit();

    //Other init calls
    //...
}


@Override
public void onStart()
{
    super.onStart();

    //Call your Fragment functions that uses getActivity()
    myFragment.onPageSelected();
}
 9
Author: bvmobileapps,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-14 16:12:20
@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    // run the code making use of getActivity() from here
}
 3
Author: dheeran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-14 16:04:25

He estado luchando contra este tipo de problema durante un tiempo, y creo que he encontrado una solución confiable.

Es bastante difícil saber con certeza que this.getActivity() no va a devolver null para un Fragment, especialmente si se trata de cualquier tipo de comportamiento de red que le da a su código tiempo suficiente para retirar referencias Activity.

En la solución a continuación, declaro una pequeña clase de administración llamada ActivityBuffer. Esencialmente, este class trata de mantener un referencia confiable a un Activity propietario, y prometiendo ejecutar Runnables dentro de un contexto Activity válido siempre que haya una referencia válida disponible. Los Runnable s están programados para la ejecución en el subproceso de la interfaz de usuario inmediatamente si el Context está disponible, de lo contrario la ejecución se pospone hasta que Context esté listo.

/** A class which maintains a list of transactions to occur when Context becomes available. */
public final class ActivityBuffer {

    /** A class which defines operations to execute once there's an available Context. */
    public interface IRunnable {
        /** Executes when there's an available Context. Ideally, will it operate immediately. */
        void run(final Activity pActivity);
    }

    /* Member Variables. */
    private       Activity        mActivity;
    private final List<IRunnable> mRunnables;

    /** Constructor. */
    public ActivityBuffer() {
        // Initialize Member Variables.
        this.mActivity  = null;
        this.mRunnables = new ArrayList<IRunnable>();
    }

    /** Executes the Runnable if there's an available Context. Otherwise, defers execution until it becomes available. */
    public final void safely(final IRunnable pRunnable) {
        // Synchronize along the current instance.
        synchronized(this) {
            // Do we have a context available?
            if(this.isContextAvailable()) {
                // Fetch the Activity.
                final Activity lActivity = this.getActivity();
                // Execute the Runnable along the Activity.
                lActivity.runOnUiThread(new Runnable() { @Override public final void run() { pRunnable.run(lActivity); } });
            }
            else {
                // Buffer the Runnable so that it's ready to receive a valid reference.
                this.getRunnables().add(pRunnable);
            }
        }
    }

    /** Called to inform the ActivityBuffer that there's an available Activity reference. */
    public final void onContextGained(final Activity pActivity) {
        // Synchronize along ourself.
        synchronized(this) {
            // Update the Activity reference.
            this.setActivity(pActivity);
            // Are there any Runnables awaiting execution?
            if(!this.getRunnables().isEmpty()) {
                // Iterate the Runnables.
                for(final IRunnable lRunnable : this.getRunnables()) {
                    // Execute the Runnable on the UI Thread.
                    pActivity.runOnUiThread(new Runnable() { @Override public final void run() {
                        // Execute the Runnable.
                        lRunnable.run(pActivity);
                    } });
                }
                // Empty the Runnables.
                this.getRunnables().clear();
            }
        }
    }

    /** Called to inform the ActivityBuffer that the Context has been lost. */
    public final void onContextLost() {
        // Synchronize along ourself.
        synchronized(this) {
            // Remove the Context reference.
            this.setActivity(null);
        }
    }

    /** Defines whether there's a safe Context available for the ActivityBuffer. */
    public final boolean isContextAvailable() {
        // Synchronize upon ourself.
        synchronized(this) {
            // Return the state of the Activity reference.
            return (this.getActivity() != null);
        }
    }

    /* Getters and Setters. */
    private final void setActivity(final Activity pActivity) {
        this.mActivity = pActivity;
    }

    private final Activity getActivity() {
        return this.mActivity;
    }

    private final List<IRunnable> getRunnables() {
        return this.mRunnables;
    }

}

En términos de su implementación, debemos tener cuidado de aplicar los métodos del ciclo de vida para que coincidan con el comportamiento descrito anteriormente por Pawan M :

public class BaseFragment extends Fragment {

    /* Member Variables. */
    private ActivityBuffer mActivityBuffer;

    public BaseFragment() {
        // Implement the Parent.
        super();
        // Allocate the ActivityBuffer.
        this.mActivityBuffer = new ActivityBuffer();
    }

    @Override
    public final void onAttach(final Context pContext) {
        // Handle as usual.
        super.onAttach(pContext);
        // Is the Context an Activity?
        if(pContext instanceof Activity) {
            // Cast Accordingly.
            final Activity lActivity = (Activity)pContext;
            // Inform the ActivityBuffer.
            this.getActivityBuffer().onContextGained(lActivity);
        }
    }

    @Deprecated @Override
    public final void onAttach(final Activity pActivity) {
        // Handle as usual.
        super.onAttach(pActivity);
        // Inform the ActivityBuffer.
        this.getActivityBuffer().onContextGained(pActivity);
    }

    @Override
    public final void onDetach() {
        // Handle as usual.
        super.onDetach();
        // Inform the ActivityBuffer.
        this.getActivityBuffer().onContextLost();
    }

    /* Getters. */
    public final ActivityBuffer getActivityBuffer() {
        return this.mActivityBuffer;
    }

}

Finalmente, en cualquier área dentro de su Fragment que se extiende BaseFragment que no es confiable acerca de una llamada a getActivity(), simplemente haga una llamada a this.getActivityBuffer().safely(...) y declare un ActivityBuffer.IRunnable para la tarea!

El contenido de su void run(final Activity pActivity) está garantizado para ejecutarse a lo largo del subproceso de la interfaz de usuario.

 3
Author: Cawfree,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-19 21:37:31

Sé que esta es una vieja pregunta, pero creo que debo darle mi respuesta porque mi problema no fue resuelto por otros.

En primer lugar : estaba agregando fragmentos dinámicamente usando fragmentTransactions. Segundo: mis fragmentos fueron modificados usando AsyncTasks (consultas de base de datos en un servidor). Tercero: mi fragmento no fue instanciado al inicio de la actividad Cuarto: utilicé una instanciación de fragmentos personalizada "create or load it" para obtener la variable fragment. Cuarto: la actividad fue recreada debido a cambio de orientación

El problema era que quería "eliminar" el fragmento debido a la respuesta de la consulta, pero el fragmento se creó incorrectamente justo antes. No se por qué, probablemente debido al "commit" que se hizo más tarde, el fragmento no se agregó todavía cuando era el momento de eliminarlo. Por lo tanto getActivity() estaba devolviendo null.

Solución : 1) Tuve que comprobar que estaba tratando correctamente de encontrar la primera instancia del fragmento antes de crear uno nuevo 2)tuve que poner serRetainInstance (true) en ese fragmento para mantenerlo a través del cambio de orientación (no se necesita backstack, por lo tanto, no hay problema) 3) En lugar de "recrear o obtener un fragmento antiguo" justo antes de "eliminarlo", pongo directamente el fragmento al inicio de la actividad. Instanciarla al inicio de la actividad en lugar de "cargar" (o instanciar) la variable fragment antes de eliminarla evitó problemas con getActivity.

 1
Author: Feuby,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-03 15:20:05