Prática recomendada para instanciar um novo fragmento Android

706

Vi duas práticas gerais para instanciar um novo fragmento em um aplicativo:

Fragment newFragment = new MyFragment();

e

Fragment newFragment = MyFragment.newInstance();

A segunda opção utiliza um método estático newInstance()e geralmente contém o seguinte método.

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

No começo, pensei que o principal benefício era o fato de poder sobrecarregar o método newInstance () para dar flexibilidade ao criar novas instâncias de um fragmento - mas também poderia fazer isso criando um construtor sobrecarregado para o fragmento.

Perdi alguma coisa?

Quais são os benefícios de uma abordagem sobre a outra? Ou é apenas uma boa prática?

Graham Smith
fonte
Quando existem parâmetros, não há escolha, e isso é amplamente respondido aqui. Ainda, a questão permanece para a construção sem argumentos do fragmento.
Rds #
1
Depois de aprender sobre os padrões de fábrica e como uma classe de chamada que não instancia um objeto em si ajuda a dissociá-los, pensei que esse seria um ponto forte para o método newInstance (). Estou errado nisso? Não vi esse argumento específico mencionado como um benefício.
Aplicativos móveis

Respostas:

1137

Se o Android decidir recriar o seu Fragmento mais tarde, chamará o construtor sem argumentos do seu fragmento. Portanto, sobrecarregar o construtor não é uma solução.

Com isso dito, a maneira de passar as coisas para o seu Fragmento para que elas estejam disponíveis após a recriação de um fragmento pelo Android é passar um pacote para o setArgumentsmétodo.

Então, por exemplo, se quiséssemos passar um número inteiro para o 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;
}

E mais tarde no fragmento, onCreate()você pode acessar esse número inteiro usando:

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

Este pacote estará disponível mesmo que o fragmento seja recriado de alguma forma pelo Android.

Observe também: setArgumentssó pode ser chamado antes que o fragmento seja anexado à atividade.

Essa abordagem também está documentada na referência do desenvolvedor do Android: https://developer.android.com/reference/android/app/Fragment.html

yydl
fonte
7
Infelizmente, os métodos estáticos do Atlas não podem ser substituídos.
AJD 5/02
8
@yydl Acho que estou perdendo alguma coisa aqui, você não poderia usar um construtor aqui de qualquer maneira, um que crie o Bundle e chame setArguments () ainda porque ele será chamado apenas pelo seu código (e não quando o Android recriar seu fragmento)?
Mike Tunnicliffe
9
@mgibson Você deve usar um pacote para que os dados estejam disponíveis quando o fragmento for recriado posteriormente.
yydl
114
Ser FORÇADO a criar um construtor sem argumentos para fragmentos é potencialmente o maior problema em toda a programação, em qualquer lugar. Isso força uma mudança completa de paradigma na criação e inicialização de objetos. Se você é novo no Android e se deparou com esse tópico, leia a resposta acima várias vezes.
rmirabelle
9
Eu argumentaria com essa afirmação. Primeiro, a segurança de tipo é uma preocupação de linguagem, não de estrutura. Em segundo lugar, na IMO, a estrutura está entrando na área de "coisas que sua API nunca deve fazer". Se eu quiser passar a biblioteca do congresso para o meu construtor de fragmentos, será permitido. O contrato do construtor "no-args" basicamente mata o uso de injeção de dependência em fragmentos - grande eca.
21414 rmirabelle
95

O único benefício em usar o newInstance()que vejo são os seguintes:

  1. Você terá um único local em que todos os argumentos usados ​​pelo fragmento poderão ser agrupados e não precisará escrever o código abaixo sempre que instanciar um fragmento.

    Bundle args = new Bundle();
    args.putInt("someInt", someInt);
    args.putString("someString", someString);
    // Put any other arguments
    myFragment.setArguments(args);
  2. É uma boa maneira de dizer a outras classes quais argumentos ele espera que funcionem fielmente (embora você deva poder lidar com casos se nenhum argumento estiver agrupado na instância do fragmento).

Então, minha opinião é que usar uma estática newInstance()para instanciar um fragmento é uma boa prática.

500865
fonte
4
1) Como isso é diferente de colocar a lógica em um construtor? Ambos são locais únicos onde você inclui essa lógica. 2) Como os parâmetros em uma fábrica estática são diferentes dos parâmetros em um construtor? Ambos dizem quais argumentos são esperados. Meu argumento é que é um paradigma diferente, com certeza, mas não há nenhum benefício claro sobre isso ao usar construtores.
RJ Cuthbertson
2
Você não pode usar construtores personalizados para fragmento. O Framework usa o construtor sem argumento para restaurar fragmentos.
500865
5
Sim, eu concordo com você lá. Estou dizendo, conceitualmente, que não há benefício em usar o padrão estático de fábrica em vez de usar construtores sobrecarregados e vice-versa. Ambos os seus pontos são válidos nos dois padrões; não há benefício em usar um sobre o outro. O Android obriga a usar o padrão estático de fábrica - mas não há benefício em usar um ou outro.
RJ Cuthbertson
pastebin.com/EYJzES0j
RJ Cuthbertson
@RJCuthbertson Um possível benefício seria a capacidade de criar e retornar subclasses da classe do método de fábrica estática, ou seja, retornar uma subclasse apropriada para a situação.
precisa saber é
62

Há também outra maneira:

Fragment.instantiate(context, MyFragment.class.getName(), myBundle)
user1145201
fonte
Se não me engano, isso só é possível quando você usa a biblioteca de suporte do Android.
Timo
2
Tentei isso com a biblioteca de suporte, mas no onCreateView (no meu fragmento), o pacote passado foi nulo, então fui com a opção setArguments / getArguments e funcionou (para quem está lendo isso).
Jrop
1
Interessante, eu nunca vi essa abordagem antes. Tem alguma vantagem sobre outras abordagens para instanciar um fragmento?
IgorGanapolsky
22
A partir dos documentos desenvolvedor ,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
Embora eles mencionaram o mesmo que chamar construtor vazio. "args.setClassLoader (f.getClass (). getClassLoader ());" é chamado por baixo para argumentos do pacote
Gökhan Barış Aker
49

Enquanto @yydl fornece uma razão convincente para o newInstancemelhor método:

Se o Android decidir recriar o seu Fragmento mais tarde, chamará o construtor sem argumentos do seu fragmento. Portanto, sobrecarregar o construtor não é uma solução.

ainda é possível usar um construtor . Para entender por que isso ocorre, primeiro precisamos ver por que a solução alternativa acima é usada pelo Android.

Antes que um fragmento possa ser usado, é necessária uma instância. O Android chama YourFragment()(o construtor sem argumentos ) para construir uma instância do fragmento. Aqui, qualquer construtor sobrecarregado que você escreve será ignorado, pois o Android não pode saber qual usar.

Durante a vida útil de uma atividade, o fragmento é criado como descrito acima e destruído várias vezes pelo Android. Isso significa que se você colocar dados no próprio objeto de fragmento, eles serão perdidos quando o fragmento for destruído.

Para contornar o problema, o Android solicita que você armazene dados usando uma Bundle(chamada setArguments()), que pode ser acessada a partir de YourFragment. Os argumentos bundlesão protegidos pelo Android e, portanto, garantem a persistência .

Uma maneira de configurar esse pacote configurável é usando um 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;
}

No entanto, um construtor:

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

pode fazer exatamente a mesma coisa que o newInstancemétodo

Naturalmente, isso falharia e é um dos motivos pelos quais o Android quer que você use o newInstancemétodo:

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

Como explicação adicional, aqui está a Classe de Fragmentos do 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;
}

Observe que o Android pede que os argumentos sejam definidos apenas na construção e garante que eles serão mantidos.

EDIT : Como apontado nos comentários de @JHH, se você estiver fornecendo um construtor personalizado que requer alguns argumentos, o Java não fornecerá ao seu fragmento um construtor padrão sem argumento . Portanto, isso exigiria que você definisse um construtor no arg , que é um código que você poderia evitar com o newInstancemétodo factory.

EDIT : Android não permite mais usar um construtor sobrecarregado para fragmentos. Você deve usar o newInstancemétodo

ps95
fonte
Quando alguém justificaria usar android: configChanges = "guidance | keyboardHidden | screenSize"?
Luke Allison
1
O Android Studio agora gera um erro para todos os construtores não padrão em fragmentos, portanto, isso não funciona mais.
precisa saber é o seguinte
6
Santo homem, gostaria de saber quantos desenvolvedores de droides já escreveram código fora do droid. Isso é insano porque não podemos usar a abordagem que você descreve. Não há argumentos convincentes de qualquer comentário sobre o motivo pelo qual temos que usar o método estático de fábrica. É ainda mais perturbador que eles cometeriam um erro ao compilar. Esta é definitivamente a melhor resposta fornecida e mostra que não há benefício para o SFM.
precisa saber é o seguinte
3
Bem, há uma razão sutil. Você é livre para criar seu próprio construtor com argumentos, mas ainda precisa haver um construtor sem argumentos . Como as classes sempre têm um construtor implícito no-arg, a menos que um construtor com args seja explicitamente definido , isso significa que você precisaria definir explicitamente seu construtor arg e um construtor no-arg, ou o sistema não seria capaz de invocar nenhum construtor no-arg. Acredito que é por isso que a recomendação é usar um método estático de fábrica - ele simplesmente reduz o risco de esquecer de definir um construtor sem argumento.
JHH 28/10
@JHH que falhará no momento da compilação, portanto, não é um grande risco. No entanto, o problema aqui é que a sobrecarga de construtores, um paradigma de programação importante, está sendo negada pelo Android.
Ps95 #
20

Não concordo com a resposta do yydi dizendo:

Se o Android decidir recriar o seu Fragmento mais tarde, chamará o construtor sem argumentos do seu fragmento. Portanto, sobrecarregar o construtor não é uma solução.

Eu acho que é uma solução e uma boa solução, esse é exatamente o motivo pelo qual foi desenvolvida pela linguagem principal do Java.

É verdade que o sistema Android pode destruir e recriar o seu Fragment. Então você pode fazer isso:

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

Isso permitirá que você use a someIntpartir getArguments()de agora, mesmo que tenha Fragmentsido recriado pelo sistema. Esta é uma solução mais elegante que o staticconstrutor.

Na minha opinião, staticconstrutores são inúteis e não devem ser usados. Eles também o limitarão se, no futuro, você desejar estender isso Fragmente adicionar mais funcionalidade ao construtor. Com o staticconstrutor, você não pode fazer isso.

Atualizar:

O Android adicionou uma inspeção que sinaliza todos os construtores não padrão com um erro.
Eu recomendo desativá-lo, pelos motivos mencionados acima.

Ilya Gazman
fonte
4
Outro benefício de ter um método estático, que não mencionei acima, é que você não pode acidentalmente definir propriedades dele.
yydl
4
Além disso, com relação ao seu argumento sobre "estender esse fragmento", esse método seria realmente muito ruim se você estender a classe. Chamar super fará com que a chamada setArguments () seja eficaz apenas para o filho ou pai, mas não para os dois!
yydl 23/09/14
2
@yydle, você pode evitar essa situação chamando get argumentos para inicializar o Bundle filho. Java sempre melhor.
Ilya Gazman
9
É verdade, mas esse é outro motivo para incentivar as pessoas a usarem o padrão sugerido pelo Google. Obviamente, todos concordamos que sua solução é 100% tecnicamente viável. Da mesma forma, existem muitas maneiras de fazer muitas coisas. A questão, porém, é se é o melhor. E sinto fortemente que o uso do construtor não representa a verdadeira natureza de como isso deve funcionar.
yydl 29/09/14
3
Concordo com @yydl que a criação estática é melhor. Um outro benefício é a injeção de dependência de futuras novas dependências - o construtor não está preparado para isso e provavelmente causará mais alterações no código (ou mais construtores serão adicionados).
Boon
19

Algum código kotlin :

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

E você pode obter argumentos com isso:

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

A melhor prática para instar fragmentos com argumentos no android é ter o método de fábrica estática no seu 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;
}

Você deve evitar definir seus campos com a instância de um fragmento. Porque sempre que o sistema Android recriar seu fragmento, se achar que o sistema precisa de mais memória, ele recriará seu fragmento usando o construtor sem argumentos.

Você pode encontrar mais informações sobre as melhores práticas para instanciar fragmentos com argumentos aqui.

Gunhan
fonte
2

Como as perguntas sobre as melhores práticas, eu acrescentaria, que muitas vezes é uma boa idéia usar a abordagem híbrida para criar fragmentos ao trabalhar com alguns serviços da web REST

Não podemos passar objetos complexos, por exemplo, algum modelo de usuário, para exibir fragmento de usuário

Mas o que podemos fazer é fazer check-in onCreatedesse usuário! = Null e, se não - então trazê-lo da camada de dados, caso contrário - use o existente.

Dessa forma, obtemos a capacidade de recriar pelo userId em caso de recriação de fragmentos pelo Android e o snappiness para as ações do usuário, bem como a capacidade de criar fragmentos mantendo pressionado para objetar a si próprio ou apenas seu ID

Algo gosta disso:

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
fonte
3
Você disse: "Não podemos passar objetos complexos, por exemplo, algum modelo de usuário" - - Não é verdade, podemos. Assim: User user = /*...*/ coloque o usuário no pacote configurável : Bundle bundle = new Bundle(); bundle.putParcelable("some_user", user); e obtenha o usuário dos argumentos: User user = getArguments().getParcelable("some_user");O objeto deve ser implementar a interface Parcelable. link
Adam Varhegyi
3
Bem, sim, mas quando a classe é complexa e contém referências a outros objetos ... Eu, pessoalmente, prefiro mantê-lo simples, ou eu tenho objeto, ou eu não e, em seguida, precisa obtê-lo
Tigra
1

use este código 100% corrija seu problema

insira esse código no firstFragment

public static yourNameParentFragment newInstance() {

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

este exemplo envia dados booleanos

e no SecendFragment

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

deve valor no primeiro fragmento é estático

código feliz

Amin Emadi
fonte
0

A melhor maneira de instanciar o fragmento é usar o método Fragment.instantiate padrão ou criar o método factory para instanciar o fragmento
Cuidado: sempre crie um construtor vazio no fragmento outro enquanto a restauração do fragmento lançará uma exceção em tempo de execução.

Mahesh
fonte
0

Ultimamente estou aqui. Mas algumas coisas que eu acabei de saber que podem ajudá-lo um pouco.

Se você estiver usando Java, não há muito o que mudar. Mas para os desenvolvedores do kotlin, aqui estão alguns trechos a seguir que eu acho que podem torná-lo um porão para rodar:

  • Fragmento pai:
inline fun <reified T : SampleFragment> newInstance(text: String): T {
    return T::class.java.newInstance().apply {
        arguments = Bundle().also { it.putString("key_text_arg", text) }
    }
}
  • Chamada normal
val f: SampleFragment = SampleFragment.newInstance("ABC")
// or val f = SampleFragment.newInstance<SampleFragment>("ABC")
  • Você pode estender a operação init pai na classe de fragmento filho:
fun newInstance(): ChildSampleFragment {
    val child = UserProfileFragment.newInstance<ChildSampleFragment>("XYZ")
    // Do anything with the current initialized args bundle here
    // with child.arguments = ....
    return child
}

Feliz codificação.

vhfree
fonte
-2

setArguments()é inútil. Isso só traz uma bagunça.

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
fonte
Exceto que você acabou de ser forçado a substituir mais um método e criar um campo que poderia ter sido isolado no onViewCreatedescopo. Acho que é conveniência, muitas maneiras de fazer a mesma coisa. Também é uma maneira fácil de verificar se há atualizações feitas pelo usuário (comparar os pacotes de getArgumentse o pacote de onSaveInstanceState)
Overclover
@Asagen, eu gosto do seu comentário sobre comparar valores iniciais e de usuário. Editei o código e considero que ele ainda é uniforme e claro, sem getArgumentsitens. E quanto ao onViewCreatedescopo ... Podemos restaurar o pacote de estado lá. Mas eu prefiro make onCreateViewleve e rápido e fazer toda a inicialização pesada dentro de um onActivityCreated, porque Fragment.getActivity()às vezes gostaria de voltar nulle por causa de onAttach()mudanças na nova versão do API 23.
Vadim Estrela
Tudo o que você fez aqui foi movimento set e get Argumentsem saveInstanceState. Você está essencialmente fazendo a mesma coisa que é feita "sob o capô"
OneCricketeer
1
@ cricket_007, ou exatamente o oposto . Usando saveInstanceStateé "sob o capô". E usar de Argumentsé a duplicação de funcionalidades que fazem com que você verifique Argumentsnovamente : primeiro os valores e depois os saveInstanceStatevalores. Porque você tem que usar de saveInstanceStatequalquer maneira. Que tal Arguments... eles não são necessários.
Vadim Estrela
Argumentos são equivalentes a extras de intenção para fragmentos. Eles não são inúteis, eles contêm os parâmetros iniciais que são diferentes do estado atual.
BladeCoder
-12

Acredito que tenho uma solução muito mais simples para isso.

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
fonte
12
Os mObjects serão limpos se o MyFragment for recriado (o usuário acessa a tela inicial do dispositivo e depois abre o aplicativo que parou no MyFragment). Você pode reter mObjects enviando MyFragment um pacote como argumentos.
precisa saber é o seguinte
1
Além disso, como o método estático está acessando variáveis ​​de membro não estáticas?
OrhanC1
2
@ynnadkrap Você está correto, usar um pacote é o caminho a percorrer aqui.
Stefan Bogaard
2
@ OrhanC1 De acordo com este código de exemplo, o método estático não está acessando as variáveis ​​de membro. A instância do MyFragment está acessando seus membros. Não há erro aqui. No entanto, eu não recomendo esta resposta a ninguém, porque quando seu fragmento é removido da memória para abrir algum espaço pelo sistema operacional Android, após reiniciar a atividade, esse fragmento será criado com o construtor vazio padrão sem atribuir variáveis ​​ant.
Gunhan
@Gunhan Você está certo! Não é. Desculpe pela confusão :)
OrhanC1