La mejor práctica para crear instancias de un nuevo Fragmento de Android

He visto dos prácticas generales para crear un nuevo Fragmento en una aplicación:

Fragment newFragment = new MyFragment(); 

y

 Fragment newFragment = MyFragment.newInstance(); 

La segunda opción utiliza un método estático newInstance() y generalmente contiene el siguiente método.

 public static Fragment newInstance() { MyFragment myFragment = new MyFragment(); return myFragment; } 

Al principio, pensé que el principal beneficio era el hecho de que podía sobrecargar el método newInstance () para dar flexibilidad al crear nuevas instancias de un Fragmento, pero también podía hacerlo creando un constructor sobrecargado para el Fragmento.

¿Me he perdido algo?

¿Cuáles son los beneficios de un enfoque sobre el otro? ¿O es solo una buena práctica?

Si Android decide recrear su Fragmento más tarde, va a llamar al constructor sin argumentos de su fragmento. Así que sobrecargar el constructor no es una solución.

Habiendo dicho eso, la forma de pasar cosas a tu Fragmento para que estén disponibles después de que un Fragmento sea recreado por Android es pasar un paquete al método setArguments .

Entonces, por ejemplo, si quisiéramos pasar un entero al fragmento, usaríamos algo como:

 public static MyFragment newInstance(int someInt) { MyFragment myFragment = new MyFragment(); Bundle args = new Bundle(); args.putInt("someInt", someInt); myFragment.setArguments(args); return myFragment; } 

Y más adelante en Fragment onCreate() puedes acceder a ese entero usando:

 getArguments().getInt("someInt", 0); 

Este paquete estará disponible incluso si el fragmento es de alguna manera recreado por Android.

También tenga en cuenta: setArguments solo se puede llamar antes de que el Fragmento se adjunte a la Actividad.

Este enfoque también está documentado en la referencia de desarrollador de Android: https://developer.android.com/reference/android/app/Fragment.html

El único beneficio en el uso de newInstance() que veo es el siguiente:

  1. Tendrás un solo lugar donde todos los argumentos utilizados por el fragmento podrían agruparse y no tienes que escribir el código a continuación cada vez que instancias un fragmento.

     Bundle args = new Bundle(); args.putInt("someInt", someInt); args.putString("someString", someString); // Put any other arguments myFragment.setArguments(args); 
  2. Es una buena manera de decirle a otras clases qué argumentos espera que funcione fielmente (aunque deberías poder manejar casos si no hay paquetes agrupados en la instancia del fragmento).

Entonces, mi opinión es que usar una newInstance() instancia estática newInstance() para instanciar un fragmento es una buena práctica.

También hay otra forma:

 Fragment.instantiate(context, MyFragment.class.getName(), myBundle) 

Mientras @yydl da una razón convincente sobre por qué el método newInstance es mejor:

Si Android decide recrear su Fragmento más tarde, va a llamar al constructor sin argumentos de su fragmento. Así que sobrecargar el constructor no es una solución.

aún es bastante posible usar un constructor . Para ver por qué esto es así, primero debemos ver por qué la solución anterior es utilizada por Android.

Antes de que se pueda usar un fragmento, se necesita una instancia. Android llama a YourFragment() (el constructor sin argumentos ) para construir una instancia del fragmento. Aquí se ignorará cualquier constructor sobrecargado que escriba, ya que Android no puede saber cuál usar.

Durante la vida de una actividad, el fragmento se crea como se describió anteriormente y se destruye varias veces por Android. Esto significa que si coloca datos en el objeto fragmento, se perderá una vez que se destruya el fragmento.

Para solucionarlo, Android le pide que almacene datos utilizando un Bundle (llamando a setArguments() ), que luego se puede acceder desde YourFragment . Los bundle argumentos están protegidos por Android y, por lo tanto, se garantiza su persistencia .

Una forma de establecer este paquete es mediante el uso de un método newInstance estático:

 public static YourFragment newInstance (int data) { YourFragment yf = new YourFragment() /* See this code gets executed immediately on your object construction */ Bundle args = new Bundle(); args.putInt("data", data); yf.setArguments(args); return yf; } 

Sin embargo, un constructor:

 public YourFragment(int data) { Bundle args = new Bundle(); args.putInt("data", data); setArguments(args); } 

puede hacer exactamente lo mismo que el método newInstance .

Naturalmente, esto fallaría, y es una de las razones por las que Android quiere que uses el método newInstance :

 public YourFragment(int data) { this.data = data; // Don't do this } 

Como explicación adicional, aquí está la clase de fragmentos de Android:

 /** * Supply the construction arguments for this fragment. This can only * be called before the fragment has been attached to its activity; that * is, you should call it immediately after constructing the fragment. The * arguments supplied here will be retained across fragment destroy and * creation. */ public void setArguments(Bundle args) { if (mIndex >= 0) { throw new IllegalStateException("Fragment already active"); } mArguments = args; } 

Tenga en cuenta que Android solicita que los argumentos se establezcan solo durante la construcción y garantiza que se conservarán.

EDITAR : Como se señala en los comentarios de @JHH, si proporciona un constructor personalizado que requiere algunos argumentos, Java no proporcionará su fragmento con un constructor no arg predeterminado. Por lo tanto, esto requeriría que definiera un constructor no arg , que es un código que podría evitar con el método de fábrica newInstance .

EDITAR : Android ya no permite el uso de un constructor sobrecargado para los fragmentos. Debe usar el newInstance método de newInstance .

No estoy de acuerdo con la respuesta de yydi diciendo:

Si Android decide recrear su Fragmento más tarde, va a llamar al constructor sin argumentos de su fragmento. Así que sobrecargar el constructor no es una solución.

Creo que es una solución y una buena, esta es exactamente la razón por la que ha sido desarrollada por el lenguaje central de Java.

Es cierto que el sistema Android puede destruir y recrear su Fragment . Entonces puedes hacer esto:

 public MyFragment() { // An empty constructor for Android System to use, otherwise exception may occur. } public MyFragment(int someInt) { Bundle args = new Bundle(); args.putInt("someInt", someInt); setArguments(args); } 

Te permitirá extraer someInt de getArguments() este último, incluso si el Fragment ha sido recreado por el sistema. Esta es una solución más elegante que static constructor static .

Para mi opinión, static constructores static son inútiles y no deberían usarse. También lo limitarán si en el futuro desea extender este Fragment y agregar más funcionalidad al constructor. Con static constructor static no puedes hacer esto.

Actualizar:

Android agregó una inspección que marca todos los constructores no predeterminados con un error.
Recomiendo desactivarlo, por las razones mencionadas anteriormente.

Algún código de kotlin :

 companion object { fun newInstance(first: String, second: String) : SampleFragment { return SampleFragment().apply { arguments = Bundle().apply { putString("firstString", first) putString("secondString", second) } } } } 

Y puedes obtener argumentos con esto:

 val first: String by lazy { arguments?.getString("firstString") ?: "default"} val second: String by lazy { arguments?.getString("secondString") ?: "default"} 

La mejor práctica para instanciar fragmentos con argumentos en Android es tener un método de fábrica estático en tu fragmento.

 public static MyFragment newInstance(String name, int age) { Bundle bundle = new Bundle(); bundle.putString("name", name); bundle.putInt("age", age); MyFragment fragment = new MyFragment(); fragment.setArguments(bundle); return fragment; } 

Debe evitar establecer sus campos con la instancia de un fragmento. Porque cada vez que el sistema Android recrea su fragmento, si siente que el sistema necesita más memoria, entonces recreará su fragmento usando el constructor sin argumentos.

Puede encontrar más información sobre las mejores prácticas para instanciar fragmentos con argumentos aquí.

Dado que las preguntas sobre las mejores prácticas, yo agregaría, esa muy a menudo buena idea es usar un enfoque híbrido para crear fragmentos cuando se trabaja con algunos servicios web REST.

No podemos pasar objetos complejos, por ejemplo, algún modelo de usuario, para el caso de mostrar fragmento de usuario

Pero lo que podemos hacer es registrarnos en ¡ onCreate ese usuario! = Nulo y si no, luego traerlo de la capa de datos; de lo contrario, usar el existente.

De esta forma, obtenemos la capacidad de recrear por usuario Id en caso de recreación de fragmento por Android y la ligereza para las acciones de los usuarios, así como la capacidad de crear fragmentos al retener el objeto o solo su ID

Algo le gusta esto:

 public class UserFragment extends Fragment { public final static String USER_ID="user_id"; private User user; private long userId; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); userId = getArguments().getLong(USER_ID); if(user==null){ // // Recreating here user from user id(ie requesting from your data model, // which could be services, direct request to rest, or data layer sitting // on application model // user = bringUser(); } } public static UserFragment newInstance(User user, long user_id){ UserFragment userFragment = new UserFragment(); Bundle args = new Bundle(); args.putLong(USER_ID,user_id); if(user!=null){ userFragment.user=user; } userFragment.setArguments(args); return userFragment; } public static UserFragment newInstance(long user_id){ return newInstance(null,user_id); } public static UserFragment newInstance(User user){ return newInstance(user,user.id); } } 

La mejor forma de instanciar el fragmento es usar el método Fragment.instantiate predeterminado o crear un método de fábrica para crear una instancia del fragmento
Precaución: crea siempre un constructor vacío en un fragmento distinto mientras que la restauración de la memoria de fragmento arrojará una excepción en tiempo de ejecución.

setArguments() es inútil. Solo trae un desastre.

 public class MyFragment extends Fragment { public String mTitle; public String mInitialTitle; public static MyFragment newInstance(String param1) { MyFragment f = new MyFragment(); f.mInitialTitle = param1; f.mTitle = param1; return f; } @Override public void onSaveInstanceState(Bundle state) { state.putString("mInitialTitle", mInitialTitle); state.putString("mTitle", mTitle); super.onSaveInstanceState(state); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle state) { if (state != null) { mInitialTitle = state.getString("mInitialTitle"); mTitle = state.getString("mTitle"); } ... } } 

Creo que tengo una solución mucho más simpeler para esto.

 public class MyFragment extends Fragment{ private String mTitle; private List mObjects; public static MyFragment newInstance(String title, List objects) MyFragment myFrag = new MyFragment(); myFrag.mTitle = title; myFrag.mObjects = objects; return myFrag; }