Eseguire la migrazione al componente di navigazione

Il componente Navigazione è una libreria che può gestire navigazione complessa, animazione di transizione, link diretti e argomenti controllati in fase di compilazione che vengono trasmessi tra le schermate della tua app.

Questo documento funge da guida generale per eseguire la migrazione di un'app esistente per utilizzare il componente Navigazione.

A livello generale, la migrazione prevede i seguenti passaggi:

  1. Sposta dalle attività la logica dell'interfaccia utente specifica per le schermate - Fuori dalle attività la logica dell'interfaccia utente dell'app, assicurandoti che ogni attività appartenga solo alla logica dei componenti dell'interfaccia utente di navigazione globale, ad esempio Toolbar, e delega l'implementazione di ogni schermata a un frammento o a una destinazione personalizzata.

  2. Integra il componente Navigazione: per ogni attività, crea un grafico di navigazione contenente uno o più frammenti gestiti da quell'attività. Sostituisci le transazioni dei frammenti con le operazioni del componente Navigazione.

  3. Aggiungi destinazioni delle attività: sostituisci le chiamate startActivity() con azioni che utilizzano le destinazioni delle attività.

  4. Combina le attività: combina i grafici di navigazione nei casi in cui più attività condividano un layout comune.

Prerequisiti

Questa guida presuppone che tu abbia già eseguito la migrazione della tua app per utilizzare le librerie AndroidX. Se non l'hai ancora fatto, prima di continuare esegui la migrazione del tuo progetto a utilizzare AndroidX.

Sposta la logica dell'interfaccia utente specifica per la schermata fuori dalle attività

Le attività sono componenti a livello di sistema che facilitano un'interazione grafica tra la tua app e Android. Le attività sono registrate nel file manifest dell'app, in modo che Android sappia quali sono le attività disponibili per l'avvio. La classe attività consente anche alla tua app di reagire ai cambiamenti di Android, ad esempio quando l'UI dell'app entra in primo piano o esce, ruota e così via. L'attività può anche fungere da luogo per condividere lo stato tra le schermate.

Nell'ambito dell'app, le attività devono fungere da host per la navigazione e contenere la logica e le conoscenze relative alla transizione da una schermata all'altra, alla trasmissione di dati e così via. Tuttavia, è preferibile gestire i dettagli dell'interfaccia utente in un'area più piccola e riutilizzabile. L'implementazione consigliata per questo pattern è costituita da frammenti. Consulta Attività singola: perché, quando e come per saperne di più sui vantaggi dell'utilizzo dei frammenti. La navigazione supporta i frammenti tramite la dipendenza navigation-frammento. La navigazione supporta anche i tipi di destinazioni personalizzati.

Se la tua app non utilizza frammenti, la prima cosa da fare è eseguire la migrazione di ogni schermata dell'app per utilizzare un frammento. Non stai rimuovendo l'attività in questo momento. Piuttosto, stai creando un frammento per rappresentare lo schermo e separare la logica dell'interfaccia utente in base alla responsabilità.

Introduzione ai frammenti

Per illustrare il processo di introduzione dei frammenti, iniziamo con un esempio di applicazione composta da due schermate: una schermata Elenco prodotti e una schermata Dettagli prodotto. Facendo clic su un prodotto nella schermata dell'elenco, l'utente viene indirizzato a una schermata dei dettagli con ulteriori informazioni sul prodotto.

In questo esempio, le schermate dell'elenco e dei dettagli sono attualmente attività separate.

Crea un nuovo layout per ospitare l'interfaccia utente

Per introdurre un frammento, inizia creando un nuovo file di layout per l'attività in cui ospitare il frammento. Sostituisce l'attuale layout di visualizzazione dei contenuti dell'attività.

Per una visualizzazione semplice, puoi utilizzare un FrameLayout, come mostrato nel seguente esempio di product_list_host:

<FrameLayout
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:id="@+id/main_content"
   android:layout_height="match_parent"
   android:layout_width="match_parent" />

L'attributo id si riferisce alla sezione dei contenuti in cui in seguito aggiungiamo il frammento.

Successivamente, nella funzione onCreate() dell'attività, modifica il riferimento del file di layout nella funzione onCreate dell'attività in modo che rimandi a questo nuovo file di layout:

Kotlin

class ProductListActivity : AppCompatActivity() {
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // Replace setContentView(R.layout.product_list) with the line below
        setContentView(R.layout.product_list_host)
        ...
    }
}

Java

public class ProductListActivity extends AppCompatActivity {
    ...
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        ...
        // Replace setContentView(R.layout.product_list); with the line below
        setContentView(R.layout.product_list_host);
        ...
    }
}

Il layout esistente (product_list, in questo esempio) viene utilizzato come vista principale del frammento che stai per creare.

Crea un frammento

Crea un nuovo frammento per gestire l'interfaccia utente per lo schermo. È buona norma essere coerenti con il nome host dell'attività. Lo snippet seguente utilizza ProductListFragment, ad esempio:

Kotlin

class ProductListFragment : Fragment() {
    // Leave empty for now.
}

Java

public class ProductListFragment extends Fragment {
    // Leave empty for now.
}

Sposta la logica di attività in un frammento

Una volta definita la definizione del frammento, il passaggio successivo consiste nello spostare la logica dell'interfaccia utente per questa schermata dall'attività a questo nuovo frammento. Se provieni da un'architettura basata sulle attività, è probabile che la funzione onCreate() dell'attività stia utilizzando molta logica di creazione delle visualizzazioni.

Ecco una schermata di esempio basata sulle attività con logica dell'interfaccia utente che dobbiamo spostare:

Kotlin

class ProductListActivity : AppCompatActivity() {

    // Views and/or ViewDataBinding references, Adapters...
    private lateinit var productAdapter: ProductAdapter
    private lateinit var binding: ProductListActivityBinding

    ...

    // ViewModels, System Services, other Dependencies...
    private val viewModel: ProductListViewModel by viewModels()

    ...

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // View initialization logic
        DataBindingUtil.setContentView(this, R.layout.product_list_activity)

        // Post view initialization logic
        // Connect adapters
        productAdapter = ProductAdapter(productClickCallback)
        binding.productsList.setAdapter(productAdapter)

        // Initialize view properties, set click listeners, etc.
        binding.productsSearchBtn.setOnClickListener {...}

        // Subscribe to state
        viewModel.products.observe(this, Observer { myProducts ->
            ...
        })

        // ...and so on
    }
   ...
}

Java

public class ProductListActivity extends AppCompatActivity {

    // Views and/or ViewDataBinding references, adapters...
    private ProductAdapter productAdapter;
    private ProductListActivityBinding binding;

    ...

    // ViewModels, system services, other dependencies...
    private ProductListViewModel viewModel;

    ...

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // View initialization logic
        DataBindingUtil.setContentView(this, R.layout.product_list_activity);

        // Post view initialization logic
        // Connect adapters
        productAdapter = new ProductAdapter(productClickCallback);
        binding.productsList.setAdapter(productAdapter);

        // Initialize ViewModels and other dependencies
        ProductListViewModel viewModel = new ViewModelProvider(this).get(ProductListViewModel.java);

        // Initialize view properties, set click listeners, etc.
        binding.productsSearchBtn.setOnClickListener(v -> { ... });

        // Subscribe to state
        viewModel.getProducts().observe(this, myProducts ->
            ...
       );

       // ...and so on
   }

La tua attività potrebbe anche determinare quando e come l'utente passa alla schermata successiva, come mostrato nell'esempio seguente:

Kotlin

    // Provided to ProductAdapter in ProductListActivity snippet.
    private val productClickCallback = ProductClickCallback { product ->
        show(product)
    }

    fun show(product: Product) {
        val intent = Intent(this, ProductActivity::class.java)
        intent.putExtra(ProductActivity.KEY_PRODUCT_ID, product.id)
        startActivity(intent)
    }

Java

// Provided to ProductAdapter in ProductListActivity snippet.
private ProductClickCallback productClickCallback = this::show;

private void show(Product product) {
    Intent intent = new Intent(this, ProductActivity.class);
    intent.putExtra(ProductActivity.KEY_PRODUCT_ID, product.getId());
    startActivity(intent);
}

All'interno del frammento, distribuisci questo lavoro tra onCreateView() e onViewCreated(), lasciando solo la logica di navigazione nell'attività:

Kotlin

class ProductListFragment : Fragment() {

    private lateinit var binding: ProductListFragmentBinding
    private val viewModel: ProductListViewModel by viewModels()

     // View initialization logic
    override fun onCreateView(inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?): View? {
        binding = DataBindingUtil.inflate(
                inflater,
                R.layout.product_list,
                container,
                false
        )
        return binding.root
    }

    // Post view initialization logic
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        // Connect adapters
        productAdapter = ProductAdapter(productClickCallback)
        binding.productsList.setAdapter(productAdapter)

        // Initialize view properties, set click listeners, etc.
        binding.productsSearchBtn.setOnClickListener {...}

        // Subscribe to state
        viewModel.products.observe(this, Observer { myProducts ->
            ...
        })

        // ...and so on
    }

    // Provided to ProductAdapter
    private val productClickCallback = ProductClickCallback { product ->
        if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
            (requireActivity() as ProductListActivity).show(product)
        }
    }
    ...
}

Java

public class ProductListFragment extends Fragment {

    private ProductAdapter productAdapter;
    private ProductListFragmentBinding binding;

    // View initialization logic
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater,
            @Nullable ViewGroup container,
            @Nullable Bundle savedInstanceState) {
        binding = DataBindingUtil.inflate(
                inflater,
                R.layout.product_list_fragment,
                container,
                false);
        return binding.getRoot();
    }

    // Post view initialization logic
    @Override
    public void onViewCreated(@NonNull View view,
            @Nullable Bundle savedInstanceState) {

        // Connect adapters
        binding.productsList.setAdapter(productAdapter);

        // Initialize ViewModels and other dependencies
        ProductListViewModel viewModel = new ViewModelProvider(this)
                .get(ProductListViewModel.class);

        // Initialize view properties, set click listeners, etc.
        binding.productsSearchBtn.setOnClickListener(...)

        // Subscribe to state
        viewModel.getProducts().observe(this, myProducts -> {
            ...
       });

       // ...and so on

    // Provided to ProductAdapter
    private ProductClickCallback productClickCallback = new ProductClickCallback() {
        @Override
        public void onClick(Product product) {
            if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) {
                ((ProductListActivity) requireActivity()).show(product);
            }
        }
    };
    ...
}

In ProductListFragment, nota che non è necessario chiamare setContentView() per gonfiare e collegare il layout. In un frammento, onCreateView() inizializza la vista radice. onCreateView() prende un'istanza di LayoutInflater che può essere utilizzata per aumentare la visualizzazione principale in base a un file di risorse di layout. In questo esempio viene riutilizzato il layout product_list esistente che è stato utilizzato dall'attività perché non è necessario apportare alcuna modifica al layout.

Se nelle funzioni onStart(), onResume(), onPause() o onStop() dell'attività è presente una logica dell'interfaccia utente non correlata alla navigazione, puoi spostarla nelle funzioni corrispondenti con lo stesso nome sul frammento.

Inizializza il frammento nell'attività host

Dopo aver spostato tutta la logica dell'interfaccia utente nel frammento, solo la logica di navigazione dovrebbe rimanere nell'attività.

Kotlin

class ProductListActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.product_list_host)
    }

    fun show(product: Product) {
        val intent = Intent(this, ProductActivity::class.java)
        intent.putExtra(ProductActivity.KEY_PRODUCT_ID, product.id)
        startActivity(intent)
    }
}

Java

public class ProductListActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.product_list_host);
    }

    public void show(Product product) {
        Intent intent = new Intent(this, ProductActivity.class);
        intent.putExtra(ProductActivity.KEY_PRODUCT_ID, product.getId());
        startActivity(intent);
    }
}

L'ultimo passaggio consiste nel creare un'istanza del frammento in onCreate(), subito dopo aver impostato la visualizzazione dei contenuti:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.product_list_host)

    if (savedInstanceState == null) {
        val fragment = ProductListFragment()
        supportFragmentManager
                .beginTransaction()
                .add(R.id.main_content, fragment)
                .commit()
    }
}

Java

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.product_list_host);

    if (savedInstanceState == null) {
        ProductListFragment fragment = new ProductListFragment();
        getSupportFragmentManager()
                .beginTransaction()
                .add(R.id.main_content, fragment)
                .commit();
    }
}

Come mostrato in questo esempio, FragmentManager salva e ripristina automaticamente i frammenti durante le modifiche alla configurazione, quindi devi aggiungere il frammento solo se savedInstanceState è null.

Trasferisci gli extra per intent al frammento

Se l'attività riceve Extras tramite un intent, puoi passarli direttamente al frammento come argomenti.

In questo esempio, ProductDetailsFragment riceve gli argomenti direttamente dagli extra per l'intent dell'attività:

Kotlin

...

if (savedInstanceState == null) {
    val fragment = ProductDetailsFragment()

    // Intent extras and Fragment Args are both of type android.os.Bundle.
    fragment.arguments = intent.extras

    supportFragmentManager
            .beginTransaction()
            .add(R.id.main_content, fragment)
            .commit()
}

...

Java

...

if (savedInstanceState == null) {
    ProductDetailsFragment fragment = new ProductDetailsFragment();

    // Intent extras and fragment Args are both of type android.os.Bundle.
    fragment.setArguments(getIntent().getExtras());

    getSupportFragmentManager()
            .beginTransaction()
            .add(R.id.main_content, fragment)
            .commit();
}

...

A questo punto, dovresti essere in grado di testare l'esecuzione dell'app con la prima schermata aggiornata in modo da utilizzare un frammento. Continua a eseguire la migrazione delle altre schermate basate sull'attività, prendendoti del tempo per eseguire i test dopo ogni iterazione.

Integrare il componente Navigazione

Dopo aver utilizzato un'architettura basata su frammenti, puoi iniziare a integrare il componente Navigazione.

Innanzitutto, aggiungi le dipendenze di navigazione più recenti al progetto, seguendo le istruzioni riportate nelle note di rilascio della libreria di navigazione.

Crea un grafico di navigazione

Il componente Navigazione rappresenta la configurazione della navigazione dell'app in un file di risorse sotto forma di grafico, proprio come le viste dell'app. In questo modo è più semplice organizzare la navigazione dell'app al di fuori del codebase e puoi modificare visivamente la navigazione dell'app.

Per creare un grafico di navigazione, inizia creando una nuova cartella di risorse denominata navigation. Per aggiungere il grafico, fai clic con il tasto destro del mouse su questa directory e scegli Nuovo > File di risorse di navigazione.

Il componente di navigazione utilizza un'attività come host per la navigazione e scambia singoli frammenti nell'host mentre gli utenti navigano all'interno dell'app. Prima di iniziare a strutturare visivamente la navigazione dell'app, devi configurare un NavHost all'interno dell'attività che ospiterà questo grafico. Poiché stiamo utilizzando i frammenti, possiamo utilizzare l'implementazione predefinita NavHost del componente di navigazione, NavHostFragment.

Un NavHostFragment viene configurato tramite un elemento FragmentContainerView posizionato all'interno di un'attività host, come mostrato nell'esempio seguente:

<androidx.fragment.app.FragmentContainerView
   android:name="androidx.navigation.fragment.NavHostFragment"
   app:navGraph="@navigation/product_list_graph"
   app:defaultNavHost="true"
   android:id="@+id/main_content"
   android:layout_width="match_parent"
   android:layout_height="match_parent" />

L'attributo app:NavGraph rimanda al grafico di navigazione associato a questo host di navigazione. L'impostazione di questa proprietà aumenta il rendimento del grafico di navigazione e imposta la proprietà del grafico su NavHostFragment. L'attributo app:defaultNavHost assicura che NavHostFragment intercetti il pulsante Indietro del sistema.

Se utilizzi una navigazione di primo livello come DrawerLayout o BottomNavigationView, questa FragmentContainerView sostituisce l'elemento di visualizzazione dei contenuti principale. Per degli esempi, consulta Aggiornare i componenti dell'interfaccia utente con NavigationUI.

Per un layout semplice, puoi includere questo elemento FragmentContainerView come elemento secondario dell'elemento principale ViewGroup:

<FrameLayout
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_height="match_parent"
   android:layout_width="match_parent">

<androidx.fragment.app.FragmentContainerView
   android:id="@+id/main_content"
   android:name="androidx.navigation.fragment.NavHostFragment"
   app:navGraph="@navigation/product_list_graph"
   app:defaultNavHost="true"
   android:layout_width="match_parent"
   android:layout_height="match_parent" />

</FrameLayout>

Se fai clic sulla scheda Design in basso, dovresti vedere un grafico simile a quello mostrato di seguito. Nella parte superiore sinistra del grafico, in Destinazioni, puoi vedere un riferimento all'attività NavHost nel formato layout_name (resource_id).

Fai clic sul pulsante Più nella parte superiore per aggiungere i frammenti al grafico.

Il componente Navigazione fa riferimento alle singole schermate come destinazioni. Le destinazioni possono essere frammenti, attività o destinazioni personalizzate. Puoi aggiungere qualsiasi tipo di destinazione al grafico, ma tieni presente che le destinazioni delle attività sono considerate destinazioni dei terminal, perché quando raggiungi la destinazione di un'attività operi all'interno di un host di navigazione e di un grafico separati.

Il componente Navigazione fa riferimento al modo in cui gli utenti arrivano da una destinazione a un'altra come azioni. Le azioni possono anche descrivere animazioni di transizione e comportamento del pop.

Rimuovi transazioni con frammenti

Ora che utilizzi il componente Navigazione, se ti sposti tra schermate basate su frammenti nella stessa attività, puoi rimuovere le interazioni FragmentManager.

Se la tua app utilizza più frammenti nella stessa attività o nella stessa navigazione di primo livello, ad esempio un layout a scomparsa o una navigazione in basso, probabilmente stai utilizzando FragmentManager e FragmentTransactions per aggiungere o sostituire frammenti nella sezione Contenuti principali dell'interfaccia utente. Ora puoi sostituirlo e semplificarlo utilizzando il componente Navigazione, fornendo azioni per collegare le destinazioni all'interno del grafico e quindi navigando utilizzando NavController.

Ecco alcuni scenari che potresti riscontrare, oltre a possibili approcci per la migrazione, per ciascuno di essi.

Singola attività per la gestione di più frammenti

Se hai una singola attività che gestisce più frammenti, il codice di attività potrebbe avere il seguente aspetto:

Kotlin

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Logic to load the starting destination
        //  when the Activity is first created
        if (savedInstanceState == null) {
            val fragment = ProductListFragment()
            supportFragmentManager.beginTransaction()
                    .add(R.id.fragment_container, fragment, ProductListFragment.TAG)
                    .commit()
        }
    }

    // Logic to navigate the user to another destination.
    // This may include logic to initialize and set arguments on the destination
    // fragment or even transition animations between the fragments (not shown here).
    fun navigateToProductDetail(productId: String) {
        val fragment = new ProductDetailsFragment()
        val args = Bundle().apply {
            putInt(KEY_PRODUCT_ID, productId)
        }
        fragment.arguments = args

        supportFragmentManager.beginTransaction()
                .addToBackStack(ProductDetailsFragment.TAG)
                .replace(R.id.fragment_container, fragment, ProductDetailsFragment.TAG)
                .commit()
    }
}

Java

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Logic to load the starting destination when the activity is first created.
        if (savedInstanceState == null) {
            val fragment = ProductListFragment()
            supportFragmentManager.beginTransaction()
                    .add(R.id.fragment_container, fragment, ProductListFragment.TAG)
                    .commit();
        }
    }

    // Logic to navigate the user to another destination.
    // This may include logic to initialize and set arguments on the destination
    // fragment or even transition animations between the fragments (not shown here).
    public void navigateToProductDetail(String productId) {
        Fragment fragment = new ProductDetailsFragment();
        Bundle args = new Bundle();
        args.putInt(KEY_PRODUCT_ID, productId);
        fragment.setArguments(args);

        getSupportFragmentManager().beginTransaction()
                .addToBackStack(ProductDetailsFragment.TAG)
                .replace(R.id.fragment_container, fragment, ProductDetailsFragment.TAG)
                .commit();
    }
}

All'interno della destinazione di origine, è possibile che tu stia richiamando una funzione di navigazione in risposta ad alcuni eventi, come mostrato di seguito:

Kotlin

class ProductListFragment : Fragment() {
    ...
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        // In this example a callback is passed to respond to an item clicked
        //  in a RecyclerView
        productAdapter = ProductAdapter(productClickCallback)
        binding.productsList.setAdapter(productAdapter)
    }
    ...

    // The callback makes the call to the activity to make the transition.
    private val productClickCallback = ProductClickCallback { product ->
            (requireActivity() as MainActivity).navigateToProductDetail(product.id)
    }
}

Java

public class ProductListFragment extends Fragment  {
    ...
    @Override
    public void onViewCreated(@NonNull View view,
            @Nullable Bundle savedInstanceState) {
    // In this example a callback is passed to respond to an item clicked in a RecyclerView
        productAdapter = new ProductAdapter(productClickCallback);
        binding.productsList.setAdapter(productAdapter);
    }
    ...

    // The callback makes the call to the activity to make the transition.
    private ProductClickCallback productClickCallback = product -> (
        ((MainActivity) requireActivity()).navigateToProductDetail(product.getId())
    );
}

Puoi sostituirlo aggiornando il grafico di navigazione per impostare la destinazione di partenza e le azioni per collegare le destinazioni e definire gli argomenti ove richiesto:

<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/product_list_graph"
    app:startDestination="@id/product_list">

    <fragment
        android:id="@+id/product_list"
        android:name="com.example.android.persistence.ui.ProductListFragment"
        android:label="Product List"
        tools:layout="@layout/product_list">
        <action
            android:id="@+id/navigate_to_product_detail"
            app:destination="@id/product_detail" />
    </fragment>
    <fragment
        android:id="@+id/product_detail"
        android:name="com.example.android.persistence.ui.ProductDetailFragment"
        android:label="Product Detail"
        tools:layout="@layout/product_detail">
        <argument
            android:name="product_id"
            app:argType="integer" />
    </fragment>
</navigation>

Dopodiché puoi aggiornare le tue attività:

Kotlin

class MainActivity : AppCompatActivity() {

    // No need to load the start destination, handled automatically by the Navigation component
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

Java

public class MainActivity extends AppCompatActivity {

    // No need to load the start destination, handled automatically by the Navigation component
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

L'attività non richiede più un metodo navigateToProductDetail(). Nella sezione successiva aggiorniamo ProductListFragment in modo che utilizzi NavController per passare alla schermata dei dettagli del prodotto successiva.

Passa gli argomenti in modo sicuro

Il componente Navigazione ha un plug-in Gradle chiamato Args Safe che genera semplici classi di oggetti e builder per l'accesso sicuro per tipo agli argomenti specificati per destinazioni e azioni.

Dopo aver applicato il plug-in, eventuali argomenti definiti su una destinazione nel grafico di navigazione fanno sì che il framework del componente Navigazione generi una classe Arguments che fornisce argomenti sicuri per il tipo alla destinazione di destinazione. La definizione di un'azione fa sì che il plug-in generi una classe di configurazione Directions che può essere utilizzata per indicare all'elemento NavController come raggiungere la destinazione target. Quando un'azione rimanda a una destinazione che richiede argomenti, la classe Directions generata include metodi del costruttore che richiedono questi parametri.

All'interno del frammento, utilizza NavController e la classe Directions generata per fornire argomenti sicuri per il tipo alla destinazione di destinazione, come mostrato nell'esempio seguente:

Kotlin

class ProductListFragment : Fragment() {

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        // In this example a callback is passed to respond to an item clicked in a RecyclerView
        productAdapter = ProductAdapter(productClickCallback)
        binding.productsList.setAdapter(productAdapter)
    }
    ...

    // The callback makes the call to the NavController to make the transition.
    private val productClickCallback = ProductClickCallback { product ->
        val directions = ProductListDirections.navigateToProductDetail(product.id)
        findNavController().navigate(directions)
    }
}

Java

public class ProductListFragment extends Fragment  {
    ...
    @Override
    public void onViewCreated(@NonNull View view,
            @Nullable Bundle savedInstanceState) {
        // In this example a callback is passed to respond to an item clicked in a RecyclerView
        productAdapter = new ProductAdapter(productClickCallback);
        binding.productsList.setAdapter(productAdapter);
    }
    ...

    // The callback makes the call to the activity to make the transition.
    private ProductClickCallback productClickCallback = product -> {
        ProductListDirections.ViewProductDetails directions =
                ProductListDirections.navigateToProductDetail(product.getId());
        NavHostFragment.findNavController(this).navigate(directions);
    };
}

Navigazione di primo livello

Se la tua app utilizza DrawerLayout, la tua attività potrebbe prevedere molta logica di configurazione che gestisce l'apertura e la chiusura del riquadro a scomparsa e l'accesso ad altre destinazioni.

L'attività risultante potrebbe avere il seguente aspetto:

Kotlin

class MainActivity : AppCompatActivity(),
    NavigationView.OnNavigationItemSelectedListener {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val toolbar: Toolbar = findViewById(R.id.toolbar)
        setSupportActionBar(toolbar)

        val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout)
        val navView: NavigationView = findViewById(R.id.nav_view)
        val toggle = ActionBarDrawerToggle(
                this,
                drawerLayout,
                toolbar,
                R.string.navigation_drawer_open, 
                R.string.navigation_drawer_close
        )
        drawerLayout.addDrawerListener(toggle)
        toggle.syncState()

        navView.setNavigationItemSelectedListener(this)
    }

    override fun onBackPressed() {
        val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout)
        if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
            drawerLayout.closeDrawer(GravityCompat.START)
        } else {
            super.onBackPressed()
        }
    }

    override fun onNavigationItemSelected(item: MenuItem): Boolean {
        // Handle navigation view item clicks here.
        when (item.itemId) {
            R.id.home -> {
                val homeFragment = HomeFragment()
                show(homeFragment)
            }
            R.id.gallery -> {
                val galleryFragment = GalleryFragment()
                show(galleryFragment)
            }
            R.id.slide_show -> {
                val slideShowFragment = SlideShowFragment()
                show(slideShowFragment)
            }
            R.id.tools -> {
                val toolsFragment = ToolsFragment()
                show(toolsFragment)
            }
        }
        val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout)
        drawerLayout.closeDrawer(GravityCompat.START)
        return true
    }
}

private fun show(fragment: Fragment) {

    val drawerLayout = drawer_layout as DrawerLayout
    val fragmentManager = supportFragmentManager

    fragmentManager
            .beginTransaction()
            .replace(R.id.main_content, fragment)
            .commit()

    drawerLayout.closeDrawer(GravityCompat.START)
}

Java

public class MainActivity extends AppCompatActivity
        implements NavigationView.OnNavigationItemSelectedListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        DrawerLayout drawer = findViewById(R.id.drawer_layout);
        NavigationView navigationView = findViewById(R.id.nav_view);
        ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
                this,
                drawer,
                toolbar,
                R.string.navigation_drawer_open,
                R.string.navigation_drawer_close);
        drawer.addDrawerListener(toggle);
        toggle.syncState();

        navigationView.setNavigationItemSelectedListener(this);
    }

    @Override
    public void onBackPressed() {
        DrawerLayout drawer = findViewById(R.id.drawer_layout);
        if (drawer.isDrawerOpen(GravityCompat.START)) {
            drawer.closeDrawer(GravityCompat.START);
        } else {
            super.onBackPressed();
        }
    }

    @Override
    public boolean onNavigationItemSelected(MenuItem item) {
        // Handle navigation view item clicks here.
        int id = item.getItemId();

        if (id == R.id.home) {
            Fragment homeFragment = new HomeFragment();
            show(homeFragment);
        } else if (id == R.id.gallery) {
            Fragment galleryFragment = new GalleryFragment();
            show(galleryFragment);
        } else if (id == R.id.slide_show) {
            Fragment slideShowFragment = new SlideShowFragment();
            show(slideShowFragment);
        } else if (id == R.id.tools) {
            Fragment toolsFragment = new ToolsFragment();
            show(toolsFragment);
        }

        DrawerLayout drawer = findViewById(R.id.drawer_layout);
        drawer.closeDrawer(GravityCompat.START);
        return true;
    }

    private void show(Fragment fragment) {

        DrawerLayout drawerLayout = findViewById(R.id.drawer_layout);
        FragmentManager fragmentManager = getSupportFragmentManager();

        fragmentManager
                .beginTransaction()
                .replace(R.id.main_content, fragment)
                .commit();

        drawerLayout.closeDrawer(GravityCompat.START);
    }
}

Dopo aver aggiunto il componente Navigazione al progetto e creato un grafico di navigazione, aggiungi ogni destinazione dei contenuti dal grafico (ad esempio Home page, Galleria, Presentazione e Strumenti dell'esempio precedente). Assicurati che i valori id della voce di menu corrispondano ai valori id della destinazione associata, come mostrato di seguito:

<!-- activity_main_drawer.xml -->
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    tools:showIn="navigation_view">

    <group android:checkableBehavior="single">
        <item
            android:id="@+id/home"
            android:icon="@drawable/ic_menu_camera"
            android:title="@string/menu_home" />
        <item
            android:id="@+id/gallery"
            android:icon="@drawable/ic_menu_gallery"
            android:title="@string/menu_gallery" />
        <item
            android:id="@+id/slide_show"
            android:icon="@drawable/ic_menu_slideshow"
            android:title="@string/menu_slideshow" />
        <item
            android:id="@+id/tools"
            android:icon="@drawable/ic_menu_manage"
            android:title="@string/menu_tools" />
    </group>
</menu>
<!-- activity_main_graph.xml -->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main_graph"
    app:startDestination="@id/home">

    <fragment
        android:id="@+id/home"
        android:name="com.example.HomeFragment"
        android:label="Home"
        tools:layout="@layout/home" />

    <fragment
        android:id="@+id/gallery"
        android:name="com.example.GalleryFragment"
        android:label="Gallery"
        tools:layout="@layout/gallery" />

    <fragment
        android:id="@+id/slide_show"
        android:name="com.example.SlideShowFragment"
        android:label="Slide Show"
        tools:layout="@layout/slide_show" />

    <fragment
        android:id="@+id/tools"
        android:name="com.example.ToolsFragment"
        android:label="Tools"
        tools:layout="@layout/tools" />

</navigation>

Se crei una corrispondenza con i valori id del menu e del grafico, puoi collegare il NavController per questa attività per gestire automaticamente la navigazione in base alla voce di menu. L'NavController gestisce anche l'apertura e la chiusura del DrawerLayout e il comportamento dei pulsanti Su e Indietro in modo appropriato.

Puoi quindi aggiornare MainActivity per collegare NavController a Toolbar e NavigationView.

Vedi il seguente snippet per un esempio:

Kotlin

class MainActivity : AppCompatActivity()  {

    val drawerLayout by lazy { findViewById<DrawerLayout>(R.id.drawer_layout) }
    val navController by lazy {
      (supportFragmentManager.findFragmentById(R.id.main_content) as NavHostFragment).navController
    }
    val navigationView by lazy { findViewById<NavigationView>(R.id.nav_view) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val toolbar = findViewById<Toolbar>(R.id.toolbar)
        setSupportActionBar(toolbar)

        // Show and Manage the Drawer and Back Icon
        setupActionBarWithNavController(navController, drawerLayout)

        // Handle Navigation item clicks
        // This works with no further action on your part if the menu and destination id’s match.
        navigationView.setupWithNavController(navController)

    }

    override fun onSupportNavigateUp(): Boolean {
        // Allows NavigationUI to support proper up navigation or the drawer layout
        // drawer menu, depending on the situation
        return navController.navigateUp(drawerLayout)
    }
}

Java

public class MainActivity extends AppCompatActivity {

    private DrawerLayout drawerLayout;
    private NavController navController;
    private NavigationView navigationView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        drawerLayout = findViewById(R.id.drawer_layout);
        NavHostFragment navHostFragment = (NavHostFragment)
            getSupportFragmentManager().findFragmentById(R.id.main_content);
        navController = navHostFragment.getNavController();
        navigationView = findViewById(R.id.nav_view);

        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        // Show and Manage the Drawer and Back Icon
        NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout);

        // Handle Navigation item clicks
        // This works with no further action on your part if the menu and destination id’s match.
        NavigationUI.setupWithNavController(navigationView, navController);

    }

    @Override
    public boolean onSupportNavigateUp() {
        // Allows NavigationUI to support proper up navigation or the drawer layout
        // drawer menu, depending on the situation.
        return NavigationUI.navigateUp(navController, drawerLayout);

    }
}

Puoi utilizzare questa stessa tecnica sia con la navigazione basata su BottomNavigatorView sia con la navigazione basata su menu. Per altri esempi, consulta Aggiornare i componenti dell'interfaccia utente con NavigationUI.

Aggiungi destinazioni delle attività

Dopo aver collegato ogni schermo nell'app per utilizzare il componente Navigazione e non stai più utilizzando FragmentTransactions per la transizione tra destinazioni basate su frammenti, il passaggio successivo consiste nell'eliminare startActivity chiamate.

Innanzitutto, identifica i punti della tua app in cui hai due grafici di navigazione separati e utilizzi startActivity per passare da uno all'altro.

Questo esempio contiene due grafici (A e B) e una chiamata startActivity() per la transizione da A a B.

Kotlin

fun navigateToProductDetails(productId: String) {
    val intent = Intent(this, ProductDetailsActivity::class.java)
    intent.putExtra(KEY_PRODUCT_ID, productId)
    startActivity(intent)
}

Java

private void navigateToProductDetails(String productId) {
    Intent intent = new Intent(this, ProductDetailsActivity.class);
    intent.putExtra(KEY_PRODUCT_ID, productId);
    startActivity(intent);

Sostituiscile con una destinazione dell'attività nel grafico A che rappresenti la navigazione verso l'attività host del grafico B. Se hai argomenti da passare alla destinazione iniziale del grafico B, puoi designarli nella definizione della destinazione dell'attività.

Nell'esempio seguente, il grafico A definisce una destinazione di attività che prende un argomento product_id insieme a un'azione. Il grafico B non contiene modifiche.

La rappresentazione XML dei grafici A e B potrebbe avere il seguente aspetto:

<!-- Graph A -->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/product_list_graph"
    app:startDestination="@id/product_list">

    <fragment
        android:id="@+id/product_list"
        android:name="com.example.android.persistence.ui.ProductListFragment"
        android:label="Product List"
        tools:layout="@layout/product_list_fragment">
        <action
            android:id="@+id/navigate_to_product_detail"
            app:destination="@id/product_details_activity" />
    </fragment>

    <activity
        android:id="@+id/product_details_activity"
        android:name="com.example.android.persistence.ui.ProductDetailsActivity"
        android:label="Product Details"
        tools:layout="@layout/product_details_host">

        <argument
            android:name="product_id"
            app:argType="integer" />

    </activity>

</navigation>
<!-- Graph B -->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    app:startDestination="@id/product_details">

    <fragment
        android:id="@+id/product_details"
        android:name="com.example.android.persistence.ui.ProductDetailsFragment"
        android:label="Product Details"
        tools:layout="@layout/product_details_fragment">
        <argument
            android:name="product_id"
            app:argType="integer" />
    </fragment>

</navigation>

Puoi accedere all'attività host del grafico B utilizzando gli stessi meccanismi che utilizzi per raggiungere le destinazioni dei frammenti:

Kotlin

fun navigateToProductDetails(productId: String) {
    val directions = ProductListDirections.navigateToProductDetail(productId)
    findNavController().navigate(directions)
}

Java

private void navigateToProductDetails(String productId) {
    ProductListDirections.NavigateToProductDetail directions =
            ProductListDirections.navigateToProductDetail(productId);
    Navigation.findNavController(getView()).navigate(directions);

Passa gli argomenti di destinazione dell'attività a un frammento di destinazione iniziale

Se l'attività di destinazione riceve extra, come nell'esempio precedente, puoi passarli direttamente alla destinazione iniziale come argomenti, ma devi impostare manualmente il grafico di navigazione dell'host all'interno del metodo onCreate() dell'attività host in modo da poter passare gli extra relativi agli intent come argomenti al frammento, come mostrato di seguito:

Kotlin

class ProductDetailsActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.product_details_host)
        val navHostFragment = supportFragmentManager.findFragmentById(R.id.main_content) as NavHostFragment
        val navController = navHostFramgent.navController
        navController
                .setGraph(R.navigation.product_detail_graph, intent.extras)
    }

}

Java

public class ProductDetailsActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.product_details_host);
        NavHostFragment navHostFragment = (NavHostFragment)
            getSupportFragmentManager().findFragmentById(R.id.main_content);
        NavController navController = navHostFragment.getNavController();
        navController
                .setGraph(R.navigation.product_detail_graph, getIntent().getExtras());
    }

}

I dati possono essere estratti dagli argomenti frammento Bundle utilizzando la classe args generata, come mostrato nell'esempio seguente:

Kotlin

class ProductDetailsFragment : Fragment() {

    val args by navArgs<ProductDetailsArgs>()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val productId = args.productId
        ...
    }
    ...

Java

public class ProductDetailsFragment extends Fragment {

    ProductDetailsArgs args;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        args = ProductDetailsArgs.fromBundle(requireArguments());
    }

    @Override
    public void onViewCreated(@NonNull View view,
            @Nullable Bundle savedInstanceState) {
       int productId = args.getProductId();
       ...
    }
    ...

Combinare le attività

Puoi combinare grafici di navigazione nei casi in cui più attività condividono lo stesso layout, ad esempio un semplice FrameLayout contenente un singolo frammento. Nella maggior parte di questi casi, puoi semplicemente combinare tutti gli elementi di ciascun grafico di navigazione e aggiornare gli elementi di destinazione delle attività alle destinazioni dei frammenti.

L'esempio seguente combina i grafici A e B della sezione precedente:

Prima di combinare:

<!-- Graph A -->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/product_list_graph"
    app:startDestination="@id/product_list">

    <fragment
        android:id="@+id/product_list"
        android:name="com.example.android.persistence.ui.ProductListFragment"
        android:label="Product List Fragment"
        tools:layout="@layout/product_list">
        <action
            android:id="@+id/navigate_to_product_detail"
            app:destination="@id/product_details_activity" />
    </fragment>
    <activity
        android:id="@+id/product_details_activity"
        android:name="com.example.android.persistence.ui.ProductDetailsActivity"
        android:label="Product Details Host"
        tools:layout="@layout/product_details_host">
        <argument android:name="product_id"
            app:argType="integer" />
    </activity>

</navigation>
<!-- Graph B -->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/product_detail_graph"
    app:startDestination="@id/product_details">

    <fragment
        android:id="@+id/product_details"
        android:name="com.example.android.persistence.ui.ProductDetailsFragment"
        android:label="Product Details"
        tools:layout="@layout/product_details">
        <argument
            android:name="product_id"
            app:argType="integer" />
    </fragment>
</navigation>

Dopo la combinazione:

<!-- Combined Graph A and B -->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/product_list_graph"
    app:startDestination="@id/product_list">

    <fragment
        android:id="@+id/product_list"
        android:name="com.example.android.persistence.ui.ProductListFragment"
        android:label="Product List Fragment"
        tools:layout="@layout/product_list">
        <action
            android:id="@+id/navigate_to_product_detail"
            app:destination="@id/product_details" />
    </fragment>

    <fragment
        android:id="@+id/product_details"
        android:name="com.example.android.persistence.ui.ProductDetailsFragment"
        android:label="Product Details"
        tools:layout="@layout/product_details">
        <argument
            android:name="product_id"
            app:argType="integer" />
    </fragment>

</navigation>

Mantenere gli stessi nomi delle azioni durante l'unione può rendere questo processo semplice, che non richiede modifiche al codebase esistente. Ad esempio, navigateToProductDetail rimane invariato qui. L'unica differenza è che questa azione ora rappresenta la navigazione verso una destinazione del frammento all'interno della stessa NavHost anziché la destinazione di un'attività:

Kotlin

fun navigateToProductDetails(productId: String) {
    val directions = ProductListDirections.navigateToProductDetail(productId)
    findNavController().navigate(directions)
}

Java

private void navigateToProductDetails(String productId) {
    ProductListDirections.NavigateToProductDetail directions =
            ProductListDirections.navigateToProductDetail(productId);
    Navigation.findNavController(getView()).navigate(directions);

Risorse aggiuntive

Per ulteriori informazioni relative alla navigazione, vedi i seguenti argomenti: