Comunicare con i frammenti

Per riutilizzare i frammenti, sviluppali come componenti completamente autonomi che definiscono il proprio layout e comportamento. Una volta definiti questi riutilizzabili, puoi associarli a un'attività e collegarli con la logica dell'applicazione per realizzare l'UI composita complessiva.

Per reagire correttamente agli eventi utente e condividere le informazioni sullo stato, Necessità di canali di comunicazione tra un'attività e la sua o tra due o più frammenti. Per mantenere i frammenti autonomi, non hanno frammenti che comunicano direttamente con altri frammenti o con l'attività di organizzatore.

La libreria Fragment offre due opzioni di comunicazione: un file ViewModel e il frammento API Result. L'opzione consigliata dipende dal caso d'uso. Per condividere dati permanenti con API personalizzate, usa un valore ViewModel. Per un risultato una tantum con dati che possono essere inseriti in un Bundle, usa il frammento API Result.

Le seguenti sezioni mostrano come utilizzare ViewModel e il frammento API Result per comunicare tra frammenti e attività.

Condividi dati utilizzando un ViewModel

ViewModel è la scelta ideale quando devi condividere i dati più frammenti o tra frammenti e la loro attività host. ViewModel di oggetti archivia e per gestire i dati dell'interfaccia utente. Per ulteriori informazioni su ViewModel, vedi Panoramica di ViewModel.

Condividi i dati con l'attività dell'organizzatore

In alcuni casi, potrebbe essere necessario condividere i dati tra frammenti l'attività di organizzatore. Ad esempio, potresti voler attivare/disattivare una UI globale sulla base di un'interazione all'interno di un frammento.

Considera quanto segue: ItemViewModel:

Kotlin

class ItemViewModel : ViewModel() {
    private val mutableSelectedItem = MutableLiveData<Item>()
    val selectedItem: LiveData<Item> get() = mutableSelectedItem

    fun selectItem(item: Item) {
        mutableSelectedItem.value = item
    }
}

Java

public class ItemViewModel extends ViewModel {
    private final MutableLiveData<Item> selectedItem = new MutableLiveData<Item>();
    public void selectItem(Item item) {
        selectedItem.setValue(item);
    }
    public LiveData<Item> getSelectedItem() {
        return selectedItem;
    }
}

In questo esempio, i dati archiviati sono aggregati in una MutableLiveData. LiveData è sensibile al ciclo di vita osservabile. MutableLiveData consente al suo valore di essere è cambiato. Per ulteriori informazioni su LiveData, vedi Panoramica di LiveData.

Sia il frammento che l'attività host possono recuperare un'istanza condivisa di un ViewModel con ambito attività trasferendo l'attività al ViewModelProvider come costruttore. ViewModelProvider gestisce l'istanza di ViewModel o recuperarlo se esiste già. Entrambi i componenti possono osservare modificare questi dati.

Kotlin

class MainActivity : AppCompatActivity() {
    // Using the viewModels() Kotlin property delegate from the activity-ktx
    // artifact to retrieve the ViewModel in the activity scope.
    private val viewModel: ItemViewModel by viewModels()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel.selectedItem.observe(this, Observer { item ->
            // Perform an action with the latest item data.
        })
    }
}

class ListFragment : Fragment() {
    // Using the activityViewModels() Kotlin property delegate from the
    // fragment-ktx artifact to retrieve the ViewModel in the activity scope.
    private val viewModel: ItemViewModel by activityViewModels()

    // Called when the item is clicked.
    fun onItemClicked(item: Item) {
        // Set a new item.
        viewModel.selectItem(item)
    }
}

Java

public class MainActivity extends AppCompatActivity {
    private ItemViewModel viewModel;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        viewModel = new ViewModelProvider(this).get(ItemViewModel.class);
        viewModel.getSelectedItem().observe(this, item -> {
            // Perform an action with the latest item data.
        });
    }
}

public class ListFragment extends Fragment {
    private ItemViewModel viewModel;

    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        viewModel = new ViewModelProvider(requireActivity()).get(ItemViewModel.class);
        ...
        items.setOnClickListener(item -> {
            // Set a new item.
            viewModel.select(item);
        });
    }
}

Condividere dati tra frammenti

Due o più frammenti nella stessa attività spesso devono comunicare con tra loro. Ad esempio, immagina un frammento che mostra un elenco un'altra che consente all'utente di applicare vari filtri all'elenco. L'implementazione di questa richiesta non è banale senza i frammenti comunicano direttamente, ma non sono più indipendente. Inoltre, entrambi i frammenti devono gestire lo scenario in cui l'altro frammento non è ancora stato creato o visibile.

Questi frammenti possono condividere un ViewModel utilizzando l'ambito dell'attività per gestire questa comunicazione. Se condividi ViewModel in questo modo, i frammenti non hanno bisogno di essere a conoscenza l'uno dell'altro e l'attività non deve fare nulla per facilitare la comunicazione.

L'esempio seguente mostra come due frammenti possono utilizzare un ViewModel per comunicare:

Kotlin

class ListViewModel : ViewModel() {
    val filters = MutableLiveData<Set<Filter>>()

    private val originalList: LiveData<List<Item>>() = ...
    val filteredList: LiveData<List<Item>> = ...

    fun addFilter(filter: Filter) { ... }

    fun removeFilter(filter: Filter) { ... }
}

class ListFragment : Fragment() {
    // Using the activityViewModels() Kotlin property delegate from the
    // fragment-ktx artifact to retrieve the ViewModel in the activity scope.
    private val viewModel: ListViewModel by activityViewModels()
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        viewModel.filteredList.observe(viewLifecycleOwner, Observer { list ->
            // Update the list UI.
        }
    }
}

class FilterFragment : Fragment() {
    private val viewModel: ListViewModel by activityViewModels()
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        viewModel.filters.observe(viewLifecycleOwner, Observer { set ->
            // Update the selected filters UI.
        }
    }

    fun onFilterSelected(filter: Filter) = viewModel.addFilter(filter)

    fun onFilterDeselected(filter: Filter) = viewModel.removeFilter(filter)
}

Java

public class ListViewModel extends ViewModel {
    private final MutableLiveData<Set<Filter>> filters = new MutableLiveData<>();

    private final LiveData<List<Item>> originalList = ...;
    private final LiveData<List<Item>> filteredList = ...;

    public LiveData<List<Item>> getFilteredList() {
        return filteredList;
    }

    public LiveData<Set<Filter>> getFilters() {
        return filters;
    }

    public void addFilter(Filter filter) { ... }

    public void removeFilter(Filter filter) { ... }
}

public class ListFragment extends Fragment {
    private ListViewModel viewModel;

    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        viewModel = new ViewModelProvider(requireActivity()).get(ListViewModel.class);
        viewModel.getFilteredList().observe(getViewLifecycleOwner(), list -> {
            // Update the list UI.
        });
    }
}

public class FilterFragment extends Fragment {
    private ListViewModel viewModel;

    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        viewModel = new ViewModelProvider(requireActivity()).get(ListViewModel.class);
        viewModel.getFilters().observe(getViewLifecycleOwner(), set -> {
            // Update the selected filters UI.
        });
    }

    public void onFilterSelected(Filter filter) {
        viewModel.addFilter(filter);
    }

    public void onFilterDeselected(Filter filter) {
        viewModel.removeFilter(filter);
    }
}

Entrambi i frammenti utilizzano la propria attività host come ambito per il ViewModelProvider. Poiché i frammenti usano lo stesso ambito, ricevono la stessa istanza di ViewModel, il che consente loro di comunicare avanti e indietro.

Condividere dati tra un frammento padre e un frammento secondario

Quando lavori con i frammenti figlio, il frammento principale e il relativo file secondario potrebbero dover condividere dati tra loro. Per condividere i dati tra questi frammenti, utilizza il frammento padre come ambito ViewModel, come mostrato nel seguente esempio:

Kotlin

class ListFragment: Fragment() {
    // Using the viewModels() Kotlin property delegate from the fragment-ktx
    // artifact to retrieve the ViewModel.
    private val viewModel: ListViewModel by viewModels()
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        viewModel.filteredList.observe(viewLifecycleOwner, Observer { list ->
            // Update the list UI.
        }
    }
}

class ChildFragment: Fragment() {
    // Using the viewModels() Kotlin property delegate from the fragment-ktx
    // artifact to retrieve the ViewModel using the parent fragment's scope
    private val viewModel: ListViewModel by viewModels({requireParentFragment()})
    ...
}

Java

public class ListFragment extends Fragment {
    private ListViewModel viewModel;

    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        viewModel = new ViewModelProvider(this).get(ListViewModel.class);
        viewModel.getFilteredList().observe(getViewLifecycleOwner(), list -> {
            // Update the list UI.
        }
    }
}

public class ChildFragment extends Fragment {
    private ListViewModel viewModel;
    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        viewModel = new ViewModelProvider(requireParentFragment()).get(ListViewModel.class);
        ...
    }
}

Definizione dell'ambito di un ViewModel per il grafico di navigazione

Se utilizzi la Libreria di navigazione, puoi anche l'ambito di un ViewModel al ciclo di vita del ciclo di vita NavBackStackEntry. Per Ad esempio, ViewModel può limitare l'ambito a NavBackStackEntry per ListFragment:

Kotlin

class ListFragment: Fragment() {
    // Using the navGraphViewModels() Kotlin property delegate from the fragment-ktx
    // artifact to retrieve the ViewModel using the NavBackStackEntry scope.
    // R.id.list_fragment == the destination id of the ListFragment destination
    private val viewModel: ListViewModel by navGraphViewModels(R.id.list_fragment)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        viewModel.filteredList.observe(viewLifecycleOwner, Observer { item ->
            // Update the list UI.
        }
    }
}

Java

public class ListFragment extends Fragment {
    private ListViewModel viewModel;

    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
    NavController navController = NavHostFragment.findNavController(this);
        NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.list_fragment)

        viewModel = new ViewModelProvider(backStackEntry).get(ListViewModel.class);
        viewModel.getFilteredList().observe(getViewLifecycleOwner(), list -> {
            // Update the list UI.
        }
    }
}

Per ulteriori informazioni sulla definizione dell'ambito di un ViewModel in un NavBackStackEntry, consulta Interagisci in modo programmatico con il componente Navigazione.

Ricevi risultati utilizzando l'API Fragment Result

In alcuni casi, potresti voler passare un valore una tantum tra due frammenti o tra un frammento e la sua attività host. Ad esempio, potresti avere una che legge i codici QR, ritrasmettendo i dati a un frammento precedente.

In Fragment 1.3.0 e versioni successive, ogni FragmentManager implements FragmentResultOwner. Ciò significa che un FragmentManager può fungere da archivio centrale per il frammento che consentono di analizzare i dati e visualizzare i risultati. Questa modifica consente ai componenti di comunicare tra loro impostare risultati con frammenti e rimanere in ascolto di questi risultati, senza richiedere che questi componenti abbiano riferimenti diretti l'uno all'altro.

Passa i risultati tra i frammenti

Per ritrasmettere i dati al frammento A dal frammento B, imposta prima un listener di risultati sul frammento A, il frammento che riceve il risultato. Chiama setFragmentResultListener() sul FragmentManager del frammento A, come mostrato nell'esempio seguente:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // Use the Kotlin extension in the fragment-ktx artifact.
    setFragmentResultListener("requestKey") { requestKey, 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.
    }
}

Java

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    getParentFragmentManager().setFragmentResultListener("requestKey", this, new FragmentResultListener() {
        @Override
        public void onFragmentResult(@NonNull String requestKey, @NonNull Bundle bundle) {
            // We use a String here, but any type that can be put in a Bundle is supported.
            String result = bundle.getString("bundleKey");
            // Do something with the result.
        }
    });
}
il frammento b invia dati al frammento a utilizzando un FragmentManager
Figura 1. Il frammento B invia i dati al frammento A utilizzando un FragmentManager.

Nel frammento B, il frammento che produce il risultato, imposta il risultato sullo stesso FragmentManager utilizzando lo stesso requestKey. Cosa puoi fare quindi utilizzando setFragmentResult() API:

Kotlin

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

Java

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Bundle result = new Bundle();
        result.putString("bundleKey", "result");
        getParentFragmentManager().setFragmentResult("requestKey", result);
    }
});

Il frammento A riceve il risultato ed esegue il callback del listener una volta che il frammento STARTED

Per una determinata chiave puoi avere un solo listener e un risultato. Se chiami setFragmentResult() più di una volta per la stessa chiave e se il listener non è STARTED, il sistema sostituisce eventuali risultati in attesa con i risultati o il risultato finale.

Se imposti un risultato senza un listener corrispondente per riceverlo, il risultato viene memorizzato in FragmentManager finché non imposti un listener con la stessa chiave. Quando un listener riceve un risultato e attiva Callback onFragmentResult(), il risultato è stato cancellato. Questo comportamento ha due implicazioni principali:

  • I frammenti nella pila posteriore non ricevono risultati finché non sono scoppiati e sono STARTED.
  • Se il frammento in ascolto di un risultato è STARTED quando il risultato è impostato, il callback del listener si attiva immediatamente.
di Gemini Advanced.

Risultati dei frammenti di test

Utilizza le funzionalità di FragmentScenario per testare le chiamate a setFragmentResult() e setFragmentResultListener(). Crea uno scenario per il frammento sottoposto a test utilizzando launchFragmentInContainer o launchFragment, e quindi richiamare manualmente il metodo che non viene testato.

Per testare setFragmentResultListener(), crea uno scenario con frammento che effettua la chiamata a setFragmentResultListener(). Poi, chiama direttamente setFragmentResult() e verifica il risultato:

@Test
fun testFragmentResultListener() {
    val scenario = launchFragmentInContainer<ResultListenerFragment>()
    scenario.onFragment { fragment ->
        val expectedResult = "result"
        fragment.parentFragmentManager.setFragmentResult("requestKey", bundleOf("bundleKey" to expectedResult))
        assertThat(fragment.result).isEqualTo(expectedResult)
    }
}

class ResultListenerFragment : Fragment() {
    var result : String? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // Use the Kotlin extension in the fragment-ktx artifact.
        setFragmentResultListener("requestKey") { requestKey, bundle ->
            result = bundle.getString("bundleKey")
        }
    }
}

Per testare setFragmentResult(), crea uno scenario con il frammento che rende la chiamata a setFragmentResult(). Successivamente, chiama setFragmentResultListener() direttamente e verifica il risultato:

@Test
fun testFragmentResult() {
    val scenario = launchFragmentInContainer<ResultFragment>()
    lateinit var actualResult: String?
    scenario.onFragment { fragment ->
        fragment.parentFragmentManager
                .setFragmentResultListener("requestKey") { requestKey, bundle ->
            actualResult = bundle.getString("bundleKey")
        }
    }
    onView(withId(R.id.result_button)).perform(click())
    assertThat(actualResult).isEqualTo("result")
}

class ResultFragment : Fragment(R.layout.fragment_result) {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        view.findViewById(R.id.result_button).setOnClickListener {
            val result = "result"
            // Use the Kotlin extension in the fragment-ktx artifact.
            setFragmentResult("requestKey", bundleOf("bundleKey" to result))
        }
    }
}

Passa i risultati tra frammenti padre e figlio

Per passare un risultato da un frammento figlio a un elemento padre, utilizza getChildFragmentManager() del frammento padre anziché getParentFragmentManager() durante la chiamata a setFragmentResultListener().

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // Set the listener on the child fragmentManager.
    childFragmentManager.setFragmentResultListener("requestKey") { key, bundle ->
        val result = bundle.getString("bundleKey")
        // Do something with the result.
    }
}

Java

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // Set the listener on the child fragmentManager.
    getChildFragmentManager()
        .setFragmentResultListener("requestKey", this, new FragmentResultListener() {
            @Override
            public void onFragmentResult(@NonNull String requestKey, @NonNull Bundle bundle) {
                String result = bundle.getString("bundleKey");
                // Do something with the result.
            }
        });
}
un frammento figlio può usare FragmentManager per inviare un risultato
            alla relativa rete principale
Figura 2 Un frammento figlio può utilizzare FragmentManager per inviare un risultato all'elemento principale.

Il frammento figlio imposta il risultato sul relativo FragmentManager. Partner principale riceve il risultato una volta che il frammento è STARTED:

Kotlin

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

Java

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Bundle result = new Bundle();
        result.putString("bundleKey", "result");
        // The child fragment needs to still set the result on its parent fragment manager.
        getParentFragmentManager().setFragmentResult("requestKey", result);
    }
});

Ricevi risultati nell'attività organizzatore

Per ricevere un risultato di frammento nell'attività host, imposta un listener di risultati sul gestore di frammenti getSupportFragmentManager()

Kotlin

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        supportFragmentManager
                .setFragmentResultListener("requestKey", this) { requestKey, 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.
        }
    }
}

Java

class MainActivity extends AppCompatActivity {
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getSupportFragmentManager().setFragmentResultListener("requestKey", this, new FragmentResultListener() {
            @Override
            public void onFragmentResult(@NonNull String requestKey, @NonNull Bundle bundle) {
                // We use a String here, but any type that can be put in a Bundle is supported.
                String result = bundle.getString("bundleKey");
                // Do something with the result.
            }
        });
    }
}