Best practice per creare un'istanza di un nuovo frammento Android


706

Ho visto due pratiche generali per creare un'istanza di un nuovo frammento in un'applicazione:

Fragment newFragment = new MyFragment();

e

Fragment newFragment = MyFragment.newInstance();

La seconda opzione fa uso di un metodo statico newInstance()e generalmente contiene il seguente metodo.

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

Inizialmente, ho pensato che il vantaggio principale fosse il fatto che avrei potuto sovraccaricare il metodo newInstance () per dare flessibilità durante la creazione di nuove istanze di un frammento, ma potevo anche farlo creando un costruttore sovraccaricato per il frammento.

Ho dimenticato qualcosa?

Quali sono i vantaggi di un approccio rispetto all'altro? O è solo una buona pratica?


Quando ci sono parametri, non c'è scelta, e qui si trova un'ampia risposta. Tuttavia, rimane la domanda per la costruzione senza discussione del frammento.
rd

1
Dopo aver appreso i modelli di fabbrica e in che modo una classe chiamante che non crea un'istanza di un oggetto stesso aiuta a dissociarli, ho pensato che questo sarebbe stato un punto di forza per il metodo newInstance (). Mi sbaglio su questo? Non ho visto questo argomento specifico menzionato come un vantaggio.
Applicazioni mobili

Risposte:


1137

Se Android decide di ricreare il tuo frammento in un secondo momento, chiamerà il costruttore senza argomenti del tuo frammento. Quindi sovraccaricare il costruttore non è una soluzione.

Detto questo, il modo per passare le cose al tuo frammento in modo che siano disponibili dopo che un frammento è stato ricreato da Android è quello di passare un bundle al setArgumentsmetodo.

Quindi, ad esempio, se volessimo passare un intero al frammento, utilizzeremmo qualcosa del tipo:

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

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

    return myFragment;
}

E più tardi nel frammento onCreate()puoi accedere a quell'intero usando:

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

Questo pacchetto sarà disponibile anche se il frammento viene in qualche modo ricreato da Android.

Nota anche: setArgumentspuò essere chiamato solo prima che il frammento sia attaccato all'attività.

Questo approccio è documentato anche nel riferimento per gli sviluppatori Android: https://developer.android.com/reference/android/app/Fragment.html


7
@Vlasto purtroppo i metodi statici non possono essere ignorati.
AJD,

8
@yydl Penso che mi manchi qualcosa qui, non potresti comunque usare un costruttore qui, uno che crea il Bundle e chiama setArguments () ancora dal momento che verrà chiamato solo dal tuo codice (e non quando Android ricrea il tuo frammento)?
Mike Tunnicliffe,

9
@mgibson È necessario utilizzare un pacchetto se si desidera che i dati siano disponibili quando il frammento viene successivamente ricreato.
yydl,

114
Essere costretti a creare un costruttore senza argomenti per i frammenti è potenzialmente il più grande gotcha in tutta la programmazione, ovunque. Forza un cambio di paradigma completo nella creazione e inizializzazione degli oggetti. Se sei nuovo su Android e ti sei imbattuto in questa discussione, leggi la risposta sopra più e più volte.
rmirabelle,

9
Discuterei con questa affermazione. Innanzitutto, la sicurezza del tipo è un problema linguistico, non un problema di struttura. In secondo luogo, IMO, il framework sta entrando nell'area delle "cose ​​che la tua API non deve mai fare". Se voglio passare la biblioteca del congresso nel mio costruttore di frammenti, allora dovrei essere autorizzato a farlo. Il contratto di costruzione "no-args" in sostanza uccide l'uso dell'iniezione di dipendenza in frammenti - grande schifo.
rmirabelle,

95

L'unico vantaggio nell'uso di quello newInstance()che vedo sono i seguenti:

  1. Avrai un unico posto in cui tutti gli argomenti usati dal frammento potrebbero essere raggruppati e non dovrai scrivere il codice qui sotto ogni volta che crei un'istanza di un frammento.

    Bundle args = new Bundle();
    args.putInt("someInt", someInt);
    args.putString("someString", someString);
    // Put any other arguments
    myFragment.setArguments(args);
  2. È un buon modo per dire ad altre classi quali argomenti si aspetta che funzionino fedelmente (anche se dovresti essere in grado di gestire i casi se nessun argomento è raggruppato nell'istanza del frammento).

Quindi, la mia opinione è che usare una statica newInstance()per creare un'istanza di un frammento sia una buona pratica.


4
1) In che modo è diverso dal mettere la logica in un costruttore? Entrambi sono singoli posti in cui includi questa logica. 2) In che modo i parametri di una fabbrica statica sono diversi dai parametri di un costruttore? Entrambi dicono quali argomenti sono previsti. Il mio punto di vista è un paradigma diverso, certo, ma non ci sono chiari vantaggi rispetto all'uso dei costruttori.
RJ Cuthbertson,

2
Non è possibile utilizzare costruttori personalizzati per il frammento. Framework utilizza il costruttore no argomento per ripristinare i frammenti.
500865

5
Sì, sono d'accordo con te lì. Sto concettualmente dicendo che non c'è alcun vantaggio nell'usare il modello statico di fabbrica invece di usare costruttori sovraccarichi, e viceversa. Entrambi i tuoi punti sono validi in entrambi i modelli; non vi è alcun vantaggio nell'utilizzare l'uno rispetto all'altro. Android ti costringe a utilizzare il modello statico di fabbrica, ma non vi è alcun vantaggio nell'utilizzare l'uno o l'altro.
RJ Cuthbertson,

pastebin.com/EYJzES0j
RJ Cuthbertson

@RJCuthbertson Un possibile vantaggio sarebbe la possibilità di creare e restituire sottoclassi della classe del metodo factory statico, ovvero restituire una sottoclasse appropriata per la situazione.
urgentx,

62

C'è anche un altro modo:

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

Se non sbaglio, questo è possibile solo quando si utilizza la libreria di supporto Android.
Timo

2
Ho provato questo con la libreria di supporto, ma in onCreateView (nel mio frammento), il bundle passato era null, quindi sono andato con l'opzione setArguments / getArguments e ha funzionato (per chiunque leggesse questo).
Jrop,

1
Interessante, non ho mai visto questo approccio prima. Ha qualche vantaggio rispetto ad altri approcci all'istanza di un frammento?
IgorGanapolsky,

22
Dai documenti dello sviluppatore ,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
Anche se hanno menzionato lo stesso che chiamare il costruttore vuoto. "args.setClassLoader (f.getClass () getClassLoader ().);" viene chiamato sotto per gli argomenti del Bundle
Gökhan Barış Aker

49

Mentre @yydl fornisce un motivo convincente sul perché il newInstancemetodo è migliore:

Se Android decide di ricreare il tuo frammento in un secondo momento, chiamerà il costruttore senza argomenti del tuo frammento. Quindi sovraccaricare il costruttore non è una soluzione.

è ancora del tutto possibile usare un costruttore . Per capire perché, innanzitutto dobbiamo vedere perché la soluzione sopra descritta viene utilizzata da Android.

Prima di poter utilizzare un frammento, è necessaria un'istanza. Chiamate Android YourFragment()( nessun argomento costruttore ) per costruire un'istanza del frammento. Qui qualsiasi costruttore sovraccarico che scrivi verrà ignorato, poiché Android non può sapere quale utilizzare.

Nel corso della vita di un'attività il frammento viene creato come sopra e distrutto più volte da Android. Ciò significa che se si inseriscono dati nell'oggetto frammento stesso, questi verranno persi una volta che il frammento viene distrutto.

Per ovviare al problema, Android chiede di archiviare i dati utilizzando un Bundle(chiamante setArguments()), a cui è possibile accedere da YourFragment. Gli argomenti bundlesono protetti da Android e quindi sono garantiti come persistenti .

Un modo per impostare questo pacchetto è utilizzando un newInstancemetodo statico :

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

Tuttavia, un costruttore:

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

può fare esattamente la stessa cosa del newInstancemetodo.

Naturalmente, ciò fallirebbe ed è uno dei motivi per cui Android vuole che tu usi il newInstancemetodo:

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

Come ulteriore spiegazione, ecco la classe dei frammenti di 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;
}

Si noti che Android chiede che gli argomenti siano impostati solo durante la costruzione e garantisce che questi verranno conservati.

EDIT : Come sottolineato nei commenti di @JHH, se stai fornendo un costruttore personalizzato che richiede alcuni argomenti, Java non fornirà al tuo frammento un costruttore predefinito senza arg . Quindi questo richiederebbe di definire un costruttore no arg , che è il codice che potresti evitare con il newInstancemetodo factory.

EDIT : Android non consente più l'utilizzo di un costruttore sovraccaricato per i frammenti. È necessario utilizzare il newInstancemetodo


Quando si giustificherebbe l'utilizzo di Android: configChanges = "orientamento | keyboardHidden | screenSize"?
Luke Allison,

1
Android Studio ora genera un errore per tutti i costruttori non predefiniti in frammenti, quindi non funziona più.
Sheharyar,

6
Santo uomo, mi chiedo quanti sviluppatori di droidi abbiano mai scritto codice al di fuori di droide. Questo è folle che non possiamo usare l'approccio che descrivi. Non ci sono argomenti convincenti da alcun commento sul perché dovremmo usare il metodo factory statico. È ancora più inquietante che possano generare un errore durante la compilazione. Questa è sicuramente la migliore risposta fornita e dimostra che sfm non ha alcun vantaggio.
MPavlak,

3
Bene, c'è una ragione sottile. Sei libero di creare il tuo costruttore con argomenti, ma ci deve essere anche un costruttore senza argomenti . Dal momento che le classi hanno sempre un costruttore no-arg implicita a meno che un costruttore con args è esplicitamente definito , questo significa che si dovrebbero definire sia la vostra arg costruttore e un costruttore no-arg in modo esplicito, o il sistema non sarebbe in grado di richiamare qualsiasi costruttore no-arg. Credo che questo sia il motivo per cui la raccomandazione è invece quella di utilizzare un metodo statico di fabbrica - riduce semplicemente il rischio di dimenticare di definire un costruttore no-arg.
JHH,

@JHH che fallirà al momento della compilazione, quindi non è un grosso rischio. Tuttavia, il problema qui è che il sovraccarico del costruttore, un paradigma di programmazione chiave, viene negato da Android.
ps95,

20

Non sono d' accordo con la risposta yydi dicendo:

Se Android decide di ricreare il tuo frammento in un secondo momento, chiamerà il costruttore senza argomenti del tuo frammento. Quindi sovraccaricare il costruttore non è una soluzione.

Penso che sia una soluzione valida, questa è esattamente la ragione per cui è stata sviluppata dal linguaggio centrale Java.

È vero che il sistema Android può distruggere e ricreare il tuo Fragment. Quindi puoi farlo:

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

Ti permetterà di tirare someIntda getArguments()quest'ultimo in poi, anche se Fragmentricreato dal sistema. Questa è una soluzione più elegante del staticcostruttore.

Secondo me i staticcostruttori sono inutili e non dovrebbero essere usati. Inoltre ti limiteranno se in futuro desideri estenderlo Fragmente aggiungere più funzionalità al costruttore. Con il staticcostruttore non puoi farlo.

Aggiornare:

Android ha aggiunto l'ispezione che contrassegna tutti i costruttori non predefiniti con un errore.
Consiglio di disabilitarlo, per i motivi sopra menzionati.


4
Un altro vantaggio di avere un metodo statico, che non ho menzionato sopra è che non è possibile impostare accidentalmente proprietà da esso.
yydl,

4
Inoltre, per quanto riguarda il punto su "estendi questo frammento", questo metodo sarebbe davvero pessimo se estendi la classe. Chiamare super farà sì che la chiamata setArguments () sia efficace solo per il figlio o il genitore, ma non entrambi!
yydl,

2
@yydle puoi evitare questa situazione chiamando get argomenti per inizializzare il pacchetto figlio. Modo Java sempre migliore.
Ilya Gazman,

9
È vero, ma questo è un altro motivo per incoraggiare le persone a utilizzare il modello suggerito da Google. Naturalmente, siamo tutti d'accordo sul fatto che la tua soluzione è tecnicamente fattibile al 100%. Allo stesso modo ci sono molti modi per fare molte cose. La domanda, tuttavia, è se è la migliore. E sento fortemente che l'uso del costruttore non rappresenta la vera natura di come dovrebbe funzionare.
yydl,

3
Sono d'accordo con @yydl che la creazione statica è migliore. Un altro vantaggio è l'iniezione di dipendenze di nuove dipendenze future - il costruttore non è adatto a ciò e probabilmente causerà più modifiche al codice (o più costruttori da aggiungere).
Boon,

19

Alcuni codici kotlin :

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

E puoi ottenere argomenti con questo:

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

3

La migliore pratica per istanziare frammenti con argomenti in Android è di avere un metodo factory statico nel tuo frammento.

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

Dovresti evitare di impostare i campi con l'istanza di un frammento. Perché ogni volta che il sistema Android ricrea il frammento, se ritiene che il sistema abbia bisogno di più memoria, allora ricrea il frammento utilizzando il costruttore senza argomenti.

Puoi trovare maggiori informazioni sulle migliori pratiche per creare un'istanza di frammenti con argomenti qui.


2

Dopo le domande sulle migliori pratiche, aggiungerei, molto spesso una buona idea di utilizzare un approccio ibrido per creare frammenti quando si lavora con alcuni servizi Web REST

Non possiamo trasmettere oggetti complessi, ad esempio un modello utente, nel caso di visualizzazione di frammenti utente

Ma quello che possiamo fare è fare il check-in onCreate quell'utente! = Null e, in caso contrario, portarlo dal livello dati, altrimenti - usare esistente.

In questo modo otteniamo sia la capacità di ricreare dall'utenteId in caso di ricreazione di frammenti di Android e la reattività per le azioni dell'utente, sia la capacità di creare frammenti mantenendo l'oggetto stesso o solo il suo ID

Qualcosa di simile a questo:

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

3
Hai detto: "Non possiamo passare oggetti complessi, ad esempio un modello utente" - Non è vero, possiamo. In questo modo: User user = /*...*/ metti l'utente nel bundle: Bundle bundle = new Bundle(); bundle.putParcelable("some_user", user); e ottieni l'utente dagli argomenti: User user = getArguments().getParcelable("some_user"); l'oggetto deve essere implementato come interfaccia parcelable. link
Adam Varhegyi,

3
Beh, sì, ma quando la classe è complessa e contiene referrer per altri oggetti ... Personalmente preferisco mantenerlo semplice, o ho un oggetto, o non lo faccio e quindi devo ottenerlo
Tigra

1

usa questo codice al 100% per risolvere il tuo problema

inserisci questo codice in firstFragment

public static yourNameParentFragment newInstance() {

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

questo esempio invia dati booleani

e in SecendFragment

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

il valore deve nel primo frammento è statico

codice felice


0

Il modo migliore per creare un'istanza del frammento è utilizzare il metodo Fragment.instantiate predefinito o creare un metodo factory per creare un'istanza del frammento
Attenzione: creare sempre un costruttore vuoto nel frammento altro mentre il ripristino della memoria del frammento genererà un'eccezione di runtime.


0

Sono di recente qui. Ma alcune cose che ho appena saputo potrebbero aiutarti un po '.

Se stai usando Java, non c'è molto da cambiare. Ma per gli sviluppatori di kotlin, ecco alcuni frammenti seguenti che penso possano farti diventare un seminterrato:

  • Frammento principale:
inline fun <reified T : SampleFragment> newInstance(text: String): T {
    return T::class.java.newInstance().apply {
        arguments = Bundle().also { it.putString("key_text_arg", text) }
    }
}
  • Chiamata normale
val f: SampleFragment = SampleFragment.newInstance("ABC")
// or val f = SampleFragment.newInstance<SampleFragment>("ABC")
  • È possibile estendere l'operazione init padre nella classe di frammento figlio:
fun newInstance(): ChildSampleFragment {
    val child = UserProfileFragment.newInstance<ChildSampleFragment>("XYZ")
    // Do anything with the current initialized args bundle here
    // with child.arguments = ....
    return child
}

Buona codifica.


-2

setArguments()è inutile. Porta solo un casino.

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

Tranne che sei appena stato costretto a scavalcare un altro metodo e creare un campo che avrebbe potuto essere isolato onViewCreatednell'ambito. È convenienza immagino, molti modi per fare la stessa cosa. Inoltre è un modo semplice per verificare la presenza di aggiornamenti effettuati dall'utente (confronta i pacchetti di getArgumentse il pacchetto da onSaveInstanceState)
Overclover

@Asagen, mi piace il tuo commento sul confronto tra i valori iniziali e quelli dell'utente. Ho modificato il codice e ritengo che sia ancora uniforme e chiaro senza getArgumentsroba. Che dire onViewCreateddell'ambito ... Possiamo ripristinare il bundle di stato lì. Ma preferisco solo rendere onCreateViewleggero e veloce e fare tutte le inizializzazioni pesanti all'interno di un onActivityCreatedperché a Fragment.getActivity()volte piace tornare nulle a causa dei onAttach()cambiamenti nella nuova versione di API 23.
Vadim Star,

Tutto quello che hai fatto qui è stato muoverti set ed get Argumentsentrare saveInstanceState. In pratica stai facendo la stessa cosa "sotto il cofano"
OneCricketeer,

1
@ cricket_007, o proprio di fronte . L'uso saveInstanceStateè "sotto il cofano". E l'utilizzo di Argumentsè la duplicazione di funzionalità che ti fanno ricontrollare: prima i Argumentsvalori e poi i saveInstanceStatevalori. Perché devi usare in saveInstanceStatequalsiasi modo. Che dire Arguments... non sono necessari.
Vadim Star,

Gli argomenti sono l'equivalente degli extra Intent per i frammenti. Non sono inutili, contengono i parametri iniziali che sono diversi dallo stato corrente.
BladeCoder

-12

Credo di avere una soluzione molto più semplice per questo.

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

12
mObject verrà cancellato se MyFragment viene ricreato (l'utente accede alla schermata principale del dispositivo e successivamente apre l'app che è stata interrotta su MyFragment). Puoi conservare mObjects inviando MyFragment un pacchetto come argomenti.
ynnadkrap,

1
Inoltre, in che modo il metodo statico accede alle variabili membro non statiche?
OrhanC1,

2
@ynnadkrap Hai ragione, usare un bundle è la strada da percorrere qui.
Stefan Bogaard,

2
@ OrhanC1 Secondo questo codice di esempio, il metodo statico non accede alle variabili membro. L'istanza di MyFragment sta accedendo ai suoi membri. Non ci sono errori qui. Tuttavia, non consiglio questa risposta a nessuno perché quando il frammento viene rimosso dalla memoria per aprire un po 'di spazio dal sistema operativo Android, dopo aver riavviato l'attività e questo frammento verrà creato con il costruttore vuoto predefinito senza assegnare variabili di formica.
Gunhan,

@Gunhan Hai ragione! Non è.
Ci
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.