新しいAndroidフラグメントをインスタンス化するためのベストプラクティス


706

アプリケーションで新しいフラグメントをインスタンス化するための2つの一般的なプラクティスを見てきました。

Fragment newFragment = new MyFragment();

そして

Fragment newFragment = MyFragment.newInstance();

2番目のオプションは、静的メソッドを使用するnewInstance()、一般的に次のような方法が含まれています。

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

最初、主な利点は、フラグメントの新しいインスタンスを作成するときに柔軟性を与えるためにnewInstance()メソッドをオーバーロードできるということでしたが、フラグメントのオーバーロードされたコンストラクターを作成することによってもこれを行うことができました。

私は何か見落としてますか?

あるアプローチが他のアプローチより優れている点は何ですか?それともそれはただ良い習慣ですか?


パラメータがある場合、選択肢はありません。これはここで広範囲に回答されます。それでも、議論の余地のないフラグメントの構築には疑問が残ります。
RDS

1
ファクトリー・パターンと、オブジェクト自体をインスタンス化しない呼び出しクラスがそれらを分離するのにどのように役立つかを学んだ後、これがnewInstance()メソッドの強みになると思いました。私はこれについて間違っていますか?私は、この特定の議論が利益になるとは見ていません。
モバイルアプリケーション

回答:


1137

Androidが後でフラグメントを再作成することを決定した場合、フラグメントの引数のないコンストラクターを呼び出します。そのため、コンストラクターのオーバーロードは解決策ではありません。

そうは言っても、AndroidによってFragmentが再作成された後に使用できるように、Fragmentにデータを渡すsetArguments方法は、メソッドにバンドルを渡すことです。

したがって、たとえば、フラグメントに整数を渡したい場合は、次のようなものを使用します。

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

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

    return myFragment;
}

そして、後でフラグメントonCreate()を使用して、その整数にアクセスできます。

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

このバンドルは、フラグメントがAndroidによって何らかの方法で再作成された場合でも使用できます。

また、注:setArgumentsフラグメントがアクティビティにアタッチされる前にのみ呼び出すことができます。

このアプローチは、Android開発者リファレンスにも記載されています:https : //developer.android.com/reference/android/app/Fragment.html


7
@Vlasto残念ながら静的メソッドはオーバーライドできません。
AJD 2013

8
@yydl私はここに何か不足していると思いますが、とにかくここでコンストラクタを使用できませんでした。バンドルを作成してsetArguments()を呼び出すものですが、それはコードによってのみ呼び出されるためです(Androidが断片)?
Mike Tunnicliffe 2013年

9
@mgibson フラグメントを後で再作成するときにデータを使用できるようにするには、バンドルを使用する必要があります。
yydl 2013年

114
フラグメントの引数なしのコンストラクターを作成するように強制されていることは、すべてのプログラミングにおいて、どこでも、単一の最大の問題です。オブジェクトの作成と初期化でパラダイムシフトを完全に強制します。Androidが初めてで、このスレッドに出くわした場合は、上記の答えを何度も何度も読んでください。
rmir​​abelle 2013

9
私はその主張について議論します。第一に、型安全性は言語の問題であり、フレームワークの問題ではありません。第二に、IMO、フレームワークは「APIが決してしてはならないこと」の領域に足を踏み入れています。議会のライブラリをフラグメントコンストラクターに渡したい場合は、許可する必要があります。「引数なし」のコンストラクタコントラクトは、基本的にフラグメントでの依存性注入の使用を中止します。
rmir​​abelle 2014

95

newInstance()私が見るものを使用する唯一の利点は次のとおりです:

  1. フラグメントで使用されるすべての引数を1か所にまとめることができ、フラグメントをインスタンス化するたびに以下のコードを記述する必要はありません。

    Bundle args = new Bundle();
    args.putInt("someInt", someInt);
    args.putString("someString", someString);
    // Put any other arguments
    myFragment.setArguments(args);
  2. 他のクラスに忠実に動作することが期待される引数を通知するための優れた方法です(ただし、フラグメントインスタンスに引数がバンドルされていない場合は処理できるはずです)。

したがって、私は、静的newInstance()を使用してフラグメントをインスタンス化することをお勧めします。


4
1)これは、ロジックをコンストラクターに配置することとどう違うのですか?どちらも、このロジックを含める単一の場所です。2)静的ファクトリーのパラメーターは、コンストラクターのパラメーターとどのように異なりますか?どちらも予想される引数を示します。私の要点は確かに別のパラダイムだということですが、コンストラクタを使用するよりも明確な利点はありません。
RJ Cuthbertson

2
フラグメントにはカスタムコンストラクターを使用できません。フレームワークはフラグメントを復元するために引数なしのコンストラクターを使用します。
500865

5
はい、同意します。概念的には、オーバーロードされたコンストラクターを使用する代わりに静的ファクトリーパターンを使用するメリットはなく、その逆も同様です。どちらのポイントも両方のパターンで有効です。どちらか一方を使用する利点はありません。Androidでは静的なファクトリパターンを使用する必要がありますが、どちらを使用してもメリットはありません。
RJカスバートソン

pastebin.com/EYJzES0j
RJ Cuthbertson

@RJCuthbertson考えられる利点は、静的ファクトリメソッドのクラスのサブクラスを作成して返す機能、つまり状況に応じて適切なサブクラスを返す機能です。
urgentx 2017

62

別の方法もあります:

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

私が間違っていない場合、これはAndroidサポートライブラリを使用している場合にのみ可能です。
Timo

2
これをサポートライブラリで試してみましたが、(私のフラグメントの)onCreateViewでは、渡されたバンドルがnullだったので、setArguments / getArgumentsオプションを使用すると、(これを読んでいる人は)機能しました。
Jrop 2013

1
興味深いことに、私はこのアプローチを見たことがありません。フラグメントをインスタンス化する他のアプローチに勝る利点はありますか?
IgorGanapolsky 2013年

22
開発者ドキュメントからinstantiate() Creates a new instance of a Fragment with the given class name. This is the same as calling its empty constructor.
ブライアン・ボーマン

2
彼らは空のコンストラクタを呼び出すのと同じことを述べましたが。「args.setClassLoader(f.getClass()。getClassLoader());」バンドル引数の下に呼ばれている
グーカン・バリスアーカー

49

@yydlは、newInstanceメソッドが優れている理由について説得力のある理由を示していますが、

Androidが後でフラグメントを再作成することを決定した場合、フラグメントの引数のないコンストラクターを呼び出します。そのため、コンストラクターのオーバーロードは解決策ではありません。

コンストラクタを使用することは依然として可能です。これがなぜかを確認するには、まず、上記の回避策がAndroidで使用される理由を確認する必要があります。

フラグメントを使用する前に、インスタンスが必要です。AndroidはYourFragment()引数なしのコンストラクター)を呼び出して、フラグメントのインスタンスを作成します。ここでは、Androidがどちらを使用するかを認識できないため、オーバーロードしたコンストラクターは無視されます。

アクティビティの存続期間中に、フラグメントは上記のように作成され、Androidによって複数回破棄されます。つまり、フラグメントオブジェクト自体にデータを配置した場合、フラグメントが破棄されるとデータは失われます。

回避策として、AndroidはBundle(呼び出しsetArguments())を使用してデータを保存するように要求しますYourFragment。これは、からアクセスできます。引数bundleはAndroidによって保護されているため、永続的であることが保証されています。

このバンドルを設定する1つの方法は、静的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;
}

ただし、コンストラクタ:

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

newInstanceメソッドとまったく同じことができます。

当然、これは失敗し、AndroidがこのnewInstanceメソッドを使用してほしい理由の1つです。

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

さらに説明すると、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;
}

Androidは、引数が構築時にのみ設定されることを要求し、これらが保持されることを保証することに注意してください。

編集:@JHHのコメントで指摘されているように、引数を必要とするカスタムコンストラクターを提供している場合、Javaは引数のないデフォルトコンストラクターをフラグメントに提供しません。したがって、これには引数なしのコンストラクタを定義する必要があります。これは、newInstanceファクトリメソッドで回避できるコードです。

編集:Androidはフラグメントにオーバーロードされたコンストラクターを使用することを許可しなくなりました。newInstanceメソッドを使用する必要があります。


android:configChanges = "orientation | keyboardHidden | screenSize"を使用して正当化するのはいつですか?
ルークアリソン

1
Android Studioは、デフォルト以外のすべてのコンストラクターに対してフラグメントでエラーをスローするようになったため、機能しなくなりました。
Sheharyar

6
聖人よ、ドロイドの開発者がドロイドの外でコードを書いたことがあるのだろうか。これは、あなたが説明したアプローチを使用できないというのは正気ではありません。静的ファクトリメソッドを使用する必要がある理由については、コメントから説得力のある議論はありません。コンパイル時にエラーが発生することはさらに厄介です。これは間違いなく提供される最良の回答であり、sfmにメリットがないことを示しています。
MPavlak

3
さて、1つの微妙な理由があります。引数付きの独自のコンストラクタを自由に作成できますが、引数なしのコンストラクタも必要です。argsを持つコンストラクターが明示的に定義されていない限り、クラスには常に暗黙のno-argコンストラクターがあるため、arg-constructor no-argコンストラクターの両方を明示的に定義する必要があります。そうしないと、システムは何も呼び出すことができません。引数なしのコンストラクタ。私はこれが代わりに静的ファクトリーメソッドを使用することが推奨されている理由だと私は信じています-それは単に引数なしのコンストラクターを定義するのを忘れるリスクを減らすだけです。
JHH 2016年

コンパイル時に失敗する@JHHなので、それほど大きなリスクはありません。ただし、ここでの問題は、主要なプログラミングパラダイムであるコンストラクターのオーバーロードがAndroidによって拒否されていることです。
ps95 2016年

20

yydiの答え同意しません:

Androidが後でフラグメントを再作成することを決定した場合、フラグメントの引数のないコンストラクターを呼び出します。そのため、コンストラクターのオーバーロードは解決策ではありません。

私はそれが解決策であり良いものだと思います。これがまさにJavaコア言語によって開発された理由です。

Androidシステムがを破壊して再作成できることは事実ですFragment。だからあなたはこれを行うことができます:

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

システムによって再作成された場合でもsomeIntgetArguments()後者からプルすることができますFragment。これはstaticコンストラクタよりもエレガントなソリューションです。

私の意見では、staticコンストラクタは役に立たないので使用しないでください。また、将来的にこれを拡張Fragmentしてコンストラクタに機能を追加したい場合にも制限されます。ではstaticコンストラクタあなたはこれを行うことはできません。

更新:

Androidは、デフォルト以外のすべてのコンストラクターにエラーのフラグを立てる検査を追加しました。
上記の理由により、無効にすることをお勧めします。


4
静的メソッドを使用することのもう1つの利点は、上記では触れていませんが、誤ってそこからプロパティを設定できないことです。
yydl 2014

4
さらに、「このフラグメントを拡張する」という点に関しては、クラスを拡張する場合、このメソッドは実際には非常に悪いでしょう。superを呼び出すと、setArguments()呼び出しは、子または親のいずれかに対してのみ有効になり、両方に対しては無効になります。
yydl

2
@yydleでは、get Bundleを呼び出して子バンドルを初期化することで、この状況を回避できます。Javaの方が常に優れています。
Ilya Gazman 2014

9
確かに、しかしそれはグーグルが提案したパターンを人々に使うよう奨励するもう一つの理由です。もちろん、お客様のソリューションが100%技術的に実現可能であることに私たちは皆同意します。多くのことを行うには多くの方法があります。問題は、それが最高かどうかです。そして、コンストラクターを使用することは、これがどのように機能するかという本来の性質を表していないと強く感じます。
yydl 2014

3
静的作成の方が良いという@yydlに同意します。もう1つの利点は、将来の新しい依存関係の依存関係注入です。コンストラクターはこれに適合しておらず、コードの変更が増える可能性があります(またはコンストラクターを追加する必要があります)。
ブーン

19

いくつかのkotlinコード:

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

そして、これで引数を得ることができます:

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

3

Androidで引数を使用してフラグメントをインスタンス化するベストプラクティスは、フラグメントに静的なファクトリメソッドを含めることです。

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

フラグメントのインスタンスでフィールドを設定することは避けてください。Androidシステムがフラグメントを再作成するたびに、システムがより多くのメモリを必要とする場合は、引数なしのコンストラクターを使用してフラグメントを再作成するためです。

引数を使用しフラグメントをインスタンス化するためのベストプラクティスについて詳しくは、こちらをご覧ください。


2

ベストプラクティスについての質問から、いくつかのREST Webサービスを操作するときにフラグメントを作成するためにハイブリッドアプローチを使用することが非常によくあることを付け加えておきます

ユーザーフラグメントを表示する場合、複雑なオブジェクト、たとえば一部のユーザーモデルを渡すことはできません

しかし、私たちができることは、onCreateそのユーザーをチェックインすることです!= null、そうでない場合-データレイヤーから彼を連れてきます。それ以外の場合は、既存のものを使用します。

このようにして、Androidによるフラグメントの再作成の場合にuserIdによって再作成する機能と、ユーザーアクションのスナップ性、およびオブジェクト自体またはそのIDのみを保持してフラグメントを作成する機能の両方を取得します

これが好きなもの:

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
「複雑なオブジェクト、たとえば一部のユーザーモデルなどを渡すことはできません」- 事実ではありません。このように: User user = /*...*/ ユーザーをバンドルに入れ、 Bundle bundle = new Bundle(); bundle.putParcelable("some_user", user); 引数からユーザーを取得し User user = getArguments().getParcelable("some_user"); ます。オブジェクトはParcelableインターフェースを実装する必要があります。リンク
アダムVarhegyi

3
ええ、そうですが、クラスが複雑で、別のオブジェクトへの参照が含まれている場合...私は個人的に、オブジェクトを持っているか、持っていないのでそれを取得する必要があるかのどちらかで、単純に保つことを好みます
Tigra

1

このコードを使用して問題を100%修正します

このコードをfirstFragmentに入力します

public static yourNameParentFragment newInstance() {

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

このサンプルはブールデータを送信します

そして、でSecendFragment

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

最初のフラグメントの値は静的である必要があります

幸せなコード


0

フラグメントをインスタンス化する最良の方法は、デフォルトのFragment.instantiateメソッドを使用するか、フラグメントをインスタンス化するファクトリメソッドを作成する ことです
注意:フラグメントメモリを復元するとランタイム例外がスローされますが、フラグメント内に常に1つの空のコンストラクターを作成してください。


0

私は最近ここにいます。しかし、私が今知っていることは少し役立つかもしれません。

Javaを使用している場合、変更することはほとんどありません。しかし、kotlin開発者のために、実行するための地下室を作ることができると私が思う以下のスニペットがあります:

  • 親フラグメント:
inline fun <reified T : SampleFragment> newInstance(text: String): T {
    return T::class.java.newInstance().apply {
        arguments = Bundle().also { it.putString("key_text_arg", text) }
    }
}
  • 通常の通話
val f: SampleFragment = SampleFragment.newInstance("ABC")
// or val f = SampleFragment.newInstance<SampleFragment>("ABC")
  • 次の方法で、子フラグメントクラスの親init操作を拡張できます。
fun newInstance(): ChildSampleFragment {
    val child = UserProfileFragment.newInstance<ChildSampleFragment>("XYZ")
    // Do anything with the current initialized args bundle here
    // with child.arguments = ....
    return child
}

ハッピーコーディング。


-2

setArguments()役に立たない。それは混乱をもたらすだけです。

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

ただし、もう1つのメソッドをオーバーライドして、onViewCreatedスコープに分離されている可能性のあるフィールドを作成することを余儀なくされているだけです。同じことをする多くの方法が便利だと思います。また、それは、ユーザによって行わアップデートをチェックする簡単な方法(の束比較だgetArgumentsとのバンドルをonSaveInstanceState
Overclover

@Asagen、初期値とユーザー値の比較についてのコメントが好きです。私はコードを編集し、それがまだ均一で明確なgetArgumentsものであると考えています。onViewCreatedスコープについてはどうですか...状態バンドルをそこに復元できます。しかし、私はちょうどメイク好むonCreateView光を迅速かつ内部のすべての重いの初期化を行うonActivityCreatedためにFragment.getActivity()、時には復帰したいnullとためのonAttach()API 23の新バージョンでの変更
ヴァディムスター

すべてのあなたはここでやった動き setget ArgumentssaveInstanceState。基本的に、「ボンネットの下で」行われるのと同じことを行っています
OneCricketeer

1
@ cricket_007、またはちょうど反対。使用saveInstanceStateは「内部」です。そして、ofの使用は、Arguments最初のArguments値、次にsaveInstanceState値の二重チェックを行う機能の複製です。あなたはsaveInstanceStateどんな方法でも使わなければならないからです。何についてArguments...それらは必要ありません。
Vadim Star

引数は、フラグメントのIntentエクストラに相当します。それらは役に立たないわけではなく、現在の状態とは異なる初期パラメータが含まれています。
BladeCoder 2017

-12

私はこれに対してもっと簡単な解決策があると信じています。

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
MyFragmentが再作成された場合、mObjectはクリアされます(ユーザーがデバイスのホーム画面に移動し、後でMyFragmentで中断したアプリを開きます)。MyFragmentに引数としてバンドルを送信することにより、mObjectを保持できます。
ynnadkrap

1
また、静的メソッドは非静的メンバー変数にどのようにアクセスしますか?
OrhanC1

2
@ynnadkrap正解です。バンドルを使用することがここに行く方法です。
Stefan Bogaard、2015年

2
@ OrhanC1このコード例によると、静的メソッドはメンバー変数にアクセスしていません。MyFragmentのインスタンスがそのメンバーにアクセスしています。ここにはエラーはありません。ただし、この回答は誰にもお勧めしません。フラグメントがメモリから削除されてandroid osによって一部のスペースが開かれると、アクティビティを再起動すると、この変数はant変数を割り当てずにデフォルトの空のコンストラクターで作成されます。
ガンハン2015年

@群韓そうですね!そうではありません。混乱して申し訳ありません:)
OrhanC1 2016
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.