studio - java android fragment
¿Realmente los fragmentos necesitan un constructor vacío? (4)
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 llena el parámetro
myfragment = new MyFragment();
myfragment.setParameter("here the value of my parameter");
3 - ¡Disfrútalo!
Obviamente puedes cambiar el tipo y la cantidad de parámetros. Rapido y Facil.
Tengo un Fragment
con 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 hacer un constructor vacío como sugiere este mensaje de error, pero eso no tiene sentido para mí desde entonces, tendría que llamar a un método separado para terminar de configurar el Fragment
.
Tengo curiosidad por saber por qué este accidente sólo ocurre de vez en cuando. Tal vez estoy usando el ViewPager
incorrectamente? Yo mismo hago una instancia de todos los Fragment
y los guardo en una lista dentro de la Activity
. No uso las transacciones de FragmentManager
, ya que los ejemplos de ViewPager
que he visto no lo requerían y todo parecía funcionar durante el desarrollo.
Como lo señaló CommonsWare en esta pregunta https://.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 Fragmento :-)
Ellos si.
De todos modos, no deberías estar anulando al constructor. Debe tener un método estático newInstance()
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 agarrando los argumentos de esta manera:
@Override
public void onCreate(Bundle savedInstanceState) {
title = getArguments().getInt(EXTRA_TITLE);
message = getArguments().getString(EXTRA_MESSAGE);
//...
//etc
//...
}
Entonces usted podría crear una instancia de su 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 intentos.
Razón - Lectura extra
Pensé que explicaría por qué para la gente que se pregunta por qué.
Verá que el método de 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 instanciarse, comprueba que el accesor es public
y que ese cargador de clases le permite acceder .
Es un método bastante desagradable en general, pero permite que FragmentManger
mate y Fragments
crear Fragments
con estados. (El subsistema Android hace cosas similares con Activities
).
Clase de ejemplo
Me preguntan mucho sobre cómo llamar a newInstance
, (no confunda esto con el método de clase. Este ejemplo de toda la 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...
}
Sí, como puede ver, el paquete de soporte también crea una instancia de los fragmentos (cuando se destruyen y se vuelven a abrir). Las subclases de Fragemnt necesitan un constructor vacío público ya que esto es lo que llama el marco.