¿Los fragmentos realmente necesitan un constructor vacío?

Resuelto asked hace 54 años • 5 respuestas

Tengo Fragmentun constructor que toma múltiples argumentos. Mi aplicación funcionó bien durante el desarrollo, pero en producción mis usuarios a veces ven este bloqueo:

android.support.v4.app.Fragment$InstantiationException: Unable to instantiate fragment 
make sure class name exists, is public, and has an empty constructor that is public

Podría crear un constructor vacío como sugiere este mensaje de error, pero eso no tiene sentido para mí ya que entonces tendría que llamar a un método separado para terminar de configurar el archivo Fragment.

Tengo curiosidad por saber por qué este bloqueo sólo ocurre ocasionalmente. ¿ Quizás estoy usando ViewPagerincorrectamente? Yo mismo creo una instancia de todos los Fragments y los guardo en una lista dentro del archivo Activity. No uso FragmentManagertransacciones, ya que los ViewPagerejemplos que he visto no lo requieren y todo parecía estar funcionando durante el desarrollo.

 avatar Jan 01 '70 08:01
Aceptado

Ellos si.

De todos modos, no deberías anular el constructor. Debe tener un newInstance()método estático definido y pasar cualquier parámetro mediante argumentos (paquete)

Por ejemplo:

public static final MyFragment newInstance(int title, String message) {
    MyFragment f = new MyFragment();
    Bundle bdl = new Bundle(2);
    bdl.putInt(EXTRA_TITLE, title);
    bdl.putString(EXTRA_MESSAGE, message);
    f.setArguments(bdl);
    return f;
}

Y por supuesto tomando los argumentos de esta manera:

@Override
public void onCreate(Bundle savedInstanceState) {
    title = getArguments().getInt(EXTRA_TITLE);
    message = getArguments().getString(EXTRA_MESSAGE);

    //...
    //etc
    //...
}

Luego crearías una instancia desde tu administrador de fragmentos de esta manera:

@Override
public void onCreate(Bundle savedInstanceState) {
    if (savedInstanceState == null){
        getSupportFragmentManager()
            .beginTransaction()
            .replace(R.id.content, MyFragment.newInstance(
                R.string.alert_title,
                "Oh no, an error occurred!")
            )
            .commit();
    }
}

De esta manera, si se desconecta y se vuelve a adjuntar, el estado del objeto se puede almacenar a través de los argumentos. Al igual que los paquetes adjuntos a los Intents.

Razón: lectura adicional

Pensé en explicar por qué a la gente que se pregunta por qué.

Si marca: https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/Fragment.java

Verá que el instantiate(..)método en la Fragmentclase llama al newInstancemétodo:

public static Fragment instantiate(Context context, String fname, @Nullable Bundle args) {
    try {
        Class<?> clazz = sClassMap.get(fname);
        if (clazz == null) {
            // Class not found in the cache, see if it's real, and try to add it
            clazz = context.getClassLoader().loadClass(fname);
            if (!Fragment.class.isAssignableFrom(clazz)) {
                throw new InstantiationException("Trying to instantiate a class " + fname
                        + " that is not a Fragment", new ClassCastException());
            }
            sClassMap.put(fname, clazz);
        }
        Fragment f = (Fragment) clazz.getConstructor().newInstance();
        if (args != null) {
            args.setClassLoader(f.getClass().getClassLoader());
            f.setArguments(args);
        }
        return f;
    } catch (ClassNotFoundException e) {
        throw new InstantiationException("Unable to instantiate fragment " + fname
                + ": make sure class name exists, is public, and has an"
                + " empty constructor that is public", e);
    } catch (java.lang.InstantiationException e) {
        throw new InstantiationException("Unable to instantiate fragment " + fname
                + ": make sure class name exists, is public, and has an"
                + " empty constructor that is public", e);
    } catch (IllegalAccessException e) {
        throw new InstantiationException("Unable to instantiate fragment " + fname
                + ": make sure class name exists, is public, and has an"
                + " empty constructor that is public", e);
    } catch (NoSuchMethodException e) {
        throw new InstantiationException("Unable to instantiate fragment " + fname
                + ": could not find Fragment constructor", e);
    } catch (InvocationTargetException e) {
        throw new InstantiationException("Unable to instantiate fragment " + fname
                + ": calling Fragment constructor caused an exception", e);
    }
}

http://docs.oracle.com/javase/6/docs/api/java/lang/Class.html#newInstance() Explica por qué, al crear una instancia, verifica que el descriptor de acceso lo sea publicy que ese cargador de clases permita el acceso a él.

Es un método bastante desagradable en general, pero permite matar FragmentMangery recrear Fragmentscon estados. (El subsistema de Android hace cosas similares con Activities).

Clase de ejemplo

Me preguntan mucho sobre las llamadas newInstance. No confunda esto con el método de clase. Este ejemplo de clase completa debería mostrar el uso.

/**
 * Created by chris on 21/11/2013
 */
public class StationInfoAccessibilityFragment extends BaseFragment implements JourneyProviderListener {

    public static final StationInfoAccessibilityFragment newInstance(String crsCode) {
        StationInfoAccessibilityFragment fragment = new StationInfoAccessibilityFragment();

        final Bundle args = new Bundle(1);
        args.putString(EXTRA_CRS_CODE, crsCode);
        fragment.setArguments(args);

        return fragment;
    }

    // Views
    LinearLayout mLinearLayout;

    /**
     * Layout Inflater
     */
    private LayoutInflater mInflater;
    /**
     * Station Crs Code
     */
    private String mCrsCode;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mCrsCode = getArguments().getString(EXTRA_CRS_CODE);
    }

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

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        mLinearLayout = (LinearLayout)view.findViewBy(R.id.station_info_accessibility_linear);
        //Do stuff
    }

    @Override
    public void onResume() {
        super.onResume();
        getActivity().getSupportActionBar().setTitle(R.string.station_info_access_mobility_title);
    }

    // Other methods etc...
}
Chris.Jenkins avatar May 04 '2012 14:05 Chris.Jenkins

Como señaló CommonsWare en esta pregunta https://stackoverflow.com/a/16064418/1319061 , este error también puede ocurrir si está creando una subclase anónima de un Fragmento, ya que las clases anónimas no pueden tener constructores.

No hagas subclases anónimas de Fragment :-)

JesperB avatar Jun 24 '2013 13:06 JesperB

Sí, como puede ver, el paquete de soporte también crea instancias de los fragmentos (cuando se destruyen y se vuelven a abrir). Sus Fragmentsubclases necesitan un constructor público vacío, ya que esto es lo que llama el marco.

Sveinung Kval Bakken avatar May 04 '2012 14:05 Sveinung Kval Bakken