Android 从 onActivityResult 显示 DialogFragment

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10114324/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 02:32:04  来源:igfitidea点击:

Show DialogFragment from onActivityResult

androidandroid-fragmentsandroid-dialogfragment

提问by Kurtis Nusbaum

I have the following code in my onActivityResult for a fragment of mine:

我的 onActivityResult 中有以下代码用于我的片段:

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

However, I'm getting the following error:

但是,我收到以下错误:

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

Anybody know what's going on, or how I can fix this? I should note I'm using the Android Support Package.

有谁知道发生了什么,或者我该如何解决这个问题?我应该注意到我正在使用 Android 支持包。

采纳答案by Arcao

If you use Android support library, onResume method isn't the right place, where to play with fragments. You should do it in onResumeFragments method, see onResume method description: http://developer.android.com/reference/android/support/v4/app/FragmentActivity.html#onResume%28%29

如果您使用 Android 支持库,则 onResume 方法不是正确的地方,在哪里玩片段。您应该在 onResumeFragments 方法中执行此操作,请参阅 onResume 方法说明:http: //developer.android.com/reference/android/support/v4/app/FragmentActivity.html#onResume%28%29

So the correct code from my point of view should be:

所以从我的角度来看,正确的代码应该是:

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

回答by Kurtis Nusbaum

EDIT: Not a bug, but more of a deficiency in the fragments framework. The better answer to this question is the one provided by @Arcao above.

编辑:不是错误,而是片段框架中的更多缺陷。这个问题的更好答案是上面@Arcao 提供的答案。

---- Original post ----

---- 原帖----

Actually it's a known bugwith the support package (edit: not actually a bug. see @alex-lockwood's comment). A posted work around in the comments of the bug report is to modify the source of the DialogFragment like so:

实际上,这是支持包的一个已知错误(编辑:实际上不是错误。请参阅@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;
}

Note this is a giant hack. The way I actually did it was just make my own dialog fragment that I could register with from the original fragment. When that other dialog fragment did things (like be dismissed), it told any listeners that it was going away. I did it like this:

请注意,这是一个巨大的黑客攻击。我实际上这样做的方式只是制作我自己的对话片段,我可以从原始片段中注册。当另一个对话片段做了一些事情(比如被解雇)时,它告诉任何听众它正在消失。我是这样做的:

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

So now I have a way to notify the PlayerListFragment when things happen. Note that its very important that you call unregisterPasswordEnteredListener appropriately (in the above case when ever the PlayerListFragment "goes away") otherwise this dialog fragment might try to call functions on the registered listener when that listener doesn't exist any more.

所以现在我有办法在事情发生时通知 PlayerListFragment。请注意,正确调用 unregisterPasswordEnteredListener 非常重要(在上述情况下,当 PlayerListFragment “消失”时),否则当该侦听器不再存在时,此对话框片段可能会尝试调用已注册侦听器上的函数。

回答by twig

The comment left by @Natixis a quick one-liner that some people may have removed.

@Natix留下的评论是一些人可能已删除的快速单行评论。

The simplest solution to this problem is to call super.onActivityResult()BEFORE running your own code. This works regardless if you're using the support library or not and maintains behavioural consistency in your Activity.

这个问题最简单的解决方案是在运行你自己的代码之前调用super.onActivityResult()。无论您是否使用支持库,这都有效,并在您的 Activity 中保持行为一致性。

There is:

有:

The more I read into this the more insane hacks I've seen.

我读的越多,我看到的越疯狂的黑客攻击。

If you're still running into issues, then the one by Alex Lockwoodis the one to check.

如果您仍然遇到问题,那么可以检查Alex Lockwood的那个。

回答by hrnt

I believe it is an Android bug. Basically Android calls onActivityResult at a wrong point in the activity/fragment lifecycle (before onStart()).

我相信这是一个Android错误。基本上,Android 在活动/片段生命周期中的错误点(在 onStart() 之前)调用 onActivityResult。

The bug is reported at https://issuetracker.google.com/issues/36929762

该错误报告在https://issuetracker.google.com/issues/36929762

I solved it by basically storing the Intent as a parameter I later processed in onResume().

我基本上通过将 Intent 作为参数存储在 onResume() 中来解决它。

[EDIT] There are nowadays better solutions for this issue that were not available back in 2012. See the other answers.

[编辑] 对于这个问题,现在有更好的解决方案,但在 2012 年还没有。请参阅其他答案。

回答by Simon Jacobs

EDIT: Yet anotheroption, and possibly the best yet (or, at least what the support library expects...)

编辑:另一种选择,可能是最好的(或者,至少支持库所期望的......)

If you're using DialogFragments with the Android support library, you should be using a subclass of FragmentActivity. Try the following:

如果您将 DialogFragments 与 Android 支持库一起使用,则应该使用 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
}

I took a look at the sourcefor FragmentActivity, and it looks like it's calling an internal fragment manager in order to resume fragments without losing state.

我查看了 FragmentActivity的源代码,看起来它正在调用内部片段管理器,以便在不丢失状态的情况下恢复片段。



I found a solution that's not listed here. I create a Handler, and start the dialog fragment in the Handler. So, editing your code a bit:

我找到了此处未列出的解决方案。我创建了一个处理程序,并在处理程序中启动对话框片段。所以,稍微编辑一下你的代码:

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
}

This seems cleaner and less hacky to me.

这对我来说似乎更干净,更少黑客。

回答by gkee

There are two DialogFragment show() methods - show(FragmentManager manager, String tag)and show(FragmentTransaction transaction, String tag).

有两个 DialogFragment show() 方法 -show(FragmentManager manager, String tag)show(FragmentTransaction transaction, String tag).

If you want to use the FragmentManager version of the method (as in the original question), an easy solution is to override this method and use commitAllowingStateLoss:

如果您想使用该方法的 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();
  }

}

Overriding show(FragmentTransaction, String)this way is not as easy because it should also modify some internal variables within the original DialogFragment code, so I wouldn't recommend it - if you want to use that method, then try the suggestions in the accepted answer (or the comment from Jeffrey Blattman).

覆盖show(FragmentTransaction, String)这种方式并不容易,因为它还应该修改原始 DialogFragment 代码中的一些内部变量,所以我不推荐它 - 如果你想使用这种方法,然后尝试接受答案中的建议(或来自杰弗里·布拉特曼)。

There is some risk in using commitAllowingStateLoss - the documentation states "Like commit() but allows the commit to be executed after an activity's state is saved. This is dangerous because the commit can be lost if the activity needs to later be restored from its state, so this should only be used for cases where it is okay for the UI state to change unexpectedly on the user."

使用 commitAllowingStateLoss 存在一些风险 - 文档说明“像 commit() 但允许在保存活动状态后执行提交。这是危险的,因为如果活动稍后需要从其状态恢复,提交可能会丢失,所以这应该只用于用户界面状态可以意外更改的情况。”

回答by handrenliang

You cannot show dialog after attached activity called its method onSaveInstanceState(). Obviously, onSaveInstanceState() is called before onActivityResult(). So you should show your dialog in this callback method OnResumeFragment(), you do not need to override DialogFragment's show() method. Hope this will help you.

在附加活动调用其方法 onSaveInstanceState() 后,您无法显示对话框。显然,onSaveInstanceState() 在 onActivityResult() 之前被调用。所以你应该在这个回调方法 OnResumeFragment() 中显示你的对话框,你不需要覆盖 DialogFragment 的 show() 方法。希望这会帮助你。

回答by PearsonArtPhoto

I came up with a third solution, based partially from hmt's solution. Basically, create an ArrayList of DialogFragments, to be shown upon onResume();

我想出了第三个解决方案,部分基于 hmt 的解决方案。基本上,创建一个 DialogFragments ArrayList,在 onResume() 上显示;

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

回答by Eurig Jones

onActivityResult() executes before onResume(). You need to do your UI in onResume() or later.

onActivityResult() 在 onResume() 之前执行。您需要在 onResume() 或更高版本中完成您的 UI。

Use a boolean or whatever you need to communicate that a result has come back between both of these methods.

使用布尔值或任何你需要的东西来传达结果已经在这两种方法之间返回。

... That's it. Simple.

... 就是这样。简单的。

回答by Ricard

It happens because when #onActivityResult() is called, the parent activity has already call #onSaveInstanceState()

发生这种情况是因为当调用 #onActivityResult() 时,父 Activity 已经调用了 #onSaveInstanceState()

I would use a Runnable to "save" the action (show dialog) on #onActivityResult() to use it later when activity has been ready.

我将使用 Runnable 在 #onActivityResult() 上“保存”操作(显示对话框),以便稍后在活动准备就绪时使用它。

With this approach we make sure the action we want to will always work

通过这种方法,我们确保我们想要的操作始终有效

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