Hiển thị DialogFragment từ onActivityResult


82

Tôi có mã sau trong onActivityResult của mình cho một đoạn của tôi:

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

Tuy nhiên, tôi gặp lỗi sau:

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

Có ai biết chuyện gì đang xảy ra không, hoặc làm cách nào để tôi có thể sửa lỗi này? Tôi nên lưu ý rằng tôi đang sử dụng Gói hỗ trợ Android.

Câu trả lời:


75

Nếu bạn sử dụng thư viện hỗ trợ Android, phương pháp onResume không phải là nơi thích hợp, nơi có thể chơi với các mảnh vỡ. Bạn nên làm điều đó trong phương thức onResumeFragment, xem mô tả phương thức onResume: http://developer.android.com/reference/android/support/v4/app/FragmentActivity.html#onResume%28%29

Vì vậy, mã chính xác theo quan điểm của tôi phải là:

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

13
+1, đây là câu trả lời chính xác. Lưu ý rằng onResumeFragments()không tồn tại trong Activitylớp. Nếu bạn đang sử dụng cơ bản Activity, bạn nên sử dụng onPostResume()thay thế.
Alex Lockwood

3
Trước khi thực hiện giải pháp này, vui lòng đọc phần này để biết lý do tại sao đó là một cuộc tấn công. Có một giải pháp đơn giản hơn nhiều ẩn trong các bình luận của một giải pháp khác trong câu hỏi này.
cành cây

1
Việc gọi super.onActivityResult không ngăn được IllegalStateException, do đó không phải là bản sửa lỗi của subj. vấn đề
demaksee

1
Câu hỏi này là lần truy cập đầu tiên trên google cho vấn đề này, nhưng câu trả lời được chấp nhận không phải là câu trả lời tốt nhất theo quan điểm của tôi. Câu trả lời này nên được chấp nhận thay vào đó: stackoverflow.com/a/30429551/1226020
JHH

27

CHỈNH SỬA: Không phải lỗi, mà là do thiếu sót trong khung phân đoạn. Câu trả lời tốt hơn cho câu hỏi này là câu trả lời được cung cấp bởi @Arcao ở trên.

---- Bài gốc ----

Trên thực tế, đó là một lỗi đã biết với gói hỗ trợ (chỉnh sửa: không thực sự là một lỗi. Xem nhận xét của @ alex-lockwood). Một công việc được đăng xung quanh các nhận xét của báo cáo lỗi là sửa đổi nguồn của DialogFragment như sau:

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

Lưu ý đây là một vụ hack khổng lồ. Cách tôi thực sự đã làm là chỉ tạo một đoạn hộp thoại của riêng tôi mà tôi có thể đăng ký từ đoạn gốc. Khi đoạn hộp thoại khác đó thực hiện những điều (như bị loại bỏ), nó nói với bất kỳ người nghe nào rằng nó sẽ biến mất. Tôi đã làm nó như thế này:

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

Vì vậy, bây giờ tôi có một cách để thông báo cho PlayerListFragment khi sự việc xảy ra. Lưu ý rằng điều rất quan trọng là bạn phải gọi unregisterPasswordEnteredListener một cách thích hợp (trong trường hợp trên là khi PlayerListFragment "biến mất") nếu không phân đoạn hộp thoại này có thể cố gắng gọi các hàm trên trình nghe đã đăng ký khi trình nghe đó không còn tồn tại nữa.


3
một giải pháp không yêu cầu sao chép nguồn ... chỉ cần ghi đè show()và bắt IllegalStateException.
Jeffrey Blattman

1
Làm cách nào để sửa đổi nguồn của DialogFragment? Hoặc bạn có thể đăng giải pháp của bạn được đề cập ở cuối bài đăng của bạn?
Piotr Ślesarew 23/11/12

1
@PeterSlesarew Tôi đã đăng giải pháp (khá cụ thể) của mình.
Kurtis Nusbaum

9
Gah, đây không phải là lỗi! Khung công tác Android cố ý ném ngoại lệ vì nó không an toàn để thực hiện các giao dịch phân mảnh bên trong onActivityResult()! Hãy thử giải pháp này thay vì: stackoverflow.com/questions/16265733/...
Alex Lockwood

2
@AlexLockwood Tài liệu không cảnh báo về điều này khi câu hỏi này được hỏi. Ngoài ra, mặc dù giải pháp của bạn hiện có vẻ tốt , nhưng nó đã không hoạt động trở lại vào tháng 4 năm 2012 onPostResumeonResumeFragmentsđều là những bổ sung tương đối mới cho thư viện hỗ trợ.
giờ

24

Nhận xét do @Natix để lại là một phần ngắn gọn mà một số người có thể đã xóa.

Giải pháp đơn giản nhất cho vấn đề này là gọi super.onActivityResult () TRƯỚC KHI chạy mã của riêng bạn. Điều này hoạt động bất kể bạn có đang sử dụng thư viện hỗ trợ hay không và duy trì tính nhất quán về hành vi trong Hoạt động của bạn.

Có:

Tôi càng đọc về điều này, tôi càng thấy nhiều vụ hack điên rồ hơn.

Nếu bạn vẫn gặp sự cố, thì giải pháp của Alex Lockwood là thứ cần kiểm tra.


Điều gì xảy ra nếu bạn có tài sản thừa kế? Việc gọi super.onActivityResult () có thể là một vấn đề nếu bạn muốn chạy mã của mình trước khi gọi super, siêu lớp có thể có mã riêng bên trong onActivityResult, hãy cẩn thận.
Ricard

Tôi đang thêm mã super.onActivityResult(requestCode, resultCode, data)trước bất kỳ mã nào của mình, nó đã khắc phục sự cố của tôi. Nhưng khi thêm kế thừa hoặc ghi đè onActivityResult mặc định, chúng ta nên xử lý onStart / onResume theo cách thủ công
mochadwi

14

Tôi tin rằng đó là một lỗi Android. Về cơ bản, Android gọi onActivityResult tại một điểm sai trong vòng đời hoạt động / phân mảnh (trước onStart ()).

Lỗi được báo cáo tại https://issuetracker.google.com/issues/36929762

Tôi đã giải quyết nó bằng cách lưu trữ Intent về cơ bản như một tham số mà sau này tôi đã xử lý trong onResume ().

[EDIT] Ngày nay, có những giải pháp tốt hơn cho vấn đề này mà chưa có từ năm 2012. Xem các câu trả lời khác.


8
Thực ra đó không hẳn là một lỗi. Như ra nhọn trong các ý kiến đó, nó rõ ràng khẳng định rằng onActivityResult()được gọi trướconResume()
Kurtis Nusbaum

2
Bạn đã đọc bình luận cuối cùng cho lỗi? Lỗi là onActivityResult () được gọi trước onStart (), không phải nó được gọi trước onResume ().
hrnt

À vâng, điều đó cũng đúng. Bỏ lỡ điều đó. Mặc dù tôi vẫn tin rằng báo cáo lỗi khác có liên quan hơn một chút đến vấn đề của tôi.
Kurtis Nusbaum

Nó được xác định rõ khi onActivityResult được gọi. Do đó, nó không thể là một lỗi, ngay cả khi nó có vẻ không phù hợp trong một số trường hợp.
sstn

1
@sstn, bạn có thể nói rõ hơn không? Nó được xác định rõ khi onActivityResult được gọi (= ngay trước onResume). Android không gọi onActivityResult ngay trước onResume. Vì vậy, nó là một lỗi.
hrnt

11

CHỈNH SỬA: Tuy nhiên, một tùy chọn khác và có thể là tốt nhất (hoặc, ít nhất là những gì thư viện hỗ trợ mong đợi ...)

Nếu bạn đang sử dụng DialogFragment với thư viện hỗ trợ Android, bạn nên sử dụng một lớp con của FragmentActivity. Hãy thử những cách sau:

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
}

Tôi đã xem xét nguồn cho FragmentActivity và có vẻ như nó đang gọi một trình quản lý phân mảnh nội bộ để tiếp tục các phân mảnh mà không bị mất trạng thái.


Tôi đã tìm thấy một giải pháp không được liệt kê ở đây. Tôi tạo một Trình xử lý và bắt đầu phân đoạn hộp thoại trong Trình xử lý. Vì vậy, hãy chỉnh sửa mã của bạn một chút:

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
}

Điều này có vẻ sạch sẽ hơn và ít hack hơn đối với tôi.


5
Việc sử dụng Trình xử lý để giải quyết vấn đề này chỉ làm tăng thêm độ trễ, do đó khó xảy ra sự cố hơn. Nhưng nó không tin rằng vấn đề sẽ biến mất! Nó giống như giải quyết các điều kiện chủng tộc bằng cách sử dụng Thread#sleep().
Alex Lockwood

27
Gọi điện super.onActivityResult()là giải pháp làm việc đơn giản nhất hiện có và nó có lẽ phải là câu trả lời được chấp nhận! Tôi vô tình nhận thấy cuộc gọi siêu bị thiếu và rất ngạc nhiên rằng việc thêm nó vẫn hoạt động. Điều này cho phép tôi xóa một trong những thủ thuật cũ được đề cập trong trang này (lưu hộp thoại vào một biến tạm thời và hiển thị nó trong onResume()).
Natix

Giải pháp tốt. Vấn đề duy nhất là onActivityResult()không trả về bất kỳ giá trị nào cho biết liệu các đoạn có xử lý kết quả hay không.
Michael

Gọi super.onActivityResult () không salve tai nạn IllegalStateException trong dự án của tôi
demaksee

9

Có hai phương thức DialogFragment show () - show(FragmentManager manager, String tag)show(FragmentTransaction transaction, String tag).

Nếu bạn muốn sử dụng phiên bản FragmentManager của phương thức (như trong câu hỏi ban đầu), một giải pháp dễ dàng là ghi đè phương thức này và sử dụng commitAllowingStateLoss:

public class MyDialogFragment extends DialogFragment {

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

}

Ghi đè show(FragmentTransaction, String)theo cách này không phải là dễ dàng vì nó cũng sẽ sửa đổi một số biến nội bộ trong mã DialogFragment ban đầu, vì vậy tôi không khuyên bạn nên thực hiện nó - nếu bạn muốn sử dụng phương pháp đó, hãy thử các đề xuất trong câu trả lời được chấp nhận (hoặc nhận xét từ Jeffrey Blattman).

Có một số rủi ro khi sử dụng commitAllowingStateLoss - tài liệu cho biết "Giống như commit () nhưng cho phép thực hiện cam kết sau khi trạng thái của hoạt động được lưu. Điều này rất nguy hiểm vì cam kết có thể bị mất nếu sau đó hoạt động cần được khôi phục từ trạng thái của nó , vì vậy điều này chỉ nên được sử dụng cho các trường hợp trạng thái giao diện người dùng có thể thay đổi bất ngờ đối với người dùng. "


4

Bạn không thể hiển thị hộp thoại sau khi hoạt động đính kèm được gọi là phương thức onSaveInstanceState () của nó. Rõ ràng, onSaveInstanceState () được gọi trước onActivityResult (). Vì vậy, bạn nên hiển thị hộp thoại của mình trong phương thức gọi lại này OnResumeFragment (), bạn không cần ghi đè phương thức show () của DialogFragment. Hy vọng điều này sẽ giúp bạn.


3

Tôi đã đưa ra giải pháp thứ ba, một phần dựa trên giải pháp của hmt. Về cơ bản, tạo một ArrayList of DialogFragment, được hiển thị trên 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");
}

3

onActivityResult () thực thi trước onResume (). Bạn cần thực hiện giao diện người dùng của mình trong onResume () hoặc mới hơn.

Sử dụng boolean hoặc bất cứ thứ gì bạn cần để thông báo rằng một kết quả đã trở lại giữa cả hai phương pháp này.

... Đó là nó. Đơn giản.


2

Tôi biết câu này đã được trả lời cách đây khá lâu .. nhưng có một cách dễ dàng hơn nhiều để thực hiện việc này so với một số câu trả lời khác mà tôi đã thấy ở đây ... Trong trường hợp cụ thể của tôi, tôi cần hiển thị DialogFragment từ một đoạn onActivityResult () phương pháp.

Đây là mã của tôi để xử lý điều đó và nó hoạt động rất đẹp:

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

Như đã đề cập trong một số bài viết khác, việc cam kết mất trạng thái có thể gây ra sự cố nếu bạn không cẩn thận ... trong trường hợp của tôi, tôi chỉ hiển thị thông báo lỗi cho người dùng bằng nút để đóng hộp thoại, vì vậy nếu tình trạng của nó bị mất nó không phải là một vấn đề lớn.

Hi vọng điêu nay co ich...


2

Đó là một câu hỏi cũ nhưng tôi đã giải quyết bằng cách đơn giản nhất, tôi nghĩ:

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

2

Điều này xảy ra bởi vì khi #onActivityResult () được gọi, hoạt động chính đã gọi #onSaveInstanceState ()

Tôi sẽ sử dụng Runnable để "lưu" hành động (hộp thoại hiển thị) trên #onActivityResult () để sử dụng sau khi hoạt động đã sẵn sàng.

Với cách tiếp cận này, chúng tôi đảm bảo rằng hành động chúng tôi muốn sẽ luôn hoạt động

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

0

Giải pháp sạch nhất mà tôi tìm thấy là:

@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

Tôi gặp lỗi này khi đang .show(getSupportFragmentManager(), "MyDialog");hoạt động.

Hãy thử .show(getSupportFragmentManager().beginTransaction(), "MyDialog");trước.

Nếu vẫn không hoạt động, bài đăng này ( Hiển thị DialogFragment từ onActivityResult ) giúp tôi giải quyết vấn đề.


0

Cách khác:

@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

chỉ cần gọi super.onActivityResult(requestCode, resultCode, data);trước khi xử lý phân mảnh


-3

Như các bạn đã biết vấn đề này là do onActivityResult () đang gọi trước onstart (), vì vậy chỉ cần gọi onstart () khi bắt đầu trong onActivityResult () như tôi đã làm trong đoạn mã này

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
      onStart();
      //write you code here
}

Bạn nên không bao giờ gọi các phương thức vòng đời Android trực tiếp. Chúng chỉ nên được gọi bởi hệ thống.
Chantell Osejo
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.