มีวิธีการทำงานเช่น start fragment for result หรือไม่?


95

ขณะนี้ฉันมีส่วนในภาพซ้อนทับ นี่คือการลงชื่อเข้าใช้บริการ ในแอพโทรศัพท์แต่ละขั้นตอนที่ฉันต้องการแสดงในภาพซ้อนทับคือหน้าจอและกิจกรรมของตัวเอง ขั้นตอนการลงชื่อเข้าใช้มี 3 ส่วนและแต่ละส่วนมีกิจกรรมของตนเองที่ถูกเรียกด้วย startActivityForResult ()

ตอนนี้ฉันต้องการทำสิ่งเดียวกันโดยใช้ชิ้นส่วนและภาพซ้อนทับ ภาพซ้อนทับจะแสดงส่วนที่เกี่ยวข้องกับแต่ละกิจกรรม ปัญหาคือชิ้นส่วนเหล่านี้โฮสต์อยู่ในกิจกรรมใน Honeycomb API ฉันสามารถทำให้ส่วนแรกทำงานได้ แต่ฉันต้อง startActivityForResult () ซึ่งเป็นไปไม่ได้ มีบางอย่างตามบรรทัดของ startFragmentForResult () ที่ฉันสามารถเริ่มต้นส่วนใหม่ได้หรือไม่และเมื่อเสร็จแล้วจะส่งคืนผลลัพธ์ไปยังส่วนก่อนหน้าหรือไม่

คำตอบ:


58

Fragments ทั้งหมดอยู่ในกิจกรรม การเริ่มต้น Fragment เพื่อให้ได้ผลลัพธ์นั้นไม่สมเหตุสมผลมากนักเนื่องจากกิจกรรมที่เป็นที่เก็บนั้นสามารถเข้าถึงได้เสมอและในทางกลับกัน หาก Fragment ต้องการส่งต่อผลลัพธ์ก็สามารถเข้าถึงกิจกรรมและตั้งค่าผลลัพธ์และทำให้เสร็จได้ ในกรณีของการแลกเปลี่ยน Fragments ในกิจกรรมเดียวกิจกรรมนั้นยังคงสามารถเข้าถึงได้โดย Fragments ทั้งสองและข้อความทั้งหมดของคุณที่ส่งผ่านก็สามารถผ่านกิจกรรมได้

เพียงจำไว้ว่าคุณมีการสื่อสารระหว่าง Fragment กับกิจกรรมของมันเสมอ การเริ่มต้นและจบด้วยผลลัพธ์เป็นกลไกในการสื่อสารระหว่าง Activities - จากนั้น Activities สามารถมอบหมายข้อมูลที่จำเป็นให้กับ Fragments ได้


11
นอกจากนี้เมื่อโหลดส่วนหนึ่งจากส่วนอื่นคุณสามารถตั้งค่าแฟรกเมนต์เป้าหมายและเรียกกลับไปยังonActivityResultเมธอดของพาเรนต์แฟรกเมนต์ได้หากต้องการ
PJL

4
คำตอบของคุณไม่สมบูรณ์เศษกำลังใช้วงจรชีวิตเหมือนกิจกรรม ดังนั้นเราจึงไม่สามารถส่งผ่านอาร์กิวเมนต์ผ่านวิธีการได้ แต่เราควรใช้บันเดิลเพื่อส่งผ่านค่า แม้ว่าชิ้นส่วนจะตั้งค่าไว้ที่ใดที่หนึ่ง แต่เราจำเป็นต้องรู้ว่าเขาสิ้นสุดเมื่อใด ชิ้นส่วนก่อนหน้าควรได้รับค่าเมื่อเริ่มต้น / ดำเนินการต่อหรือไม่? นี่คือความคิด แต่ไม่มีวิธีที่เหมาะสมในการจัดเก็บค่าชิ้นส่วนสามารถเรียกใช้โดยชิ้นส่วน / กิจกรรมอื่น ๆ ได้
Loenix

1
มันสมเหตุสมผลแล้วที่จะเรียกว่า 'start fragment for result' เพราะมันจะให้ความยืดหยุ่นมากกว่ามาก -> ทำไมแฟรกเมนต์ตอนเด็ก ๆ ควรรู้เกี่ยวกับพาเรนต์? ส่วนย่อยจะไม่สะอาดมากนักในการทำงานและส่งคืนผลลัพธ์ไม่ว่าเขาจะอยู่ในกิจกรรมใดก็ตามโดยเฉพาะอย่างยิ่งในบริบทของแอปกิจกรรมเดียว
daneejela

แล้วหน่วยความจำรั่วที่อาจเกิดขึ้นล่ะ?
daneejela

60

หากคุณต้องการมีวิธีการสื่อสารระหว่าง Fragments

setTargetFragment(Fragment fragment, int requestCode)
getTargetFragment()
getTargetRequestCode()

คุณสามารถโทรกลับโดยใช้สิ่งเหล่านี้

Fragment invoker = getTargetFragment();
if(invoker != null) {
    invoker.callPublicMethod();
}

ฉันยังไม่ได้ยืนยัน แต่อาจจะใช้ได้ setTargetFragment()ดังนั้นคุณควรดูแลเกี่ยวกับการรั่วไหลของหน่วยความจำที่เกิดจากการอ้างอิงแบบวงกลมโดยการละเมิดของ
nagoya0

3
ทางออกที่ดีที่สุด! ใช้งานได้ดีเมื่อมีหนึ่งกิจกรรมและสแต็กของส่วนที่พยายามค้นหาส่วนที่เหมาะสมเพื่อแจ้งเตือนจะเป็นฝันร้าย ขอบคุณ
Damien Praca

1
@ userSeven7s สิ่งนี้ไม่สามารถใช้แทนเคสได้ แต่ควรใช้กับกรณีซ่อน
Muhammad Babar

1
@ nagoya0 จะดีกว่าถ้าเราใช้ WeakReference สำหรับชิ้นส่วนเป้าหมาย
quangson91

แม้ว่าsetTargetFragmentจะใช้งานได้ แต่ปัจจุบันเลิกใช้งานแล้ว ดูsetResultListenerในstackoverflow.com/a/61881149/2914140ที่นี่
CoolMind

11

เราสามารถแบ่งปันViewModelเดียวกันระหว่างชิ้นส่วน

SharedViewModel

import android.arch.lifecycle.MutableLiveData
import android.arch.lifecycle.ViewModel

class SharedViewModel : ViewModel() {

    val stringData: MutableLiveData<String> by lazy {
        MutableLiveData<String>()
    }

}

FirstFragment

import android.arch.lifecycle.Observer
import android.os.Bundle
import android.arch.lifecycle.ViewModelProviders
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup

class FirstFragment : Fragment() {

    private lateinit var sharedViewModel: SharedViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        activity?.run {
            sharedViewModel = ViewModelProviders.of(activity).get(SharedViewModel::class.java)
        }
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_first, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        sharedViewModel.stringData.observe(this, Observer { dateString ->
            // get the changed String
        })

    }

}

SecondFragment

import android.arch.lifecycle.ViewModelProviders
import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGrou

class SecondFragment : Fragment() {

    private lateinit var sharedViewModel: SharedViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        activity?.run {
            sharedViewModel = ViewModelProviders.of(activity).get(SharedViewModel::class.java)
        }
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_first, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        changeString()
    }

    private fun changeString() {
        sharedViewModel.stringData.value = "Test"
    }

}

3
สวัสดี Levon ฉันคิดว่าโค้ดด้านบนจะไม่แชร์อินสแตนซ์โมเดลมุมมองเดียวกัน คุณกำลังส่งผ่าน (แฟรกเมนต์) สำหรับ ViewModelProviders.of (this) .get (SharedViewModel :: class.java) สิ่งนี้จะสร้างสองอินสแตนซ์แยกกันสำหรับแฟรกเมนต์ คุณต้องผ่านกิจกรรม ViewModelProviders.of (activity) .get (SharedViewModel :: class.java)
Shailendra Patil

@ShailendraPatil จับดีฉันจะแก้ไขตอนนี้
Levon Petrosyan

8

เมื่อเร็ว ๆ นี้ Google เพิ่งเพิ่มความสามารถใหม่FragmentManagerที่ทำให้FragmentManagerสามารถทำหน้าที่เป็นร้านค้ากลางสำหรับผลลัพธ์ส่วนย่อย เราสามารถส่งผ่านข้อมูลไปมาระหว่าง Fragments ได้อย่างง่ายดาย

เริ่มต้นส่วนย่อย

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // Use the Kotlin extension in the fragment-ktx artifact
    setResultListener("requestKey") { key, bundle ->
        // We use a String here, but any type that can be put in a Bundle is supported
        val result = bundle.getString("bundleKey")
        // Do something with the result...
    }
}

Fragment ที่เราต้องการให้ผลลัพธ์กลับมา

button.setOnClickListener {
    val result = "result"
    // Use the Kotlin extension in the fragment-ktx artifact
    setResult("requestKey", bundleOf("bundleKey" to result))
}

ตัวอย่างนี้นำมาจากเอกสารอย่างเป็นทางการของ Google https://developer.android.com/training/basics/fragments/pass-data-between#kotlin

ณ วันที่เขียนคำตอบคุณลักษณะนี้ยังคงอยู่ในalphaสถานะ คุณสามารถทดลองใช้โดยใช้การอ้างอิงนี้

androidx.fragment:fragment:1.3.0-alpha05

4

2 เซ็นต์ของฉัน

ฉันสลับระหว่างชิ้นส่วนโดยการสลับชิ้นส่วนเก่ากับชิ้นใหม่โดยใช้ซ่อนและแสดง / เพิ่ม (ที่มีอยู่ / ใหม่) ดังนั้นคำตอบนี้มีไว้สำหรับนักพัฒนาที่ใช้ชิ้นส่วนอย่างที่ฉันทำ

จากนั้นฉันใช้onHiddenChangedวิธีการเพื่อให้ทราบว่าชิ้นส่วนเก่าได้ถูกเปลี่ยนกลับจากชิ้นใหม่ ดูรหัสด้านล่าง

ก่อนที่จะออกจากส่วนใหม่ฉันตั้งค่าผลลัพธ์ในพารามิเตอร์ส่วนกลางที่จะสอบถามโดยส่วนเก่า นี่เป็นวิธีแก้ปัญหาที่ไร้เดียงสามาก

@Override
public void onHiddenChanged(boolean hidden) {
    super.onHiddenChanged(hidden);
    if (hidden) return;
    Result result = Result.getAndReset();
    if (result == Result.Refresh) {
        refresh();
    }
}

public enum Result {
    Refresh;

    private static Result RESULT;

    public static void set(Result result) {
        if (RESULT == Refresh) {
            // Refresh already requested - no point in setting anything else;
            return;
        }
        RESULT = result;
    }

    public static Result getAndReset() {
        Result result = RESULT;
        RESULT = null;
        return result;
    }
}

getAndReset()วิธีการคืออะไร?
EpicPandaForce

ยังไม่ถูกonResume()เรียกในส่วนแรกเมื่อส่วนที่สองถูกยกเลิก?
PJ_Finnegan

2

ในส่วนของคุณคุณสามารถเรียก getActivity () สิ่งนี้จะช่วยให้คุณสามารถเข้าถึงกิจกรรมที่สร้างส่วน จากนั้นคุณสามารถเรียกใช้วิธีการปรับแต่งของคุณเพื่อตั้งค่าหรือส่งผ่านค่า


1

มีไลบรารี Android - FlowRที่ช่วยให้คุณสามารถเริ่มชิ้นส่วนสำหรับผลลัพธ์ได้

เริ่มต้นส่วนสำหรับผลลัพธ์

Flowr.open(RequestFragment.class)
    .displayFragmentForResults(getFragmentId(), REQUEST_CODE);

การจัดการผลลัพธ์ในส่วนการโทร

@Override
protected void onFragmentResults(int requestCode, int resultCode, Bundle data) {
    super.onFragmentResults(requestCode, resultCode, data);

    if (requestCode == REQUEST_CODE) {
        if (resultCode == Activity.RESULT_OK) {
            demoTextView.setText("Result OK");
        } else {
            demoTextView.setText("Result CANCELED");
        }
    }
}

การตั้งค่าผลลัพธ์ใน Fragment

Flowr.closeWithResults(getResultsResponse(resultCode, resultData));

1

โซลูชันที่ใช้อินเทอร์เฟซ (และ Kotlin) แนวคิดหลักคือการกำหนดอินเทอร์เฟซการเรียกกลับนำไปใช้ในกิจกรรมของคุณจากนั้นเรียกใช้จากส่วนของคุณ

ขั้นแรกสร้างอินเทอร์เฟซActionHandler:

interface ActionHandler {
    fun handleAction(actionCode: String, result: Int)
}

จากนั้นเรียกสิ่งนี้จากลูกของคุณ (ในกรณีนี้คือส่วนของคุณ):

companion object {
    const val FRAGMENT_A_CLOSED = "com.example.fragment_a_closed"
}

fun closeFragment() {
    try {
        (activity as ActionHandler).handleAction(FRAGMENT_A_CLOSED, 1234)
    } catch (e: ClassCastException) {
        Timber.e("Calling activity can't get callback!")
    }
    dismiss()
}

สุดท้ายให้ใช้สิ่งนี้ในผู้ปกครองของคุณเพื่อรับการติดต่อกลับ (ในกรณีนี้คือกิจกรรมของคุณ):

class MainActivity: ActionHandler { 
    override fun handleAction(actionCode: String, result: Int) {
        when {
            actionCode == FragmentA.FRAGMENT_A_CLOSED -> {
                doSomething(result)
            }
            actionCode == FragmentB.FRAGMENT_B_CLOSED -> {
                doSomethingElse(result)
            }
            actionCode == FragmentC.FRAGMENT_C_CLOSED -> {
                doAnotherThing(result)
            }
        }
    }

0

วิธีที่ง่ายที่สุดในการส่งข้อมูลกลับคือ setArgument () ตัวอย่างเช่นคุณมี fragment1 ซึ่งเรียก fragment2 ซึ่งเรียก fragment3, fragment1 -> framgnet2 -> fargment3

ในส่วนย่อย 1

public void navigateToFragment2() {
    if (fragmentManager == null) return;

    Fragment2 fragment = Fragment2.newInstance();
    String tag = "Fragment 2 here";
    fragmentManager.beginTransaction()
            .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
            .add(R.id.flContent, fragment, tag)
            .addToBackStack(null)
            .commitAllowingStateLoss();
}

ใน fragment2 เราเรียกว่า fragment3 ตามปกติ

private void navigateToFragment3() {
    if (fragmentManager == null) return;
    Fragment3 fragment = new Fragment3();
    fragmentManager.beginTransaction()
            .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
            .replace(R.id.flContent, fragment, tag)
            .addToBackStack(null)
            .commit();
}

เมื่อเราทำภารกิจใน fragment3 เสร็จแล้วเราจะเรียกสิ่งนี้ว่า:

FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
if (fragmentManager == null) return;
fragmentManager.popBackStack();
Bundle bundle = new Bundle();
bundle.putString("bundle_filter", "data");
fragmentManager.findFragmentByTag("Fragment 2 here").setArguments(bundle);

ตอนนี้ใน fragment2 เราสามารถเรียกอาร์กิวเมนต์ได้อย่างง่ายดาย

@Override
public void onResume() {
    super.onResume();
    Bundle rgs = getArguments();
    if (args != null) 
        String data = rgs.getString("bundle_filter");
}

ระวังข้อนี้อาจใช้งานได้ในบางกรณี แต่ถ้าแฟรกเมนต์ของคุณมีอาร์กิวเมนต์ดั้งเดิม ('แฟรกเมนต์ 2' ในตัวอย่างนี้) สิ่งนี้จะแทนที่อาร์กิวเมนต์เดิมที่ใช้ในการสร้างแฟรกเมนต์ซึ่งอาจนำไปสู่สถานะที่ไม่สอดคล้องกันหาก ชิ้นส่วนจะถูกทำลายและสร้างขึ้นใหม่
Juan

0

อีกสิ่งหนึ่งที่คุณสามารถทำได้โดยขึ้นอยู่กับสถาปัตยกรรมของคุณคือใช้ ViewModel ที่ใช้ร่วมกันระหว่างชิ้นส่วน ดังนั้นในกรณีของฉัน FragmentA คือรูปแบบและ FragmentB คือมุมมองการเลือกไอเท็มที่ผู้ใช้สามารถค้นหาและเลือกไอเท็มเก็บไว้ใน ViewModel เมื่อฉันกลับมาที่ FragmentA ข้อมูลจะถูกเก็บไว้แล้ว!

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.