web-dev-qa-db-fra.com

Méthode recommandée pour instancier un nouveau fragment Android

J'ai vu deux pratiques générales pour instancier un nouveau fragment dans une application:

Fragment newFragment = new MyFragment();

et

Fragment newFragment = MyFragment.newInstance();

La deuxième option utilise une méthode statique newInstance() et généralement contient la méthode suivante.

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

Au début, je pensais que le principal avantage était le fait que je pouvais surcharger la méthode newInstance () pour donner de la souplesse lors de la création de nouvelles instances d'un fragment - mais je pouvais également le faire en créant un constructeur surchargé pour le fragment.

Ai-je oublié quelque chose?

Quels sont les avantages d'une approche par rapport à l'autre? Ou est-ce juste une bonne pratique?

665
Graham Smith

Si Android décide de recréer votre fragment ultérieurement, il appellera le constructeur sans argument de votre fragment. Donc, surcharger le constructeur n'est pas une solution.

Cela dit, le moyen de transmettre des éléments à votre fragment afin qu’ils soient disponibles une fois le fragment reconstitué par Android consiste à transmettre un ensemble à la méthode setArguments.

Donc, par exemple, si nous voulions passer un entier au fragment, nous utiliserions quelque chose comme:

public static MyFragment newInstance(int someInt) {
    MyFragment myFragment = new MyFragment();

    Bundle args = new Bundle();
    args.putInt("someInt", someInt);
    myFragment.setArguments(args);

    return myFragment;
}

Et plus tard dans le fragment onCreate(), vous pouvez accéder à cet entier en utilisant:

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

Cet ensemble sera disponible même si le fragment est en quelque sorte recréé par Android.

Notez également que setArguments ne peut être appelé que lorsque le fragment est attaché à l'activité.

Cette approche est également documentée dans la référence du développeur Android: https://developer.Android.com/reference/Android/app/Fragment.html

1068
yydl

Le seul avantage à utiliser la newInstance() que je vois est le suivant:

  1. Vous aurez un seul endroit où tous les arguments utilisés par le fragment pourraient être regroupés et vous n'avez pas à écrire le code ci-dessous à chaque fois que vous instanciez un fragment.

    Bundle args = new Bundle();
    args.putInt("someInt", someInt);
    args.putString("someString", someString);
    // Put any other arguments
    myFragment.setArguments(args);
    
  2. C’est un bon moyen de dire aux autres classes quels arguments il attend doit travailler fidèlement (bien que vous puissiez être capable de gérer les cas si aucun argument n’est groupé dans l’instance de fragment).

Donc, ma conclusion est que l’utilisation d’une newInstance() statique pour instancier un fragment est une bonne pratique.

95
500865

Il y a aussi un autre moyen:

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

Alors que @yydl donne une raison convaincante pour laquelle la méthode newInstance est meilleure:

Si Android décide de recréer votre fragment ultérieurement, il appellera le constructeur sans argument de votre fragment. Donc, surcharger le constructeur n'est pas une solution.

il est encore possible d'utiliser un constructeur . Pour savoir pourquoi, il faut d’abord comprendre pourquoi la solution de contournement ci-dessus est utilisée par Android.

Avant qu'un fragment puisse être utilisé, une instance est nécessaire. Android appelle YourFragment() (le constructeur sans argument) pour construire une instance du fragment. Ici, tout constructeur surchargé que vous écrivez sera ignoré, car Android ne sait pas lequel utiliser.

Dans la vie d'une activité, le fragment est créé comme ci-dessus et détruit à plusieurs reprises par Android. Cela signifie que si vous mettez des données dans l'objet fragment lui-même, elles seront perdues une fois le fragment détruit.

Pour résoudre ce problème, Android vous demande de stocker des données à l'aide de Bundle (appelant setArguments()), accessible à partir de YourFragment. Les arguments bundles sont protégés par Android et sont donc garantis persistants.

Une façon de définir cet ensemble consiste à utiliser une méthode statique newInstance:

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;
}

Cependant, un constructeur:

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

peut faire exactement la même chose que la méthode newInstance.

Naturellement, cela échouerait et c’est l’une des raisons pour lesquelles Android souhaite que vous utilisiez la méthode newInstance:

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

Comme plus d'explications, voici la classe de fragments d'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;
}

Notez que Android demande que les arguments soient définis niquement lors de la construction et garantit leur conservation.

EDIT: Comme indiqué dans les commentaires de @JHH, si vous fournissez un constructeur personnalisé qui nécessite des arguments, alors Java ne fournira pas votre fragment avec un no arg constructeur par défaut. Vous devrez donc définir un constructeur no arg , code que vous pouvez éviter avec la méthode de fabrique newInstance.

EDIT: Android n'autorise plus l'utilisation d'un constructeur surchargé pour les fragments. Vous devez utiliser la méthode newInstance.

41
ps95

Je ne suis pas d'accord avec yydi réponse en disant:

Si Android décide de recréer votre fragment ultérieurement, il appellera le constructeur sans argument de votre fragment. Donc, surcharger le constructeur n'est pas une solution.

Je pense que c’est une solution et une bonne solution, c’est précisément la raison pour laquelle il a été développé par Java langage principal.

Il est vrai que le système Android peut détruire et recréer votre Fragment. Donc, vous pouvez faire ceci:

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);
}

Cela vous permettra de tirer someInt de getArguments() cette dernière, même si le Fragment a été recréé par le système. C'est une solution plus élégante que le constructeur static.

À mon avis, static les constructeurs sont inutiles et ne doivent pas être utilisés. En outre, ils vous limiteront si à l'avenir vous souhaitez étendre cette Fragment et ajouter plus de fonctionnalités au constructeur. Avec le constructeur static, vous ne pouvez pas faire cela.

Mise à jour:

Android a ajouté l'inspection qui marque tous les constructeurs non par défaut avec une erreur.
Je recommande de le désactiver, pour les raisons mentionnées ci-dessus.

17
Ilya Gazman

Quelques kotlin code:

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

Et vous pouvez obtenir des arguments avec ceci:

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

La meilleure pratique pour occulter des fragments avec des arguments dans Android consiste à avoir une méthode fabrique statique dans votre fragment.

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;
}

Vous devriez éviter de définir vos champs avec l'instance d'un fragment. Parce que chaque fois que Android système recrée votre fragment, s'il estime que le système a besoin de plus de mémoire, il recréera votre fragment en utilisant un constructeur sans argument.

Vous pouvez trouver plus d’informations sur meilleure pratique pour instancier des fragments avec des arguments ici.

3
Gunhan

Depuis les questions sur les meilleures pratiques, j’ajouterais que c'est très souvent une bonne idée d'utiliser une approche hybride pour créer des fragments lorsque vous travaillez avec certains REST services Web.

Nous ne pouvons pas transmettre d’objets complexes, par exemple certains modèles d’utilisateur, pour afficher le fragment d’utilisateur

Mais ce que nous pouvons faire, c’est d’enregistrer onCreate cet utilisateur! = Null et si ce n’est pas le cas, amenez-le de la couche de données, sinon, utilisez-le.

De cette façon, nous obtenons à la fois la possibilité de recréer par userId en cas de recréation de fragment par Android et la vivacité pour les actions de l'utilisateur, ainsi que la possibilité de créer des fragments en conservant l'objet seul ou son identifiant.

Quelque chose aime ça:

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);
    }
}
2
Tigra

Le meilleur moyen d’instancier le fragment est d’utiliser la méthode par défaut Fragment.instantiate ou de créer une fabrique pour instancier le fragment.
Attention: toujours créer un constructeur vide dans un fragment, alors que la restauration de la mémoire de fragment lèvera une exception d'exécution.

0
Mahesh

setArguments() est inutile. Cela n'amène qu'un désordre.

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");
        } 
        ...
    }
}
0
Vadim Star