283 votes

Les fragments ont-ils vraiment besoin d'un constructeur vide ?

J'ai un fragment avec un constructeur avec de multiples arguments, tout a bien fonctionné pendant la phase de test mais maintenant, après qu'environ 300 utilisateurs aient téléchargé l'application, j'ai UNE occurrence de cette exception :

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

Je veux dire que je pourrais fournir un Constructeur différent mais cela n'a pas beaucoup de sens puisque je devrais alors appeler une autre méthode pour vraiment configurer le Fragment.

Je suis curieux de savoir pourquoi cela se produit sporadiquement et pas toujours. Peut-être que j'utilise mal le Fragmented Viewpager, car j'instancie moi-même tous les fragments et les enregistre dans une liste à l'intérieur de l'activité. Je n'utilise pas la transaction FragmentManager, car l'exemple sur le Fragmented Viewpager n'était pas très clair à ce sujet et, au final, tout a bien fonctionné.

25 votes

Dans certaines versions d'Android (au moins ICS), vous pouvez aller dans Paramètres -> Options du développeur et activer "Ne pas conserver les activités". Cela vous donnera un moyen déterministe de tester les cas où un constructeur sans argument est nécessaire.

0 votes

J'ai eu le même problème. J'assignais les données du bundle à la place des variables membres (en utilisant un ctor non par défaut). Mon programme ne se plantait pas lorsque je tuais l'application - cela se produisait uniquement lorsque le planificateur mettait mon application en veilleuse pour "gagner de la place". J'ai découvert cela en allant dans Task Mgr et en ouvrant une tonne d'autres applications, puis en rouvrant mon application en mode débogage. Elle a planté à chaque fois. Le problème a été résolu lorsque j'ai utilisé la réponse de Chris Jenkins pour utiliser les bundle args.

0 votes

Ce fil de discussion pourrait vous intéresser : stackoverflow.com/questions/15519214/

373voto

Chris.Jenkins Points 5261

Oui, ils le font.

Vous ne devriez pas vraiment surcharger le constructeur de toute façon. Vous devriez avoir un newInstance() méthode statique définie et passage de tout paramètre via les arguments (bundle)

Par exemple :

public static final AlertDialogFragment newInstance(int title, String message)
{
    AlertDialogFragment f = new AlertDialogFragment();
    Bundle bdl = new Bundle(2);
    bdl.putInt(EXTRA_TITLE, title);
    bdl.putString(EXTRA_MESSAGE, message);
    f.setArguments(bdl);
    return f;
}

Et bien sûr, en récupérant les arguments de cette façon :

@Override
public Dialog onCreate(Bundle savedInstanceState)
{
    title = getArguments().getInt(EXTRA_TITLE);
    message = getArguments().getString(EXTRA_MESSAGE);

    //...
    //etc
    //...
}

Ainsi, en cas de détachement et de rattachement, l'état de l'objet peut être stocké dans les arguments. Un peu comme les bundles attachés aux Intents.

Motif - Lecture supplémentaire

J'ai pensé que je pourrais expliquer pourquoi pour les personnes qui se demandent pourquoi.

Si vous vérifiez : https://Android.googlesource.com/platform/frameworks/support/+/refs/heads/master/v4/java/Android/support/v4/app/Fragment.java

Vous verrez le instantiate(..) dans la méthode Fragment appelle la classe newInstance méthode. [http://docs.oracle.com/javase/6/docs/api/java/lang/Class.html#newInstance()](http://docs.oracle.com/javase/6/docs/api/java/lang/Class.html#newInstance()) Explique pourquoi, lors de l'instanciation, il vérifie que l'accesseur est public et que ce chargeur de classe lui permette d'y accéder.

C'est une méthode assez désagréable dans l'ensemble, mais elle permet à la FragmentManger pour tuer et recréer Fragments avec des états. (Le sous-système Android fait des choses similaires avec Activities ).

Exemple de classe

On me demande souvent d'appeler newInstance (Ne confondez pas cette méthode avec la méthode de classe. L'exemple de la classe entière devrait montrer l'utilisation.

/**
 * 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...
}

0 votes

J'ai eu de sérieux défauts de conception dans mon fragment :) j'aimerais quand même savoir quand et pourquoi le framework déclenche l'instanciation des fragments par lui-même, puisque je ne les utilise pas dans un fichier de mise en page et qu'ils sont maintenus dans une liste à l'intérieur de l'activité, donc ils devraient être vivants jusqu'à ce que l'activité soit détruite.

3 votes

Si vous interrompez l'activité ou la détruisez. Donc vous allez sur l'écran d'accueil et l'activité est alors détruite par Android pour gagner de la place. L'état des fragments sera sauvegardé (en utilisant les args) puis l'objet sera récupéré (normalement). Ainsi, au retour de l'activité, les fragments devraient essayer d'être recréés en utilisant l'état sauvegardé, new Default() puis onCreate etc... De plus, si l'activité essaie d'économiser des ressources (téléphone à faible mémoire), elle peut supprimer les objets qui viennent d'être pausés... Commonsguy devrait être en mesure de mieux expliquer. En bref, vous ne savez pas ! :)

0 votes

Le seul problème que j'ai avec cette conception, cependant, @Chris.Jenkins est quand vous avez besoin d'un grand objet ou d'une liste d'objets dans votre fragment que vous fournissez habituellement dans votre constructeur. J'ai l'intention de faire une remarque, mais si vous pouviez peut-être partager vos idées, je vous en serais reconnaissant.

19voto

JesperB Points 1804

Comme l'a noté CommonsWare dans cette question http://stackoverflow.com/a/16064418/1319061 Cette erreur peut également se produire si vous créez une sous-classe anonyme d'un fragment, car les classes anonymes ne peuvent pas avoir de constructeurs.

Ne faites pas de sous-classes anonymes de Fragment :-)

1 votes

Ou, comme CommonsWare l'a mentionné dans cet article, assurez-vous de déclarer une Activité/Fragment/Récepteur interne comme "statique" pour éviter cette erreur.

8voto

Oui, comme vous pouvez le voir, le package de support instancie également les fragments (lorsqu'ils sont détruits et réouverts). Vos sous-classes de fragments ont besoin d'un constructeur public vide car c'est ce qui est appelé par le framework.

0 votes

Le constructeur de fragments vides doit-il appeler le constructeur super() ou non ? Je pose cette question car je sais que le constructeur public vide est obligatoire. Si l'appel à super() n'a pas de sens pour un constructeur public vide

0 votes

@TNR car toutes les abstractions de fragments ont un constructeur vide. super() serait inutile, car la classe parente a enfreint la règle du constructeur public vide. Donc non, vous n'avez pas besoin de passer super() dans votre constructeur.

4 votes

En fait, il n'est pas nécessaire de définir explicitement un constructeur vide dans un fragment. Chaque classe Java possède de toute façon un constructeur implicite par défaut. Tiré de : docs.oracle.com/javase/tutorial/java/javaOO/constructeurs.html ~ "Le compilateur fournit automatiquement un constructeur par défaut, sans argument, pour toute classe sans constructeur."

1voto

becke-ch Points 11

Jetez un coup d'œil à la documentation officielle : Fragment : https://developer.Android.com/reference/Android/app/Fragment

Toutes les sous-classes de Fragment doivent inclure un constructeur public sans argument. Le framework ré-instanciera souvent une classe de fragment en cas de besoin, notamment lors de la restauration de l'état, et doit être capable de trouver ce constructeur pour l'instancier. Si le constructeur sans argument n'est pas disponible, une exception d'exécution se produira dans certains cas pendant la restauration d'état.

-8voto

Alecs Points 21

Voici ma solution simple :

1 - Définissez votre fragment

public class MyFragment extends Fragment {

    private String parameter;

    public MyFragment() {
    }

    public void setParameter(String parameter) {
        this.parameter = parameter;
    } 
}

2 - Créez votre nouveau fragment et remplissez le paramètre

    myfragment = new MyFragment();
    myfragment.setParameter("here the value of my parameter");

3 - Profitez-en !

Vous pouvez évidemment modifier le type et le nombre de paramètres. C'est simple et rapide.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X