Eseguire la migrazione al componente di navigazione

Il componente Navigazione è una libreria in grado di gestire navigazione complessa, animazione delle transizioni, link diretti e un argomento con modalità di compilazione verificata che passa tra le schermate dell'app.

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

A livello generale, la migrazione prevede i seguenti passaggi:

  1. Rimuovi la logica dell'interfaccia utente specifica per la schermata dalle attività. Sposta l'UI dell'app. uscire dalle attività, assicurandosi che ciascuna attività possieda solo la logica di componenti della UI di navigazione globale, ad esempio Toolbar, mentre delega implementazione di ogni schermata su un frammento o una destinazione personalizzata.

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

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

  4. Combina attività: combina i grafici di navigazione nei casi in cui più attività condividono lo stesso layout.

di Gemini Advanced.

Prerequisiti

Questa guida presuppone che tu abbia già eseguito la migrazione della tua app per utilizzarla librerie AndroidX. Se non lo hai già fatto, eseguire la migrazione del progetto per utilizzare AndroidX prima continua.

Sposta logica UI 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 per far sapere ad Android quali attività sono disponibili per l'avvio. L'attività consente alla tua app di reagire anche ai cambiamenti di Android, ad esempio quando l'UI dell'app entra o esce dal primo piano, ruota e così via. La attività può servire anche da luogo di condividere lo stato tra le schermate.

Nel contesto della tua app, le attività devono fungere da host per la navigazione. e dovrebbe contenere la logica e la conoscenza di come passare da uno schermo all'altro, trasferire dati e così via. Tuttavia, è preferibile gestire i dettagli della UI. in una parte più piccola e riutilizzabile dell'interfaccia utente. L'implementazione consigliata in questo caso è fragments. 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-fragment. La navigazione supporta anche tipi di destinazioni personalizzate.

Se la tua app non utilizza frammenti, la prima cosa da fare è eseguire la migrazione ogni schermata dell'app utilizzando un frammento. Non stai rimuovendo l'attività in a questo punto. ma piuttosto un frammento che rappresenta lo schermo e si rompe a parte la logica dell'UI in base alla responsabilità.

Introduzione ai frammenti

Per illustrare il processo di introduzione dei frammenti, iniziamo con un esempio di un'applicazione composta da due schermate: una per l'elenco dei prodotti e una schermata con i dettagli del prodotto. Facendo clic su un prodotto nella schermata dell'elenco, a una schermata dei dettagli per scoprire di più sul prodotto.

In questo esempio, le schermate con elenco e dettagli sono attualmente attività separate.

Crea un nuovo layout per ospitare l'UI

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

Per una visualizzazione semplice, puoi utilizzare un FrameLayout, come mostrato di seguito esempio 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, a cui in seguito aggiungeremo il parametro .

Successivamente, nella funzione onCreate() dell'attività, modifica il riferimento del file di layout nella funzione onCreate dell'attività per puntare 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 (in questo esempio product_list) viene utilizzato come vista principale per il frammento che stai per creare.

Crea un frammento

Crea un nuovo frammento per gestire l'UI per il tuo schermo. È buona prassi coerente con il nome host dell'attività. Lo snippet riportato di seguito 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

Con la definizione del frammento, il passaggio successivo è spostare la logica dell'interfaccia utente schermata dall'attività al nuovo frammento. Se provieni da basata sull'attività, probabilmente c'è molta logica per la creazione delle viste che si verificano nella funzione onCreate() della tua attività.

Ecco un esempio di schermata basata sull'attività con logica UI 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 controllare quando e come l'utente accede 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 quest'opera tra onCreateView() e onViewCreated(), mentre nell'attività rimane solo la logica di navigazione:

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, noterai che non è possibile effettuare chiamate a setContentView() per gonfiare e collegare il layout. In un frammento, onCreateView() inizializza la classe vista root. onCreateView() prende un'istanza di un LayoutInflater che può essere utilizzato per gonfiare la vista principale in base a un file di risorse di layout. Questo esempio riutilizza il metodo layout product_list esistente che è stato utilizzato dall'attività perché non è presente nulla deve modificare il layout stesso.

Se disponi di logiche UI all'interno di onStart(), onResume() dell'attività, Le funzioni onPause() o onStop() non correlate alla navigazione, puoi spostarle nelle funzioni corrispondenti con lo stesso nome sul frammento.

Inizializzare il frammento nell'attività host

Dopo aver spostato tutta la logica dell'interfaccia utente verso il basso nel frammento, verrà utilizzata solo la 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(), 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 per modificare la configurazione, quindi devi aggiungere il frammento solo se savedInstanceState è nullo.

Passa gli extra per intent al frammento

Se la tua attività riceve Extras tramite un intent, puoi trasmetterli all' direttamente come argomenti.

In questo esempio, ProductDetailsFragment riceve direttamente i propri argomenti dagli extra relativi all'intenzione 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 riuscire a testare l'esecuzione dell'app con la prima schermata aggiornato per utilizzare un frammento. Continua con la migrazione del resto dei dati basati sulle attività schermate, il che richiede tempo per la verifica 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 nel Note di rilascio della libreria di navigazione.

Crea un grafico di navigazione

Il componente Navigazione rappresenta la configurazione di navigazione dell'app in un un file di risorse sotto forma di grafico, proprio come sono rappresentate le visualizzazioni dell'app. Ciò consente di mantenere la navigazione dell'app organizzata al di fuori del codebase e fornire un modo per modificare visivamente la navigazione dell'app.

Per creare un grafico di navigazione, crea innanzitutto una nuova cartella di risorse 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 Navigazione utilizza un'attività come host per la navigazione e scambia i singoli frammenti all'interno dell'host mentre gli utenti navigano la tua app. Per poter iniziare a organizzare visivamente la navigazione dell'app, devi devi configurare un NavHost all'interno dell'attività che lo ospiterà grafico. Poiché utilizziamo i frammenti, possiamo usare il comando implementazione predefinita di NavHost, NavHostFragment

Un NavHostFragment viene configurato tramite un FragmentContainerView posizionati 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à comporta l'gonfiamento del grafico di navigazione e l'impostazione del grafico in NavHostFragment. L'attributo app:defaultNavHost garantisce che NavHostFragment intercetta il pulsante Indietro del sistema.

Se utilizzi la navigazione di primo livello, come DrawerLayout o BottomNavigationView, questa FragmentContainerView sostituisce l'elemento principale della visualizzazione dei contenuti. Consulta Aggiornare i componenti dell'UI con NavigationUI per consultare alcuni esempi.

Per un layout semplice, puoi includere questo elemento: FragmentContainerView come elemento secondario della radice 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, sotto Destinazioni, puoi vedere un riferimento all'attività NavHost nel modulo. di layout_name (resource_id).

Fai clic sul pulsante Più in alto 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 nel grafico, ma tieni presente che le destinazioni delle attività sono considerate destinazioni dei terminal perché, dopo aver raggiunto un'attività destinazione, opera all'interno di un host di navigazione e di un grafico separati.

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

Rimuovi transazioni dei frammenti

Ora che stai utilizzando il componente Navigazione, se ti sposti tra schermate con frammenti nella stessa attività, puoi rimuovere FragmentManager e interazioni.

Se la tua app utilizza più frammenti nella stessa attività o di primo livello come il layout a scomparsa o la navigazione in basso, probabilmente usando un FragmentManager FragmentTransactions per aggiungere o sostituire frammenti nella sezione del contenuto principale della UI. Ora è possibile essere sostituita e semplificata nel componente Navigazione fornendo azioni per collegare le destinazioni nel grafico e la navigazione utilizzando NavController.

Ecco alcuni scenari che potresti incontrare e il tuo approccio per ogni scenario.

Una singola attività che gestisce più frammenti

Se hai una singola attività che gestisce più frammenti, la tua 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, potresti richiamare una funzione di navigazione 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())
    );
}

Per sostituire questo valore, aggiorna il grafico di navigazione la destinazione iniziale e le azioni per collegare le destinazioni e definire argomenti dove 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 la tua 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(). Nei prossimi aggiorniamo ProductListFragment in modo che utilizzi NavController per la navigazione alla schermata successiva dei dettagli del prodotto.

Passa argomenti in sicurezza

Il componente Navigazione ha un plug-in Gradle chiamato Arg sicuri che genera semplici classi di oggetti e builder per l'accesso sicuro per tipo per le destinazioni e le azioni.

Una volta applicato il plug-in, tutti gli argomenti definiti su una destinazione nel grafico di navigazione fa sì che la struttura del componente Navigazione generi un 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 configurazione Directions che può essere utilizzata per indicare a NavController come indirizzare l'utente la destinazione target. Quando un'azione rimanda a una destinazione che richiede argomenti, la classe Directions generata include metodi costruttore che richiedono questi parametri.

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

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 un DrawerLayout, potresti avere molta logica di configurazione nell'attività che gestisce l'apertura e la chiusura del riquadro a scomparsa e la navigazione per altre destinazioni.

L'attività risultante potrebbe avere un aspetto simile a questo:

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 aver creato un grafico di navigazione, aggiungi ogni destinazione dei contenuti dal grafico (ad esempio Home, Galleria, SlideShow e Strumenti nell'esempio precedente). Assicurati che che i valori della voce di menu id corrispondano ai valori id di destinazione associati; 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 corrisponde ai valori id del menu e del grafico, puoi collegare il NavController per questa attività per gestire automaticamente la navigazione in base a alla voce di menu. NavController gestisce anche l'apertura e la chiusura DrawerLayout e come gestire il comportamento dei pulsanti Su e Indietro in modo appropriato.

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

Vedi lo snippet che segue per avere 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 con la navigazione basata su BottomNavigatorView e navigazione basata su menu. Consulta Aggiornare i componenti dell'UI con NavigationUI per altri esempi.

Aggiungi destinazioni per le attività

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

Per prima cosa, identifica i punti dell'app in cui sono disponibili due grafici di navigazione separati e utilizzano startActivity per la transizione da un ambiente all'altro.

Questo esempio contiene due grafici (A e B) e una chiamata startActivity() a 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);

Poi, sostituisci questi elementi con una destinazione attività nel grafico A che rappresenti vai all'attività che ospita il grafico B. Se hai argomenti da passare destinazione iniziale del grafico B, puoi designarla nella destinazione dell'attività definizione di Kubernetes.

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

La rappresentazione XML dei grafici A e B potrebbe essere simile alla seguente:

<!-- 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 utilizzati per accedere alle 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 della destinazione dell'attività a un frammento della destinazione di inizio

Se l'attività relativa alla destinazione riceve extra, come nell'esempio precedente, passarli direttamente alla destinazione iniziale come argomenti, ma devi impostare manualmente il grafico di navigazione del padrone di casa all'interno dei campi onCreate() in modo da poter passare gli extra per intent come argomenti 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 dei frammenti Bundle utilizzando il metodo generata args, 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();
       ...
    }
    ...

Combina le attività

È possibile combinare i grafici di navigazione nei casi in cui più attività condividano lo stesso layout, ad esempio un elemento FrameLayout semplice contenente un singolo frammento. Nella nella maggior parte dei casi, basta combinare tutti gli elementi di grafico di navigazione e aggiornamento di eventuali elementi della destinazione dell'attività in frammentazione destinazioni.

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

Prima della combinazione:

<!-- 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ò semplificare l'operazione senza dover modificare il codebase esistente. Ad esempio: Il valore navigateToProductDetail rimane invariato qui. L'unica differenza è che questa azione ora rappresenta la navigazione verso una destinazione di frammento all'interno della stessa NavHost anziché una destinazione per l'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, consulta i seguenti argomenti: