Meilleure pratique pour instancier un nouveau fragment Android

706

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 contient généralement 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 flexibilité 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 s'agit-il simplement d'une bonne pratique?

Graham Smith
la source
Lorsqu'il y a des paramètres, il n'y a pas de choix, et cela est largement répondu ici. Pourtant, la question demeure de la construction sans argument du fragment.
rds
1
Après avoir appris les modèles d'usine et comment une classe appelante qui n'instancie pas un objet lui-même aide à les découpler, j'ai pensé que ce serait un point fort pour la méthode newInstance (). Suis-je dans l'erreur? Je n'ai pas vu cet argument spécifique évoqué comme un avantage.
Applications mobiles du

Réponses:

1137

Si Android décide de recréer votre fragment plus tard, il appellera le constructeur sans argument de votre fragment. Surcharger le constructeur n'est donc pas une solution.

Cela étant dit, la façon de transmettre des éléments à votre fragment afin qu'ils soient disponibles après la recréation d'un fragment par Android est de passer un bundle à la setArgumentsméthode.

Ainsi, 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);

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

Notez également: setArgumentsne 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

yydl
la source
7
@Vlasto, malheureusement, les méthodes statiques ne peuvent pas être remplacées.
AJD
8
@yydl Je pense que je manque quelque chose ici, ne pourriez-vous pas utiliser un constructeur ici de toute façon, celui qui crée le bundle et appelle setArguments () encore car il ne sera appelé que par votre code (et pas quand Android recrée votre fragment)?
Mike Tunnicliffe
9
@mgibson Vous devez utiliser un ensemble si vous souhaitez que les données soient disponibles lors de la recréation ultérieure du fragment.
yydl
114
Être forcé de créer un constructeur sans argument pour les fragments est potentiellement le plus gros problème de toute la programmation, où que ce soit. Il force un changement de paradigme complet dans la création et l'initialisation d'objets. Si vous êtes nouveau sur Android et que vous êtes tombé sur ce fil, veuillez lire la réponse ci-dessus encore et encore et encore.
rmirabelle
9
Je contesterais cette affirmation. Premièrement, la sécurité des types est une préoccupation linguistique, pas une préoccupation de cadre. Deuxièmement, l'OMI, le cadre entre dans le domaine des "choses que votre API ne doit jamais faire". Si je veux passer la bibliothèque du congrès dans mon constructeur de fragments, alors je devrais être autorisé à le faire. Le contrat de constructeur «sans argument» tue essentiellement l'utilisation de l'injection de dépendance dans les fragments - beurk majeur.
rmirabelle
95

Le seul avantage à utiliser le newInstance()que je vois sont les suivants:

  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 pour fonctionner fidèlement (bien que vous devriez être capable de gérer les cas si aucun argument n'est groupé dans l'instance de fragment).

Donc, mon avis est que l'utilisation d'une statique newInstance()pour instancier un fragment est une bonne pratique.

500865
la source
4
1) En quoi est-ce différent de mettre la logique dans un constructeur? Les deux sont des endroits uniques où vous incluez cette logique. 2) En quoi les paramètres d'une usine statique sont-ils différents de ceux d'un constructeur? Les deux indiquent quels arguments sont attendus. Mon point étant que c'est un paradigme différent, bien sûr, mais il n'y a aucun avantage clair à cela sur l'utilisation de constructeurs.
RJ Cuthbertson
2
Vous ne pouvez pas utiliser de constructeurs personnalisés pour le fragment. Framework utilise le constructeur no argument pour restaurer des fragments.
500865
5
Oui, je suis d'accord avec vous là-bas. Je dis conceptuellement qu'il n'y a aucun avantage à utiliser le modèle d'usine statique au lieu d'utiliser des constructeurs surchargés, et vice versa. Vos deux points sont valables dans les deux modèles; il n'y a aucun avantage à utiliser l'un sur l'autre. Android vous oblige à utiliser le modèle d'usine statique - mais il n'y a aucun avantage à utiliser l'un ou l'autre.
RJ Cuthbertson
pastebin.com/EYJzES0j
RJ Cuthbertson
@RJCuthbertson Un avantage possible serait la possibilité de créer et de renvoyer des sous - classes de la classe de la méthode d'usine statique, c'est-à-dire de renvoyer une sous-classe appropriée pour la situation.
urgentx
62

Il existe également un autre moyen:

Fragment.instantiate(context, MyFragment.class.getName(), myBundle)
user1145201
la source
Si je ne me trompe pas, cela n'est possible que lorsque vous utilisez la bibliothèque de support Android.
Timo
2
J'ai essayé cela avec la bibliothèque de support, mais dans onCreateView (dans mon fragment), le paquet transmis était nul, alors j'ai choisi l'option setArguments / getArguments et cela a fonctionné (pour tous ceux qui lisent ceci).
Jrop
1
Intéressant, je n'ai jamais vu cette approche auparavant. At-il des avantages par rapport à d'autres approches pour instancier un fragment?
IgorGanapolsky
22
Du développeur docs ,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
Bien qu'ils aient mentionné la même chose que d'appeler un constructeur vide. "args.setClassLoader (f.getClass (). getClassLoader ());" est appelé en dessous pour les arguments du Bundle
Gökhan Barış Aker
49

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

Si Android décide de recréer votre fragment plus tard, il appellera le constructeur sans argument de votre fragment. Surcharger le constructeur n'est donc pas une solution.

il est tout à fait possible d'utiliser un constructeur . Pour voir pourquoi cela est, nous devons d'abord voir 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 arguments ) pour construire une instance du fragment. Ici, tout constructeur surchargé que vous écrivez sera ignoré, car Android ne peut pas savoir lequel utiliser.

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

Pour contourner ce problème, Android vous demande de stocker les données à l'aide d'un Bundle(appel setArguments()), auquel vous pourrez ensuite accéder YourFragment. Les arguments bundlesont protégés par Android et sont donc garantis persistants .

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

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 newInstanceméthode.

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

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

Pour 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 qu'Android demande que les arguments ne soient définis qu'à la construction et garantit qu'ils seront conservés.

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 constructeur par défaut sans argument . Donc, cela vous obligerait à définir un constructeur sans argument , qui est du code que vous pourriez éviter avec la newInstanceméthode d'usine.

EDIT : Android ne permet plus d'utiliser un constructeur surchargé pour les fragments. Vous devez utiliser la newInstanceméthode.

ps95
la source
Quand justifierait-on d'utiliser Android: configChanges = "orientation | keyboardHidden | screenSize"?
Luke Allison
1
Android Studio génère maintenant une erreur pour tous les constructeurs non par défaut dans les fragments, donc cela ne fonctionne plus.
Sheharyar
6
Mon Dieu, je me demande combien de développeurs de droïdes ont déjà écrit du code en dehors de droïde. C'est fou que nous ne puissions pas utiliser l'approche que vous décrivez. Il n'y a AUCUN argument convaincant d'aucun commentaire pour expliquer pourquoi nous devons utiliser la méthode d'usine statique. Il est encore plus troublant qu'ils soulèvent une erreur lors de la compilation. C'est certainement la meilleure réponse fournie et montre qu'il n'y a aucun avantage pour sfm.
MPavlak
3
Eh bien, il y a une raison subtile. Vous êtes libre de créer votre propre constructeur avec des arguments, mais il doit également y avoir un constructeur sans argument . Étant donné que les classes ont toujours un constructeur sans argument implicite, sauf si un constructeur avec args est explicitement défini , cela signifie que vous devez définir à la fois votre constructeur arg et un sans argument, sinon le système ne pourra pas invoquer constructeur sans arg. Je crois que c'est pourquoi la recommandation est d'utiliser à la place une méthode d'usine statique - elle réduit simplement le risque d'oublier de définir un constructeur sans argument.
JHH
@JHH qui échouera au moment de la compilation, donc pas un gros risque. Cependant, le problème ici est que la surcharge des constructeurs, un paradigme de programmation clé, est refusée par Android.
ps95
20

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

Si Android décide de recréer votre fragment plus tard, il appellera le constructeur sans argument de votre fragment. Surcharger le constructeur n'est donc pas une solution.

Je pense que c'est une solution et une bonne, c'est exactement la raison pour laquelle elle a été développée par le langage Java Core.

Il est vrai que le système Android peut détruire et recréer votre Fragment. Vous pouvez donc 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);
}

Il vous permettra de tirer someIntde getArguments()ce dernier, même s'il a Fragmentété recréé par le système. C'est une solution plus élégante que le staticconstructeur.

Pour moi, les staticconstructeurs sont inutiles et ne doivent pas être utilisés. De plus, ils vous limiteront si à l'avenir vous souhaitez étendre cela Fragmentet ajouter plus de fonctionnalités au constructeur. Avec le staticconstructeur, vous ne pouvez pas faire cela.

Mise à jour:

Android a ajouté une 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.

Ilya Gazman
la source
4
Un autre avantage d'avoir une méthode statique, que je n'ai pas mentionnée ci-dessus, c'est que vous ne pouvez pas accidentellement en définir des propriétés.
yydl
4
De plus, en ce qui concerne votre point sur "étendre ce fragment", cette méthode serait en fait très mauvaise si vous étendez la classe. Si vous appelez super, l'appel setArguments () ne sera efficace que pour l'enfant ou le parent, mais pas les deux!
yydl
2
@yydle vous pouvez éviter cette situation en appelant get arguments pour initialiser le Bundle enfant. Java toujours mieux.
Ilya Gazman
9
C'est vrai, mais c'est une autre raison d'encourager les gens à utiliser le modèle suggéré par Google. Bien sûr, nous convenons tous que votre solution est techniquement réalisable à 100%. De la même manière, il existe de nombreuses façons de faire beaucoup de choses. La question, cependant, est de savoir si c'est le meilleur. Et je pense fermement que l'utilisation du constructeur ne représente pas la vraie nature de la façon dont cela est censé fonctionner.
yydl
3
Je suis d'accord avec @yydl que la création statique est meilleure. Un autre avantage est l'injection de dépendance de nouvelles dépendances futures - le constructeur est mal adapté à cela et provoquera probablement plus de changement de code (ou plus de constructeurs à ajouter).
Boon
19

Certains 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"}
Rafols
la source
3

La meilleure pratique pour instance de fragments avec des arguments dans Android est d'avoir une méthode d'usine 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 devez éviter de définir vos champs avec l'instance d'un fragment. Parce que chaque fois que le système Android 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 arguments.

Vous pouvez trouver plus d'informations sur les meilleures pratiques pour instancier des fragments avec des arguments ici.

Gunhan
la source
2

Étant donné que les questions sur les meilleures pratiques, j'ajouterais, cette très bonne idée d'utiliser une approche hybride pour créer des fragments lorsque vous travaillez avec certains services Web REST

Nous ne pouvons pas passer des objets complexes, par exemple certains modèles utilisateur, pour le cas de l'affichage d'un fragment utilisateur

Mais ce que nous pouvons faire, c'est archiver onCreatecet utilisateur! = Null et sinon - l'amener de la couche de données, sinon - utiliser l'existant.

De cette façon, nous gagnons à la fois la capacité de recréer par userId en cas de recréation de fragments par Android et l'accrochage pour les actions de l'utilisateur, ainsi que la capacité de créer des fragments en se tenant à l'objet lui-même ou uniquement 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);
    }
}
Tigra
la source
3
Vous avez dit: "Nous ne pouvons pas passer des objets complexes, par exemple un modèle utilisateur," - Ce n'est pas vrai, nous le pouvons. Comme ceci: User user = /*...*/ mettez l'utilisateur dans le bundle: Bundle bundle = new Bundle(); bundle.putParcelable("some_user", user); et récupérez l'utilisateur à partir des arguments: User user = getArguments().getParcelable("some_user"); l'objet doit être implémenter l'interface Parcelable. link
Adam Varhegyi
3
Eh bien, oui, mais lorsque la classe est complexe et contient des références à d'autres objets ... Personnellement, je préfère rester simple, soit j'ai un objet, soit je n'en ai pas et je dois l'obtenir
Tigra
1

utiliser ce code 100% résoudre votre problème

entrez ce code dans firstFragment

public static yourNameParentFragment newInstance() {

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

cet échantillon envoie des données booléennes

et dans SecendFragment

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

la valeur du premier fragment doit être statique

code heureux

Amin Emadi
la source
0

La meilleure façon d'instancier le fragment est d'utiliser la méthode Fragment.instantiate par défaut ou de créer la méthode d'usine pour instancier le fragment
.

Mahesh
la source
0

Je suis ici dernièrement. Mais quelque chose que je viens de savoir pourrait vous aider un peu.

Si vous utilisez Java, il n'y a rien à changer. Mais pour les développeurs de kotlin, voici un extrait suivant, je pense, qui peut faire de vous un sous-sol sur lequel fonctionner:

  • Fragment parent:
inline fun <reified T : SampleFragment> newInstance(text: String): T {
    return T::class.java.newInstance().apply {
        arguments = Bundle().also { it.putString("key_text_arg", text) }
    }
}
  • Appel normal
val f: SampleFragment = SampleFragment.newInstance("ABC")
// or val f = SampleFragment.newInstance<SampleFragment>("ABC")
  • Vous pouvez étendre l'opération d'initialisation parent dans la classe de fragment enfant en:
fun newInstance(): ChildSampleFragment {
    val child = UserProfileFragment.newInstance<ChildSampleFragment>("XYZ")
    // Do anything with the current initialized args bundle here
    // with child.arguments = ....
    return child
}

Codage heureux.

vhfree
la source
-2

setArguments()est inutile. Cela ne fait qu'amener un gâchis.

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
la source
Sauf que vous venez d'être forcé de remplacer une autre méthode et de créer un champ qui aurait pu être isolé de la onViewCreatedportée. C'est pratique, je suppose, de nombreuses façons de faire la même chose. C'est aussi un moyen facile de vérifier les mises à jour effectuées par l'utilisateur (comparer les bundles de getArgumentset le bundle de onSaveInstanceState)
Overclover
@Asagen, j'aime votre commentaire sur la comparaison des valeurs initiales et des valeurs utilisateur. J'ai édité du code et je considère qu'il est toujours uniforme et clair sans getArgumentstrucs. Qu'en est-il de la onViewCreatedportée ... Nous pouvons restaurer le paquet d'état là-bas. Mais je préfère juste faire onCreateViewléger et rapide et faire toutes les initialisations lourdes à l'intérieur d'un onActivityCreatedparce que Fragment.getActivity()j'aime parfois revenir nullet à cause des onAttach()changements dans la nouvelle version de l'API 23.
Vadim Star
Tout ce que vous avez fait ici était mouvement set et get Argumentsdans saveInstanceState. Vous faites essentiellement la même chose que ce qui se fait "sous le capot"
OneCricketeer
1
@ cricket_007, ou juste en face . L'utilisation saveInstanceStateest "sous le capot". Et l'utilisation de Argumentsest une duplication de fonctionnalités qui vous fait vérifier: d'abord les Argumentsvaleurs puis les saveInstanceStatevaleurs. Parce que vous devez utiliser saveInstanceStaten'importe quel moyen. Qu'en est-il Arguments... ils ne sont pas nécessaires.
Vadim Star
Les arguments sont l'équivalent des extras d'intention pour les fragments. Ils ne sont pas inutiles, ils contiennent les paramètres initiaux qui sont différents de l'état actuel.
BladeCoder
-12

Je crois que j'ai une solution beaucoup plus simple pour cela.

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
la source
12
mObjects sera effacé s'il se trouve que MyFragment est recréé (l'utilisateur accède à l'écran d'accueil de l'appareil et ouvre ensuite l'application qui s'est arrêtée à MyFragment). Vous pouvez conserver les mObjects en envoyant un paquet à MyFragment comme arguments.
ynnadkrap du
1
De plus, comment la méthode statique accède-t-elle aux variables membres non statiques?
OrhanC1
2
@ynnadkrap Vous avez raison, utiliser un bundle est le chemin à parcourir ici.
Stefan Bogaard
2
@ OrhanC1 Selon cet exemple de code, la méthode statique n'accède pas aux variables membres. L'instance de MyFragment accède à ses membres. Il n'y a pas d'erreur ici. Cependant, je ne recommande cette réponse à personne car lorsque votre fragment est supprimé de la mémoire pour ouvrir de l'espace par le système d'exploitation Android, puis après le redémarrage de l'activité et ce fragment sera créé avec le constructeur vide par défaut sans affecter de variables ant.
Gunhan
@Gunhan Vous avez raison! Ce n'est pas. Désolé pour la confusion :)
OrhanC1