Práctica recomendada para crear instancias de un nuevo fragmento de Android

706

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

Fragment newFragment = new MyFragment();

y

Fragment newFragment = MyFragment.newInstance();

La segunda opción hace uso de 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?

Graham Smith
fuente
Cuando hay parámetros, no hay otra opción, y esto se responde ampliamente aquí. Aún así, la cuestión sigue siendo la construcción sin argumentos del fragmento.
rds
1
Después de aprender sobre los patrones de fábrica y cómo una clase de llamada que no instancia un objeto en sí mismo ayuda a desacoplarlos, pensé que este sería un punto fuerte para el método newInstance (). ¿Estoy equivocado en esto? No he visto este argumento específico mencionado como un beneficio.
Aplicaciones móviles del

Respuestas:

1137

Si Android decide recrear su Fragmento más tarde, llamará al constructor sin argumentos de su fragmento. Por lo tanto, sobrecargar el constructor no es una solución.

Dicho esto, la forma de pasar cosas a tu Fragment para que estén disponibles después de que Android haya recreado un Fragment es pasar un paquete al setArgumentsmétodo.

Entonces, por ejemplo, si quisiéramos pasar un número 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 el Fragmento onCreate()puedes acceder a ese número entero usando:

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

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

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

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

yydl
fuente
77
@Vlasto desafortunadamente, los métodos estáticos no se pueden anular.
AJD
8
@yydl Creo que me falta algo aquí, ¿no podrías usar un constructor aquí de todos modos? fragmento)?
Mike Tunnicliffe
99
@mgibson Debe usar un paquete si desea que los datos estén disponibles cuando el fragmento se vuelva a crear más tarde.
yydl
114
Ser FORZADO para crear un constructor sin argumentos para fragmentos es potencialmente el mayor problema en toda la programación, en cualquier lugar. Fuerza un cambio de paradigma completo en la creación e inicialización de objetos. Si eres nuevo en Android y te has topado con este hilo, lee la respuesta una y otra vez.
rmirabelle
99
Yo discutiría con esa afirmación. Primero, la seguridad de los tipos es una preocupación del lenguaje, no una preocupación del marco. En segundo lugar, en mi opinión, el marco está entrando en el área de "cosas que su API nunca debe hacer". Si quiero pasar la biblioteca del congreso a mi constructor de fragmentos, entonces debería permitirme hacerlo. El contrato del constructor "sin argumentos" básicamente mata el uso de la inyección de dependencia en fragmentos, gran asco.
rmirabelle
95

El único beneficio al usar lo newInstance()que veo son los siguientes:

  1. Tendrá un solo lugar donde todos los argumentos utilizados por el fragmento podrían agruparse y no tiene que escribir el código a continuación cada vez que cree una instancia de 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 funcionen fielmente (aunque debería ser capaz de manejar casos si no hay argumentos agrupados en la instancia del fragmento).

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

500865
fuente
44
1) ¿Cómo es esto diferente de poner la lógica en un constructor? Ambos son lugares únicos donde incluye esta lógica. 2) ¿En qué se diferencian los parámetros de una fábrica estática de los parámetros de un constructor? Ambos dicen qué argumentos se esperan. Mi punto es que es un paradigma diferente, claro, pero no hay un beneficio claro sobre el uso de constructores.
RJ Cuthbertson
2
No puede usar constructores personalizados para fragmentos. Framework utiliza el constructor sin argumentos para restaurar fragmentos.
500865
55
Sí, estoy de acuerdo contigo allí. Digo conceptualmente que no hay ningún beneficio en usar el patrón de fábrica estático en lugar de usar constructores sobrecargados, y viceversa. Ambos puntos son válidos en ambos patrones; no hay beneficio de usar uno sobre el otro. Android te obliga a usar el patrón estático de fábrica, pero no es beneficioso usar uno u otro.
RJ Cuthbertson
pastebin.com/EYJzES0j
RJ Cuthbertson
@RJCuthbertson Un posible beneficio sería la capacidad de crear y devolver subclases de la clase del método de fábrica estática, es decir, devolver una subclase apropiada para la situación.
urgente
62

También hay otra forma:

Fragment.instantiate(context, MyFragment.class.getName(), myBundle)
usuario1145201
fuente
Si no me equivoco, esto solo es posible cuando utiliza la biblioteca de soporte de Android.
Timo
2
Intenté esto con la biblioteca de soporte, pero en onCreateView (en mi fragmento), el paquete pasado era nulo, así que elegí la opción setArguments / getArguments y funcionó (para cualquiera que lea esto).
Jrop
1
Interesante, no he visto este enfoque antes. ¿Tiene alguna ventaja sobre otros enfoques para crear instancias de un Fragmento?
IgorGanapolsky
22
De los documentos del desarrollador ,instantiate() Creates a new instance of a Fragment with the given class name. This is the same as calling its empty constructor.
Brian Bowman
2
Aunque mencionaron lo mismo que llamar a constructor vacío. "args.setClassLoader (f.getClass (). getClassLoader ());" se llama por debajo de los argumentos del paquete
Gökhan Barış Aker
49

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

Si Android decide recrear su Fragmento más tarde, llamará al constructor sin argumentos de su fragmento. Por lo tanto, sobrecargar el constructor no es una solución.

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

Antes de que se pueda usar un fragmento, se necesita una instancia. Android llama 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 útil de una Actividad, el fragmento se crea como se describe arriba y Android lo destruye varias veces. Esto significa que si coloca datos en el propio objeto de fragmento, se perderá una vez que se destruya el fragmento.

Para solucionarlo, Android le pide que almacene datos utilizando un Bundle(llamada setArguments()), al que luego se puede acceder desde YourFragment. Los argumentos bundleestán protegidos por Android y, por lo tanto, se garantiza que sean persistentes .

Una forma de configurar este paquete es mediante el uso de un newInstancemétodo 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 newInstancemétodo.

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

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

Como explicación adicional, aquí está la Clase de Fragmento 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 en la construcción y garantiza que se retendrán.

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

EDITAR : Android ya no permite usar un constructor sobrecargado para fragmentos. Debes usar el newInstancemétodo.

ps95
fuente
¿Cuándo se justificaría usar android: configChanges = "oriente | keyboardHidden | screenSize"?
Luke Allison
1
Android Studio ahora arroja un error para todos los constructores no predeterminados en fragmentos, por lo que esto ya no funciona.
Sheharyar
66
Santo hombre, me pregunto cuántos desarrolladores de droides han escrito código fuera del droide. Es una locura que no podamos usar el enfoque que usted describe. NO hay argumentos convincentes de ningún comentario sobre por qué DEBEMOS usar el método estático de fábrica. Es aún más inquietante que generen un error al compilar. Esta es definitivamente la mejor respuesta proporcionada y muestra que no hay ningún beneficio para SFM.
MPavlak
3
Bueno, hay una sutil razón. Usted es libre de crear su propio constructor con argumentos, pero aún debe existir un constructor sin argumentos . Dado que las clases siempre tienen un constructor implícito sin argumentos a menos que se defina explícitamente un constructor con argumentos , esto significa que tendría que definir su constructor arg y un constructor sin argumentos explícitamente, o el sistema no podría invocar ninguno constructor sin argumentos. Creo que esta es la razón por la cual la recomendación es utilizar un método de fábrica estático: simplemente reduce el riesgo de olvidarse de definir un constructor sin argumentos.
JHH
@JHH que fallará en el momento de la compilación, por lo que no es un gran riesgo. Sin embargo, el problema aquí es que Android está negando la sobrecarga del constructor, un paradigma clave de programación.
ps95
20

No estoy de acuerdo con la respuesta de yydi diciendo:

Si Android decide recrear su Fragmento más tarde, llamará al constructor sin argumentos de su fragmento. Por lo tanto, 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 fue desarrollada por el lenguaje central de Java.

Es cierto que el sistema Android puede destruir y recrear tu 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);
}

Le permitirá avanzar someIntdesde getArguments()este último, incluso si el sistema lo Fragmentha recreado. Esta es una solución más elegante que el staticconstructor.

En mi opinión, los staticconstructores son inútiles y no deberían usarse. También lo limitarán si en el futuro desea ampliar esto Fragmenty agregar más funcionalidades al constructor. Con el staticconstructor no puedes hacer esto.

Actualizar:

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

Ilya Gazman
fuente
44
Otro beneficio de tener un método estático, que no mencioné anteriormente, es que no puede establecer accidentalmente propiedades a partir de él.
yydl
44
Además, con respecto a su punto sobre "extender este fragmento", este método sería realmente malo si alguna vez extiende la clase. Llamar a super hará que la llamada setArguments () solo sea efectiva para el niño o el padre, ¡pero no para ambos!
yydl
2
@yydle puede evitar esta situación llamando a obtener argumentos para inicializar el Bundle secundario. Manera de Java siempre mejor.
Ilya Gazman
99
Es cierto, pero esa es otra razón para alentar a las personas a usar el patrón sugerido por Google. Por supuesto, todos estamos de acuerdo en que su solución es 100% técnicamente factible. De la misma manera, hay muchas maneras de hacer muchas cosas. La pregunta, sin embargo, es si es la mejor. Y creo firmemente que usar el constructor no representa la verdadera naturaleza de cómo se supone que funciona.
yydl
3
Estoy de acuerdo con @yydl en que la creación estática es mejor. Otro beneficio es la inyección de dependencia de nuevas dependencias futuras: el constructor no está preparado para eso y probablemente causará más cambios de código (o se agregarán más constructores).
Boon
19

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"}
Rafols
fuente
3

La mejor práctica para instanciar fragmentos con argumentos en Android es tener un método de fábrica estático en su 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 configurar sus campos con la instancia de un fragmento. Porque cada vez que el sistema Android recrea tu fragmento, si siente que el sistema necesita más memoria, entonces recreará tu fragmento usando un constructor sin argumentos.

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

Gunhan
fuente
2

Desde las preguntas sobre las mejores prácticas, agregaría, que a menudo es una buena idea utilizar un enfoque híbrido para crear fragmentos al trabajar con algunos servicios web REST

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

Pero lo que podemos hacer es registrar a onCreateese usuario! = Nulo y si no, luego traerlo de la capa de datos, de lo contrario, usar el existente.

De esta manera, obtenemos tanto la capacidad de recrear por ID de usuario en caso de recreación de fragmentos por parte de Android como la facilidad para las acciones del usuario, así como la capacidad de crear fragmentos al mantener el objeto en sí 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(i.e 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);
    }
}
Tigra
fuente
3
Usted dijo: "No podemos pasar objetos complejos, por ejemplo, algún modelo de Usuario", no es cierto, podemos. De esta manera: User user = /*...*/ coloque al usuario en el paquete: Bundle bundle = new Bundle(); bundle.putParcelable("some_user", user); y obtenga al usuario de argumentos: User user = getArguments().getParcelable("some_user"); el objeto debe implementar la interfaz Parcelable. enlace
Adam Varhegyi
3
Bueno, sí, pero cuando la clase es compleja y contiene referencias a otros objetos ... Personalmente prefiero que sea simple, o tengo un objeto, o no lo tengo y luego necesito obtenerlo
Tigra
1

usa este código 100% soluciona tu problema

ingrese este código en el primer fragmento

public static yourNameParentFragment newInstance() {

    Bundle args = new Bundle();
    args.putBoolean("yourKey",yourValue);
    YourFragment fragment = new YourFragment();
    fragment.setArguments(args);
    return fragment;
}

esta muestra envía datos booleanos

y en SecendFragment

yourNameParentFragment name =yourNameParentFragment.newInstance();
   Bundle bundle;
   bundle=sellDiamondFragments2.getArguments();
  boolean a= bundle.getBoolean("yourKey");

debe valor en el primer fragmento es estático

código feliz

Amin Emadi
fuente
0

La mejor manera de crear una instancia del fragmento es usar el método predeterminado Fragment.instantiate o crear un método de fábrica para crear una instancia del fragmento
Precaución: siempre cree un constructor vacío en otro fragmento mientras restaura la memoria del fragmento generará una excepción en tiempo de ejecución.

Mahesh
fuente
0

Últimamente estoy aquí. Pero algunas cosas que acabo de saber que podrían ayudarte un poco.

Si está utilizando Java, no hay mucho que cambiar. Pero para los desarrolladores de kotlin, aquí hay un fragmento de código siguiente que creo que puede convertirlo en un sótano para ejecutar:

  • Fragmento principal:
inline fun <reified T : SampleFragment> newInstance(text: String): T {
    return T::class.java.newInstance().apply {
        arguments = Bundle().also { it.putString("key_text_arg", text) }
    }
}
  • Llamada normal
val f: SampleFragment = SampleFragment.newInstance("ABC")
// or val f = SampleFragment.newInstance<SampleFragment>("ABC")
  • Puede extender la operación de inicio principal en la clase de fragmento secundario mediante:
fun newInstance(): ChildSampleFragment {
    val child = UserProfileFragment.newInstance<ChildSampleFragment>("XYZ")
    // Do anything with the current initialized args bundle here
    // with child.arguments = ....
    return child
}

Feliz codificación

vhfree
fuente
-2

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");
        } 
        ...
    }
}
Vadim Star
fuente
Excepto que acaba de verse obligado a anular un método más y crear un campo que podría haberse aislado al onViewCreatedalcance. Es conveniencia, supongo, muchas formas de hacer lo mismo. También es una manera fácil de verificar las actualizaciones realizadas por el usuario (compare los paquetes de getArgumentsy el paquete de onSaveInstanceState)
Overclover
@Asagen, me gusta tu comentario sobre comparar valores iniciales y de usuario. Edité el código y considero que todavía es uniforme y claro sin getArgumentscosas. ¿Qué pasa con el onViewCreatedalcance ... Podemos restaurar el paquete de estado allí. Pero prefiero hacer onCreateViewque la inicialización sea ligera y rápida, y hacer todas las inicializaciones pesadas dentro de una, onActivityCreatedporque a Fragment.getActivity()veces me gusta regresar nully debido a los onAttach()cambios en la nueva versión de API 23.
Vadim Star
Todo lo que hiciste aquí fue mudarte set y get Argumentsentrar saveInstanceState. Básicamente estás haciendo lo mismo que se hace "bajo el capó"
OneCricketeer
1
@ cricket_007, o justo enfrente . El uso saveInstanceStatees "debajo del capó". Y el uso de Argumentses una duplicación de la funcionalidad que hace que verifique dos veces: primero Argumentsvalores y luego saveInstanceStatevalores. Porque tienes que usar de saveInstanceStatecualquier manera. ¿Qué pasa con Arguments... no son necesarios.
Vadim Star
Los argumentos son el equivalente de Intenciones extras para fragmentos. No son inútiles, contienen los parámetros iniciales que son diferentes del estado actual.
BladeCoder
-12

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

public class MyFragment extends Fragment{

   private String mTitle;
   private List<MyObject> mObjects;

   public static MyFragment newInstance(String title, List<MyObject> objects)
   MyFragment myFrag = new MyFragment();
   myFrag.mTitle = title;
   myFrag.mObjects = objects;
   return myFrag;
   }
Stefan Bogaard
fuente
12
Los objetos se borrarán si MyFragment se vuelve a crear (el usuario va a la pantalla de inicio del dispositivo y luego abre la aplicación que se quedó en MyFragment). Puede retener mObjects enviando a MyFragment un paquete como argumentos.
ynnadkrap
1
Además, ¿cómo accede el método estático a las variables miembro no estáticas?
OrhanC1
2
@ynnadkrap Tienes razón, usar un paquete es el camino a seguir aquí.
Stefan Bogaard
2
@ OrhanC1 Según este código de ejemplo, el método estático no está accediendo a las variables miembro. La instancia de MyFragment está accediendo a sus miembros. No hay error aquí. Sin embargo, no recomiendo esta respuesta a nadie porque cuando el sistema operativo Android elimina su fragmento de la memoria para abrir algo de espacio, luego de reiniciar la actividad, este fragmento se creará con el constructor vacío predeterminado sin asignar variables ant.
Gunhan
@Gunhan Tienes razón! No es. Perdón por la confusión :)
OrhanC1