Nhận kết quả từ DialogFragment


232

tôi đang dùng DialogFragments cho một số thứ: chọn mục từ danh sách, nhập văn bản.

Cách tốt nhất để trả lại một giá trị (tức là một chuỗi hoặc một mục từ danh sách) trở lại hoạt động / đoạn gọi là gì?

Hiện tại tôi đang thực hiện hoạt động gọi điện DismissListenervà cung cấp cho DialogFragment một tham chiếu đến hoạt động. Hộp thoại sau đó gọiOnDimiss phương thức trong hoạt động và hoạt động lấy kết quả từ đối tượng DialogFragment. Rất lộn xộn và nó không hoạt động đối với thay đổi cấu hình (thay đổi hướng) vì DialogFragment mất tham chiếu đến hoạt động.

Cảm ơn vì bất kì sự giúp đỡ.


10
DialogFragments vẫn chỉ là những mảnh vỡ. Cách tiếp cận của bạn thực sự là cách được khuyến nghị cho các đoạn được sử dụng để nói lại hoạt động chính. developer.android.com/guide/topics/fundamentals/ từ
viết mã

1
Cảm ơn vì điều đó. Tôi đã rất gần (như bạn nói). Bit mà tài liệu được liên kết đã giúp tôi sử dụng onAttach () và truyền hoạt động cho người nghe.
James Cross

2
@codinguser, @Styx - "cung cấp cho DialogFragment một tham chiếu đến hoạt động" - chi tiết này có một chút rủi ro, vì cả Activityvà và DialogFragmentcó thể được tạo lại. Sử dụng Activitythông qua onAttach(Activity activity)là cách thích hợp và được đề nghị.
sstn

Câu trả lời:


246

Sử dụng myDialogFragment.setTargetFragment(this, MY_REQUEST_CODE)từ nơi bạn hiển thị hộp thoại, và sau đó khi hộp thoại của bạn kết thúc, từ đó bạn có thể gọi getTargetFragment().onActivityResult(getTargetRequestCode(), ...)và thực hiện onActivityResult()trong đoạn chứa.

Nó có vẻ như là một sự lạm dụng onActivityResult(), đặc biệt là vì nó không liên quan đến các hoạt động. Nhưng tôi đã thấy nó được đề xuất bởi những người chính thức của google và thậm chí có thể trong các bản demo api. Tôi nghĩ đó là những gì g/setTargetFragment()đã được thêm vào.


2
setTargetFragment đề cập rằng mã yêu cầu được sử dụng trong onActivityResult vì vậy tôi đoán sử dụng phương pháp này là ổn.
Giorgi

87
Nếu mục tiêu là một hoạt động thì sao?
Fernando Gallego

9
Nếu mục tiêu là hoạt động, tôi sẽ khai báo giao diện với phương thức như "void onActivityResult2 (int requestCode, int resultCode, Intent data)" và triển khai nó bằng một Activity. Trong DialogFragment, chỉ cần getActivity và kiểm tra giao diện này và gọi nó một cách thích hợp.
Ruslan Yanchyshyn

4
Đó là giải pháp không tốt. Nó sẽ không hoạt động sau khi lưu và khôi phục trạng thái phân đoạn hộp thoại. LocalBroadcastManager là giải pháp tốt nhất trong trường hợp này.
Nik

4
@Nik Điều đó không đúng. Đó là giải pháp tốt nhất. Không có vấn đề khi lưu và khôi phục nhà nước. Nếu bạn gặp sự cố, bạn đã sử dụng trình quản lý phân đoạn sai. Đoạn đích / người gọi vội sử dụng getChildFragmentManager () để hiển thị hộp thoại.
Không thể tin được vào ngày

140

Như bạn có thể thấy ở đây có một cách rất đơn giản để làm điều đó.

Trong bạn DialogFragmentthêm một trình nghe giao diện như:

public interface EditNameDialogListener {
    void onFinishEditDialog(String inputText);
}

Sau đó, thêm một tham chiếu đến người nghe đó:

private EditNameDialogListener listener;

Điều này sẽ được sử dụng để "kích hoạt" (các) phương thức người nghe và cũng để kiểm tra xem Activity / Fragment gốc có thực hiện giao diện này không (xem bên dưới).

Trong Activity/ FragmentActivity/ Fragmentmà "được gọi là"DialogFragment chỉ đơn giản là thực hiện giao diện này.

Trong DialogFragmenttất cả những gì bạn cần thêm vào thời điểm bạn muốn loại bỏ DialogFragmentvà trả về kết quả là đây:

listener.onFinishEditDialog(mEditText.getText().toString());
this.dismiss();

Đâu mEditText.getText().toString()là những gì sẽ được chuyển trở lại cuộc gọi Activity.

Lưu ý rằng nếu bạn muốn trả về một cái gì đó khác, chỉ cần thay đổi các đối số mà người nghe thực hiện.

Cuối cùng, bạn nên kiểm tra xem giao diện có thực sự được thực hiện bởi hoạt động / đoạn cha hay không:

@Override
public void onAttach(Context context) {
    super.onAttach(context);
    // Verify that the host activity implements the callback interface
    try {
        // Instantiate the EditNameDialogListener so we can send events to the host
        listener = (EditNameDialogListener) context;
    } catch (ClassCastException e) {
        // The activity doesn't implement the interface, throw exception
        throw new ClassCastException(context.toString()
                + " must implement EditNameDialogListener");
    }
}

Kỹ thuật này rất linh hoạt và cho phép gọi lại với kết quả ngay cả khi bạn chưa muốn bỏ qua hộp thoại.


13
Điều này hoạt động rất tốt với Activity'và FragmentActivity' nhưng nếu người gọi là a Fragment?
Brais Gabin

1
Tôi không chắc là tôi hoàn toàn hiểu bạn. Nhưng nó sẽ hoạt động như nhau nếu người gọi là a Fragment.
Assaf Gamliel

2
Nếu người gọi là một người Fragmentthì bạn có thể thực hiện một số điều: 1. Truyền đoạn đó làm tài liệu tham khảo (Có thể không phải là ý tưởng hay vì bạn có thể gây rò rỉ bộ nhớ). 2. Sử dụng FragmentManagervà gọi findFragmentByIdhoặc findFragmentByTagnó sẽ nhận được các mảnh tồn tại trong hoạt động của bạn. Tôi hy vọng nó đã giúp. Có một ngày tuyệt vời!
Assaf Gamliel

5
Vấn đề với cách tiếp cận này là phân đoạn không tốt trong việc giữ lại đối tượng vì chúng được tạo lại, ví dụ, cố gắng thay đổi hướng, HĐH sẽ tạo lại đoạn đó nhưng phiên bản của trình nghe sẽ không còn nữa
Necronet

5
@LOGiah nhìn vào câu trả lời của @ Timmmm. setTargetFragment()getTargetFragment()là ma thuật.
Brais Gabin

48

Có một cách đơn giản hơn nhiều để nhận kết quả từ DialogFragment.

Đầu tiên, trong Activity, Fragment hoặc FragmentActivity của bạn, bạn cần thêm vào thông tin sau:

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    // Stuff to do, dependent on requestCode and resultCode
    if(requestCode == 1) { // 1 is an arbitrary number, can be any int
         // This is the return result of your DialogFragment
         if(resultCode == 1) { // 1 is an arbitrary number, can be any int
              // Now do what you need to do after the dialog dismisses.
         }
     }
}

Các requestCodecơ bản là nhãn int của bạn cho DialogFragment bạn gọi, tôi sẽ chỉ cho cách thức hoạt động này trong một giây. Mã kết quả là mã mà bạn gửi lại từ DialogFragment cho biết Hoạt động chờ đợi hiện tại của bạn, Fragment hoặc FragmentActivity đã xảy ra.

Đoạn mã tiếp theo được gọi là cuộc gọi đến DialogFragment. Một ví dụ ở đây:

DialogFragment dialogFrag = new MyDialogFragment();
// This is the requestCode that you are sending.
dialogFrag.setTargetFragment(this, 1);     
// This is the tag, "dialog" being sent.
dialogFrag.show(getFragmentManager(), "dialog");

Với ba dòng này, bạn đang khai báo DialogFragment của mình, đặt requestCode (sẽ gọi onActivityResult (...) sau khi Dialog bị loại bỏ và sau đó bạn sẽ hiển thị hộp thoại. Thật đơn giản.

Bây giờ, trong DialogFragment của bạn, bạn chỉ cần thêm một dòng trực tiếp trước dismiss()để bạn gửi một kết quả Mã trở lại onActivityResult ().

getTargetFragment().onActivityResult(getTargetRequestCode(), resultCode, getActivity().getIntent());
dismiss();

Đó là nó. Lưu ý, Mã kết quả được xác định theo cách int resultCodetôi đã đặt resultCode = 1;trong trường hợp này.

Thế là xong, giờ đây bạn có thể gửi kết quả DialogFragment của mình trở lại Hoạt động gọi điện thoại, Fragment hoặc FragmentActivity.

Ngoài ra, có vẻ như thông tin này đã được đăng trước đó, nhưng không có một ví dụ đầy đủ nào được đưa ra nên tôi nghĩ tôi sẽ cung cấp thêm chi tiết.

EDIT 06.24.2016 Tôi xin lỗi vì mã sai lệch ở trên. Nhưng bạn chắc chắn không thể nhận được kết quả quay lại hoạt động được xem là dòng:

dialogFrag.setTargetFragment(this, 1);

đặt mục tiêu Fragmentvà không Activity. Vì vậy, để làm điều này, bạn cần sử dụng hiện thực InterfaceCommunicator.

Trong DialogFragmenttập hợp của bạn một biến toàn cục

public InterfaceCommunicator interfaceCommunicator;

Tạo một chức năng công cộng để xử lý nó

public interface InterfaceCommunicator {
    void sendRequestCode(int code);
}

Sau đó, khi bạn đã sẵn sàng để gửi mã trở lại Activitykhi DialogFragmentđược thực hiện chạy, bạn chỉ cần thêm dòng trước khi bạn dismiss();của bạn DialogFragment:

interfaceCommunicator.sendRequestCode(1); // the parameter is any int code you choose.

Trong hoạt động của bạn bây giờ bạn phải làm hai việc, việc đầu tiên là loại bỏ một dòng mã không còn áp dụng được:

dialogFrag.setTargetFragment(this, 1);  

Sau đó thực hiện giao diện và bạn đã hoàn tất. Bạn có thể làm điều đó bằng cách thêm dòng sau vào implementsmệnh đề ở đầu lớp:

public class MyClass Activity implements MyDialogFragment.InterfaceCommunicator

Và sau đó @Overridelà chức năng trong hoạt động,

@Override
public void sendRequestCode(int code) {
    // your code here
}

Bạn sử dụng phương thức giao diện này giống như onActivityResult()phương pháp của bạn. Ngoại trừ phương thức giao diện là cho DialogFragmentsvà khác là cho Fragments.


4
Cách tiếp cận này sẽ không hoạt động nếu mục tiêu là Hoạt động vì bạn không thể gọi onActivityResult (từ DialogFragment của bạn) do mức truy cập được bảo vệ.
Ruslan Yanchyshyn

2
Đó chỉ đơn giản là không đúng sự thật. Tôi sử dụng mã chính xác này trong các dự án của tôi. Đó là nơi tôi đã kéo nó từ và nó hoạt động tốt. Hãy nhớ nếu bạn gặp vấn đề về cấp độ truy cập được bảo vệ này, bạn có thể thay đổi cấp độ truy cập của mình cho bất kỳ phương thức và lớp nào từ được bảo vệ sang riêng tư hoặc công khai nếu cần thiết.
Brandon

6
Xin chào, bạn nói rằng bạn có thể gọi dialogFrag.setTargetFragment(this, 1)từ một Hoạt động, nhưng phương thức này nhận được một Đoạn làm đối số đầu tiên, vì vậy điều này không thể được bỏ. Tôi có đúng không
MondKin

1
Tôi sẽ gửi một số câu trả lời cho tất cả các bạn trong một vài để giải thích các hoạt động.
Brandon

1
@Swift @lcompare có lẽ bạn cần ghi đè onAttach (Ngữ cảnh ngữ cảnh) trong DialogFragment của bạn. Giống như vậy:@Override public void onAttach(Context context) { super.onAttach(context); yourInterface = (YourInterface) context; }
lidkxx

20

Vâng, quá muộn có thể là để trả lời nhưng đây là những gì tôi đã làm để lấy lại kết quả từ DialogFragment. rất giống với câu trả lời của @ brandon. Tôi đang gọi đâyDialogFragment từ một đoạn, chỉ cần đặt mã này nơi bạn đang gọi hộp thoại của bạn.

FragmentManager fragmentManager = getFragmentManager();
            categoryDialog.setTargetFragment(this,1);
            categoryDialog.show(fragmentManager, "dialog");

nơi categoryDialoglà của tôi DialogFragmentmà tôi muốn gọi và sau này trong việc thực hiện lại dialogfragmentnơi mã này, nơi bạn đang thiết lập dữ liệu của bạn trong ý định. Giá trị resultCodelà 1 bạn có thể đặt nó hoặc sử dụng hệ thống Xác định.

            Intent intent = new Intent();
            intent.putExtra("listdata", stringData);
            getTargetFragment().onActivityResult(getTargetRequestCode(), resultCode, intent);
            getDialog().dismiss();

bây giờ là lúc để quay lại đoạn gọi và thực hiện phương thức này. kiểm tra tính hợp lệ của dữ liệu hoặc kết quả thành công nếu bạn muốn với resultCoderequestCodetrong điều kiện.

 @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);        
        //do what ever you want here, and get the result from intent like below
        String myData = data.getStringExtra("listdata");
Toast.makeText(getActivity(),data.getStringExtra("listdata"),Toast.LENGTH_SHORT).show();
    }

10

Cách tiếp cận khác nhau, để cho phép một mảnh vỡ giao tiếp với Hoạt động của nó :

1) Xác định một giao diện công khai trong đoạn và tạo một biến cho nó

public OnFragmentInteractionListener mCallback;

public interface OnFragmentInteractionListener {
    void onFragmentInteraction(int id);
}

2) Truyền hoạt động cho biến mCallback trong đoạn

try {
    mCallback = (OnFragmentInteractionListener) getActivity();
} catch (Exception e) {
    Log.d(TAG, e.getMessage());
}

3) Thực hiện người nghe trong hoạt động của bạn

public class MainActivity extends AppCompatActivity implements DFragment.OnFragmentInteractionListener  {
     //your code here
}

4) Ghi đè OnFragmentInteraction trong hoạt động

@Override
public void onFragmentInteraction(int id) {
    Log.d(TAG, "received from fragment: " + id);
}

Thông tin thêm về nó: https://developer.android.com/training/basics/fragments/cransicating.html


Cảm ơn vì đã tóm tắt nó rất tốt. Chỉ cần một lưu ý cho người khác, hướng dẫn dành cho Android Devs gợi ý ghi đè lên public void onAttachđoạn và thực hiện hoạt động truyền hình ở đó
Big_Chair 13/03/19

8

Một cách dễ dàng tôi tìm thấy là như sau: Thực hiện đây là hộp thoại của bạn,

  CallingActivity callingActivity = (CallingActivity) getActivity();
  callingActivity.onUserSelectValue("insert selected value here");
  dismiss();

Và sau đó, trong hoạt động được gọi là Đoạn thoại tạo ra hàm thích hợp như sau:

 public void onUserSelectValue(String selectedValue) {

        // TODO add your implementation.
      Toast.makeText(getBaseContext(), ""+ selectedValue, Toast.LENGTH_LONG).show();
    }

Toast là để cho thấy rằng nó hoạt động. Đã làm cho tôi.


Tôi không chắc liệu đó có phải là một cách đúng đắn để làm điều đó không, nhưng chắc chắn nó hoạt động :)
soshial 6/2/2016

Sử dụng tốt hơn Interfacethay vì khớp nối cứng với các lớp bê tông.
waqaslam

6

Tôi rất ngạc nhiên khi thấy rằng không ai đã đề nghị sử dụng các chương trình phát sóng địa phương DialogFragmentđể Activityliên lạc! Tôi thấy nó đơn giản và gọn gàng hơn nhiều so với những gợi ý khác. Về cơ bản, bạn đăng ký Activityđể nghe các chương trình phát sóng và bạn gửi các chương trình phát sóng địa phương từ các DialogFragmentphiên bản của bạn . Đơn giản. Để được hướng dẫn từng bước về cách thiết lập tất cả, xem tại đây .


2
Tôi thích giải pháp đó, đây được coi là một thực tiễn tốt hay tốt nhất trong Android?
Benjamin Scharbau

1
Tôi thực sự thích hướng dẫn, cảm ơn bạn đã đăng nó. Tôi muốn thêm rằng tùy thuộc vào những gì bạn đang cố gắng thực hiện một trong hai phương pháp có thể hữu ích hơn phương pháp kia. Tôi sẽ đề xuất tuyến phát sóng cục bộ nếu bạn có nhiều đầu vào / kết quả được gửi trở lại hoạt động từ hộp thoại. Tôi sẽ khuyên bạn nên sử dụng tuyến onActivityResult nếu đầu ra của bạn rất cơ bản / đơn giản. Vì vậy, để trả lời câu hỏi thực hành tốt nhất, nó phụ thuộc vào những gì bạn đang cố gắng thực hiện!
Brandon

1
@AdilHussain Bạn nói đúng. Tôi đã đưa ra giả định rằng mọi người đang sử dụng Mảnh vỡ trong Hoạt động của họ. Tùy chọn setTargetFragment rất tuyệt nếu bạn giao tiếp với Fragment và DialogFragment. Nhưng bạn cần sử dụng phương thức Phát sóng khi đó là Hoạt động gọi DialogFragment.
Brandon

3
Vì tình yêu của Foo, đừng sử dụng chương trình phát sóng !! Nó mở ứng dụng của bạn cho một vấn đề bảo mật. Ngoài ra tôi thấy rằng ứng dụng Android tồi tệ nhất mà tôi phải làm việc trong các chương trình phát sóng lạm dụng. Bạn có thể nghĩ ra một cách tốt hơn để làm cho mã hoàn toàn không sử dụng được không? Bây giờ tôi phải root các máy thu quảng bá, thay vì một dòng mã XÓA? Để rõ ràng có những cách sử dụng cho các chương trình phát sóng, nhưng không phải trong bối cảnh này! KHÔNG BAO GIỜ trong bối cảnh này! Nó chỉ cẩu thả. Địa phương hay không. Gọi lại là tất cả những gì bạn cần.
StarWind0

1
Cũng như Guava EventBus, một tùy chọn khác là GreenRobot EventBus . Tôi chưa sử dụng Guava EventBus nhưng đã sử dụng GreenRobot EventBus và đã có trải nghiệm tốt với nó. Đẹp và đơn giản để sử dụng. Để biết một ví dụ nhỏ về cách kiến ​​trúc một ứng dụng Android để sử dụng GreenRobot EventBus, xem tại đây .
Adil Hussain

3

Hoặc chia sẻ ViewModel như hiển thị ở đây:

public class SharedViewModel extends ViewModel {
    private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

    public void select(Item item) {
        selected.setValue(item);
    }

    public LiveData<Item> getSelected() {
        return selected;
    }
}


public class MasterFragment extends Fragment {
    private SharedViewModel model;
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        itemSelector.setOnClickListener(item -> {
            model.select(item);
        });
    }
}

public class DetailFragment extends Fragment {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        model.getSelected().observe(this, { item ->
           // Update the UI.
        });
    }
}

https://developer.android.com/topic/lologists/arch architecture / viewmodel #shared_data_b between_fragments


2

Trong trường hợp của tôi, tôi cần chuyển các đối số cho một TargetFragment. Nhưng tôi đã có ngoại lệ "Đoạn đã hoạt động". Vì vậy, tôi đã khai báo một Giao diện trong DialogFragment mà ParentFragment đã triển khai. Khi ParentFragment bắt đầu DialogFragment, nó sẽ tự đặt thành TargetFragment. Sau đó, trong DialogFragment, tôi đã gọi

 ((Interface)getTargetFragment()).onSomething(selectedListPosition);

2

Ở Kotlin

    // My DialogFragment
class FiltroDialogFragment : DialogFragment(), View.OnClickListener {
    
    var listener: InterfaceCommunicator? = null

    override fun onAttach(context: Context?) {
        super.onAttach(context)
        listener = context as InterfaceCommunicator
    }

    interface InterfaceCommunicator {
        fun sendRequest(value: String)
    }   

    override fun onClick(v: View) {
        when (v.id) {
            R.id.buttonOk -> {    
        //You can change value             
                listener?.sendRequest('send data')
                dismiss()
            }
            
        }
    }
}

// Hoạt động của tôi

class MyActivity: AppCompatActivity(),FiltroDialogFragment.InterfaceCommunicator {

    override fun sendRequest(value: String) {
    // :)
    Toast.makeText(this, value, Toast.LENGTH_LONG).show()
    }
}
 

Tôi hy vọng nó phục vụ, nếu bạn có thể cải thiện xin vui lòng chỉnh sửa nó. Tiếng anh của tôi không tốt lắm


Trong trường hợp của tôi, hộp thoại được tạo từ một đoạn không hoạt động để giải pháp này không hoạt động. Nhưng tôi thích nụ cười bạn đặt :)
Ssenyonjo

0

nếu bạn muốn gửi đối số và nhận kết quả từ đoạn thứ hai, bạn có thể sử dụng Fragment.setArgument để hoàn thành nhiệm vụ này

static class FirstFragment extends Fragment {
    final Handler mUIHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case 101: // receive the result from SecondFragment
                Object result = msg.obj;
                // do something according to the result
                break;
            }
        };
    };

    void onStartSecondFragments() {
        Message msg = Message.obtain(mUIHandler, 101, 102, 103, new Object()); // replace Object with a Parcelable if you want to across Save/Restore
                                                                               // instance
        putParcelable(new SecondFragment(), msg).show(getFragmentManager().beginTransaction(), null);
    }
}

static class SecondFragment extends DialogFragment {
    Message mMsg; // arguments from the caller/FirstFragment

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onViewCreated(view, savedInstanceState);
        mMsg = getParcelable(this);
    }

    void onClickOK() {
        mMsg.obj = new Object(); // send the result to the caller/FirstFragment
        mMsg.sendToTarget();
    }
}

static <T extends Fragment> T putParcelable(T f, Parcelable arg) {
    if (f.getArguments() == null) {
        f.setArguments(new Bundle());
    }
    f.getArguments().putParcelable("extra_args", arg);
    return f;
}
static <T extends Parcelable> T getParcelable(Fragment f) {
    return f.getArguments().getParcelable("extra_args");
}

-3

Chỉ cần có nó như một trong các tùy chọn (vì chưa có ai đề cập đến nó) - bạn có thể sử dụng xe buýt sự kiện như Otto. Vì vậy, trong hộp thoại bạn làm:

bus.post(new AnswerAvailableEvent(42));

Và yêu cầu người gọi của bạn (Hoạt động hoặc Đoạn) đăng ký với nó:

@Subscribe public void answerAvailable(AnswerAvailableEvent event) {
   // TODO: React to the event somehow!
}
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.