¿Fragments realmente necesita un constructor vacío?
Tengo un Fragment
con un constructor que toma múltiples argumentos. Mi aplicación funcionó bien durante el desarrollo, pero en la 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 hacer 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 Fragment
.
Tengo curiosidad por saber por qué este accidente solo ocurre ocasionalmente. Tal vez estoy usando el ViewPager
incorrectamente? Instanciar todos los Fragment
y guardarlos en una lista dentro de la Activity
. No uso transacciones FragmentManager
, ya que los ejemplos ViewPager
que he visto no lo requerían y todo parecía estar funcionando durante el desarrollo.
4 answers
Sí lo hacen.
De todos modos, no deberías sobrescribir el constructor. Debe tener un método estático newInstance()
definido y pasar cualquier parámetro a través de argumentos (bundle)
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 agarrando los args de esta manera:
@Override
public void onCreate(Bundle savedInstanceState) {
title = getArguments().getInt(EXTRA_TITLE);
message = getArguments().getString(EXTRA_MESSAGE);
//...
//etc
//...
}
Entonces puedes crear una instancia de tu administrador de fragmentos de la siguiente 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 separa y se vuelve a unir, el estado del objeto se puede almacenar a través de los argumentos. Al igual que los paquetes unidos a Intención.
Razón-Lectura extra
Pensé en explicar por qué a la gente preguntándose por qué.
Verá que el método instantiate(..)
en la clase Fragment
llama al método newInstance
:
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 instanciarlo comprueba que el accessor es public
y que ese cargador de clases permite el acceso a él.
Es un método bastante desagradable en general, pero permite a FragmentManger
matar y recrear Fragments
con estados. (El subsistema Android hace cosas similares con Activities
).
Clase de ejemplo
Me preguntan mucho acerca de llamar a newInstance
, (no confunda esto con el método de clase. Todo este ejemplo de clase 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...
}
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-08 13:41:31
Como señala 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 hacer subclases anónimas de Fragmento: -)
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 12:18:28
Sí, como puede ver el paquete de soporte también crea instancias de los fragmentos (cuando se destruyen y se vuelven a abrir). Sus subclases Fragmnt necesitan un constructor vacío público ya que esto es lo que está siendo llamado por el framework.
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
2012-05-04 14:04:05
Aquí está mi solución simple:
1-Define tu fragmento
public class MyFragment extends Fragment {
private String parameter;
public MyFragment() {
}
public void setParameter(String parameter) {
this.parameter = parameter;
}
}
2-Crea tu nuevo fragmento y rellena el parámetro
myfragment = new MyFragment();
myfragment.setParameter("here the value of my parameter");
3 - ¡Disfrútalo!
Obviamente puede cambiar el tipo y el número de parámetros. Rápido y fácil.
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-08-01 13:19:49