¿Los fragmentos realmente necesitan 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 producción mis usuarios a veces ven este locking:

 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 diferente para terminar de configurar el Fragment .

Tengo curiosidad de por qué este choque solo ocurre ocasionalmente. ¿Tal vez estoy usando ViewPager incorrectamente? Instalé todos los Fragment y los guardo en una lista dentro de la Activity . No utilizo las transacciones de FragmentManager , ya que los ejemplos de ViewPager que he visto no lo requerían y todo parecía estar funcionando durante el desarrollo.

Ellos si.

Realmente no deberías estar anulando el constructor de todos modos. Debería tener un newInstance() método estático de 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, agarrar a los args 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 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 forma, 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 adjuntos a Intents.

Motivo: lectura extra

Pensé que podría explicar por qué las personas se preguntan por qué.

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

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 momento de la instanciación, comprueba que el descriptor de acceso es public y que ese cargador de clases permite el acceso a él. .

Es un método bastante desagradable en total, pero le permite a FragmentManger matar y recrear Fragments con estados. (El subsistema Android hace cosas similares con Activities ).

Clase de ejemplo

Me preguntan mucho sobre llamar a newInstance , (no confundas 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 Fragmento 🙂

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

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 puedes cambiar el tipo y la cantidad de parámetros. Rapido y Facil.