¿Los fragmentos realmente necesitan un constructor vacío?
Tengo Fragment
un 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 ViewPager
incorrectamente? Yo mismo creo una instancia de todos los Fragment
s y los guardo en una lista dentro del archivo Activity
. No uso FragmentManager
transacciones, ya que los ViewPager
ejemplos que he visto no lo requieren y todo parecía estar funcionando durante el desarrollo.
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 Fragment
clase llama al newInstance
mé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 public
y que ese cargador de clases permita el acceso a él.
Es un método bastante desagradable en general, pero permite matar FragmentManger
y recrear Fragments
con 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...
}
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 :-)
Sí, como puede ver, el paquete de soporte también crea instancias de los fragmentos (cuando se destruyen y se vuelven a abrir). Sus Fragment
subclases necesitan un constructor público vacío, ya que esto es lo que llama el marco.