web-dev-qa-db-ja.com

onActivityResultからDialogFragmentを表示

私のフラグメントのonActivityResultには次のコードがあります。

onActivityResult(int requestCode, int resultCode, Intent data){
   //other code
   ProgressFragment progFragment = new ProgressFragment();  
   progFragment.show(getActivity().getSupportFragmentManager(), PROG_DIALOG_TAG);
   // other code
}

ただし、次のエラーが表示されます。

Caused by: Java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState   

誰が何が起こっているのか、私はこれをどのように修正できるのか知っていますか? Androidサポートパッケージを使用していることに注意してください。

78
Kurtis Nusbaum

Androidサポートライブラリを使用している場合、onResumeメソッドは適切な場所ではありません。フラグメントを再生する場所です。onResumeFragmentsメソッドで行う必要があります。onResumeメソッドの説明を参照してください: http://developer.Android.com/reference/Android/support/v4/app/FragmentActivity.html#onResume%28%29

したがって、私の観点からの正しいコードは次のようになります。

private boolean mShowDialog = false;

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data){
  super.onActivityResult(requestCode, resultCode, data);

  // remember that dialog should be shown
  mShowDialog = true;
}

@Override
protected void onResumeFragments() {
  super.onResumeFragments();

  // play with fragments here
  if (mShowDialog) {
    mShowDialog = false;

    // Show only if is necessary, otherwise FragmentManager will take care
    if (getSupportFragmentManager().findFragmentByTag(PROG_DIALOG_TAG) == null) {
      new ProgressFragment().show(getSupportFragmentManager(), PROG_DIALOG_TAG);
    }
  }
}
73
Arcao

編集:バグではなく、フラグメントフレームワークの欠陥の多く。この質問に対するより良い答えは、上記の@Arcaoによって提供されたものです。

----元の投稿----

実際には、サポートパッケージに含まれる 既知のバグ です(編集:実際にはバグではありません。@ alex-lockwoodのコメントを参照してください)。バグレポートのコメントに投稿された回避策は、DialogFragmentのソースを次のように変更することです。

public int show(FragmentTransaction transaction, String tag) {
    return show(transaction, tag, false);
}


public int show(FragmentTransaction transaction, String tag, boolean allowStateLoss) {
    transaction.add(this, tag);
    mRemoved = false;
    mBackStackId = allowStateLoss ? transaction.commitAllowingStateLoss() : transaction.commit();
    return mBackStackId;
}

これは巨大なハックです。実際に行った方法は、元のフラグメントから登録できる独自のダイアログフラグメントを作成するだけでした。他のダイアログフラグメントが(却下されるなど)処理を行うと、リスナーにそれがなくなることを通知しました。私はこのようにしました:

public static class PlayerPasswordFragment extends DialogFragment{

 Player toJoin;
 EditText passwordEdit;
 Button okButton;
 PlayerListFragment playerListFragment = null;

 public void onCreate(Bundle icicle){
   super.onCreate(icicle);
   toJoin = Player.unbundle(getArguments());
   Log.d(TAG, "Player id in PasswordFragment: " + toJoin.getId());
 }

 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle icicle){
     View v = inflater.inflate(R.layout.player_password, container, false);
     passwordEdit = (EditText)v.findViewById(R.id.player_password_edit);
     okButton = (Button)v.findViewById(R.id.ok_button);
     okButton.setOnClickListener(new View.OnClickListener(){
       public void onClick(View v){
         passwordEntered();
       }
     });
     getDialog().setTitle(R.string.password_required);
     return v;
 }

 public void passwordEntered(){
   //TODO handle if they didn't type anything in
   playerListFragment.joinPlayer(toJoin, passwordEdit.getText().toString());
   dismiss();
 }

 public void registerPasswordEnteredListener(PlayerListFragment playerListFragment){
   this.playerListFragment = playerListFragment;
 }

 public void unregisterPasswordEnteredListener(){
   this.playerListFragment = null;
 }
}

そのため、事態が発生したときにPlayerListFragmentに通知する方法があります。 unregisterPasswordEnteredListenerを適切に呼び出すことが非常に重要であることに注意してください(上記の場合、PlayerListFragmentが「なくなる」場合)。そうしないと、このダイアログフラグメントは、そのリスナーが存在しなくなったときに登録済みリスナーの関数を呼び出そうとする場合があります。

27
Kurtis Nusbaum

@ Natix によって残されたコメントは、一部の人が削除したかもしれない簡単なワンライナーです。

この問題の最も簡単な解決策は、独自のコードを実行する前にsuper.onActivityResult()を呼び出すことです。これは、サポートライブラリを使用しているかどうかに関係なく機能し、アクティビティの動作の一貫性を維持します。

がある:

私がこれをもっと読めば読むほど、私はこれまでにない非常識なハックを目にしました。

まだ問題が発生している場合は、 Alex Lockwood によるものがチェック対象です。

19
twig

これはAndroidバグです。基本的にAndroidアクティビティ/フラグメントライフサイクルの間違ったポイントでonActivityResultを呼び出します(onStart()の前))。

バグは https://issuetracker.google.com/issues/36929762 で報告されています

基本的にInResume()で後で処理するパラメーターとしてIntentを保存することで解決しました。

[編集]最近、この問題に対するより良い解決策が2012年には利用できませんでした。他の回答を参照してください。

14
hrnt

編集:まだanotherオプション、そしておそらく最高の(または、少なくともサポートライブラリが期待するもの...)

AndroidサポートライブラリでDialogFragmentsを使用している場合、FragmentActivityのサブクラスを使用する必要があります。次を試してください。

onActivityResult(int requestCode, int resultCode, Intent data) {

   super.onActivityResult(requestCode, resultCode, intent);
   //other code

   ProgressFragment progFragment = new ProgressFragment();  
   progFragment.show(getActivity().getSupportFragmentManager(), PROG_DIALOG_TAG);

   // other code
}

FragmentActivityの source を調べましたが、状態を失わずにフラグメントを再開するために内部フラグメントマネージャーを呼び出しているようです。


ここにリストされていないソリューションを見つけました。ハンドラーを作成し、ハンドラーでダイアログフラグメントを開始します。したがって、コードを少し編集します。

onActivityResult(int requestCode, int resultCode, Intent data) {

   //other code

   final FragmentManager manager = getActivity().getSupportFragmentManager();
   Handler handler = new Handler();
   handler.post(new Runnable() {
       public void run() {
           ProgressFragment progFragment = new ProgressFragment();  
           progFragment.show(manager, PROG_DIALOG_TAG);
       }
   }); 

  // other code
}

これは、私にとってはクリーンでハッキングが少ないようです。

11
Simon Jacobs

show(FragmentManager manager, String tag)show(FragmentTransaction transaction, String tag)の2つのDialogFragment show()メソッドがあります。

メソッドのFragmentManagerバージョンを使用する場合(元の質問のように)、簡単な解決策は、このメソッドをオーバーライドしてcommitAllowingStateLossを使用することです。

_public class MyDialogFragment extends DialogFragment {

  @Override 
  public void show(FragmentManager manager, String tag) {
      FragmentTransaction ft = manager.beginTransaction();
      ft.add(this, tag);
      ft.commitAllowingStateLoss();
  }

}
_

show(FragmentTransaction, String)のオーバーライドは、元のDialogFragmentコード内のいくつかの内部変数も変更する必要があるため、それほど簡単ではありません。したがって、お勧めしません。そのメソッドを使用する場合は、受け入れられた回答(またはJeffrey Blattmanからのコメント)。

CommitAllowingStateLossを使用することには多少のリスクがあります-ドキュメントには「commit()のようですが、アクティビティの状態が保存された後にコミットを実行できます。 、したがって、これはUIの状態がユーザー上で予期せず変更されても構わない場合にのみ使用してください。」

9
gkee

接続されたアクティビティがメソッドonSaveInstanceState()を呼び出した後にダイアログを表示することはできません。明らかに、onSaveInstanceState()はonActivityResult()の前に呼び出されます。したがって、このコールバックメソッドOnResumeFragment()でダイアログを表示する必要があります。DialogFragmentのshow()メソッドをオーバーライドする必要はありません。これがお役に立てば幸いです。

4
handrenliang

onActivityResult()はonResume()の前に実行されます。 onResume()以降でUIを実行する必要があります。

これらの両方のメソッド間で結果が返されたことを伝えるためにブール値または必要なものを使用します。

... それでおしまい。シンプル。

3
Eurig Jones

部分的にhmtのソリューションに基づいた3番目のソリューションを思いつきました。基本的に、onResume()で表示されるDialogFragmentsのArrayListを作成します。

ArrayList<DialogFragment> dialogList=new ArrayList<DialogFragment>();

//Some function, like onActivityResults
{
    DialogFragment dialog=new DialogFragment();
    dialogList.add(dialog);
}


protected void onResume()
{
    super.onResume();
    while (!dialogList.isEmpty())
        dialogList.remove(0).show(getSupportFragmentManager(),"someDialog");
}
3
PearsonArtPhoto

私はこれがかなり前に答えられたことを知っています..しかし、ここで見た他の答えのいくつかよりもはるかに簡単な方法があります...私の特定のケースでは、フラグメントonActivityResult()からDialogFragmentを表示する必要がありました方法。

これはそれを処理するための私のコードであり、美しく機能します:

DialogFragment myFrag; //Don't forget to instantiate this
FragmentTransaction trans = getActivity().getSupportFragmentManager().beginTransaction();
trans.add(myFrag, "MyDialogFragmentTag");
trans.commitAllowingStateLoss();

他の投稿のいくつかで述べたように、状態の損失を伴うコミットは、注意しないと問題を引き起こす可能性があります...私の場合は、ダイアログを閉じるボタンでユーザーにエラーメッセージを表示するだけでしたので、その状態は失われますが、大したことではありません。

お役に立てれば...

2
Justin

それは古い質問ですが、私は最も簡単な方法で解決したと思います:

getActivity().runOnUiThread(new Runnable() {
    @Override
        public void run() {
            MsgUtils.toast(getString(R.string.msg_img_saved),
                    getActivity().getApplicationContext());
        }
    });
2
LucasBatalha

これは、#onActivityResult()が呼び出されたときに、親アクティビティが既に#onSaveInstanceState()を呼び出しているために発生します

Runnableを使用して、#onActivityResult()でアクション(ダイアログを表示)を「保存」して、後でアクティビティの準備ができたときに使用します。

このアプローチを使用すると、目的のアクションが常に機能するようになります。

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == YOUR_REQUEST_CODE) {
        mRunnable = new Runnable() {
            @Override
            public void run() {
                showDialog();
            }
        };
    } else {
        super.onActivityResult(requestCode, resultCode, data);
    }
}

@Override
public void onStart() {
    super.onStart();
    if (mRunnable != null) {
        mRunnable.run();
        mRunnable = null;
    }
}
2
Ricard

フラグメントを処理する前にsuper.onActivityResult(requestCode, resultCode, data);を呼び出すだけです

0
Thomas Klammer

私が見つけた最もクリーンなソリューションはこれです:

@Override
public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
    new Handler().post(new Runnable() {
        @Override
        public void run() {
            onActivityResultDelayed(requestCode, resultCode, data);
        }
    });
}

public void onActivityResultDelayed(int requestCode, int resultCode, Intent data) {
    // Move your onActivityResult() code here.
}
0
fhucho

アクティビティで.show(getSupportFragmentManager(), "MyDialog");を実行中にこのエラーが発生しました。

最初に.show(getSupportFragmentManager().beginTransaction(), "MyDialog");を試してください。

それでも機能しない場合は、この投稿( onActivityResultのDialogFragmentを表示 )で問題を解決できます。

0
Youngjae

別の方法:

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    switch (requestCode) {
        case Activity.RESULT_OK:
            new Handler(new Handler.Callback() {
                @Override
                public boolean handleMessage(Message m) {
                    showErrorDialog(msg);
                    return false;
                }
            }).sendEmptyMessage(0);
            break;
        default:
            super.onActivityResult(requestCode, resultCode, data);
    }
}


private void showErrorDialog(String msg) {
    // build and show dialog here
}
0
Maher Abuthraa