Activity e Fragment em Android com Kotlin: responsabilidades e implementação

Capítulo 5

Tempo estimado de leitura: 10 minutos

+ Exercício

Activity vs. Fragment: quem faz o quê no app moderno

Em apps Android atuais, é comum separar responsabilidades entre Activity e Fragment para ganhar organização, reutilização e melhor suporte a diferentes tamanhos de tela.

Activity (host/contêiner)

  • Hospeda a UI principal (normalmente um FragmentContainerView).
  • Coordena navegação entre telas (especialmente no padrão Single-Activity).
  • Integra componentes do sistema: permissões, intents, deep links, etc.
  • Gerencia elementos globais: toolbar, bottom navigation, drawer, etc.

Fragment (conteúdo/tela)

  • Representa uma tela ou parte de uma tela.
  • Possui ciclo de vida próprio e pode ser adicionado/removido dinamicamente.
  • Encapsula lógica de UI: inflar layout, lidar com cliques, renderizar dados.
  • Recebe argumentos para exibir conteúdo específico (ex.: id do item).

Padrão comum: Single-Activity com Fragments

No padrão Single-Activity, você tem uma única Activity (por exemplo, MainActivity) que contém um NavHostFragment (Navigation Component) e as telas são Fragments. Isso reduz complexidade de múltiplas Activities e centraliza navegação.

Ciclo de vida e inflação de layout (o essencial)

Activity: ponto de entrada

Em geral, você configura o layout e o host de navegação no onCreate.

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

Fragment: inflar e acessar views

O Fragment cria sua UI em onCreateView (ou usando o construtor com Fragment(R.layout...)). Depois, em onViewCreated, você configura listeners e renderização.

class ListFragment : Fragment(R.layout.fragment_list) {    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {        super.onViewCreated(view, savedInstanceState)        // configurar UI, listeners, adapters etc.    }}

Use ViewBinding para evitar findViewById e reduzir erros. Exemplo típico no Fragment:

Continue em nosso aplicativo e ...
  • Ouça o áudio com a tela desligada
  • Ganhe Certificado após a conclusão
  • + de 5000 cursos para você explorar!
ou continue lendo abaixo...
Download App

Baixar o aplicativo

class DetailFragment : Fragment(R.layout.fragment_detail) {    private var _binding: FragmentDetailBinding? = null    private val binding get() = _binding!!    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {        super.onViewCreated(view, savedInstanceState)        _binding = FragmentDetailBinding.bind(view)        binding.titleTextView.text = "..."    }    override fun onDestroyView() {        super.onDestroyView()        _binding = null    }}

O padrão _binding evita vazamento de memória, porque a view do Fragment pode ser destruída antes do Fragment em si.

Argumentos: Bundle vs. Safe Args

Bundle (manual)

Você pode passar dados para um Fragment via arguments:

val fragment = DetailFragment().apply {    arguments = bundleOf("itemId" to 42)}

No Fragment, leia com:

val itemId = requireArguments().getInt("itemId")

Funciona, mas é fácil errar a chave ou o tipo.

Safe Args (Navigation Component)

Quando você usa o Navigation Component, o Safe Args gera classes tipadas para argumentos, reduzindo erros. Você define argumentos no grafo de navegação e navega com ações tipadas.

Exemplo de leitura no Fragment:

private val args: DetailFragmentArgs by navArgs()override fun onViewCreated(view: View, savedInstanceState: Bundle?) {    super.onViewCreated(view, savedInstanceState)    val itemId = args.itemId}

Comunicação Fragment → Activity (eventos)

Em apps modernos, evite o Fragment “chamar métodos” diretamente na Activity como dependência rígida. Prefira padrões que desacoplam:

1) Navigation Component (recomendado para navegação)

Para eventos de navegação, o Fragment pode chamar:

findNavController().navigate(    ListFragmentDirections.actionListFragmentToDetailFragment(itemId = 42))

2) Fragment Result API (passar resultado de volta)

Útil quando um Fragment precisa retornar um valor para outro (ou para quem estiver ouvindo), sem acoplamento.

// No Fragment que envia resultadofindNavController().previousBackStackEntry?.savedStateHandle?.set("selectedId", 42)

No Fragment anterior:

val navController = findNavController()val handle = navController.currentBackStackEntry?.savedStateHandlehandle?.getLiveData<Int>("selectedId")?.observe(viewLifecycleOwner) { id ->    // usar id}

3) Interface de callback (quando fizer sentido)

Se você precisar que a Activity execute uma ação específica (ex.: abrir um diálogo global), pode usar interface. Use com cuidado para não criar dependência forte.

interface HostActions {    fun showGlobalMessage(text: String)}class SomeFragment : Fragment(R.layout.fragment_some) {    private val hostActions: HostActions        get() = requireActivity() as HostActions    fun onSomethingHappened() {        hostActions.showGlobalMessage("Olá")    }}

Prática: duas telas com Fragment (lista e detalhe), com passagem de dados e rotação

Você vai construir um fluxo simples: uma lista de itens e uma tela de detalhe. A navegação será feita com Navigation Component e Safe Args. O tratamento de rotação será feito preservando estado com ViewModel e SavedStateHandle (para o detalhe) e mantendo a seleção/scroll quando aplicável.

1) Dependências e plugins (Safe Args)

No build.gradle (nível do módulo app), habilite o Safe Args (o nome exato do plugin pode variar conforme a configuração do projeto):

plugins {    id("androidx.navigation.safeargs.kotlin")}

E garanta dependências do Navigation Component:

dependencies {    implementation("androidx.navigation:navigation-fragment-ktx:2.7.7")    implementation("androidx.navigation:navigation-ui-ktx:2.7.7")}

2) Layout da Activity com NavHost

Crie activity_main.xml com um contêiner de navegação:

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

3) Grafo de navegação com lista e detalhe

Crie res/navigation/nav_graph.xml com dois destinos: ListFragment e DetailFragment. No detalhe, defina um argumento itemId (Int).

<navigation xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    android:id="@+id/nav_graph"    app:startDestination="@id/listFragment">    <fragment        android:id="@+id/listFragment"        android:name="com.example.app.ListFragment"        android:label="Lista">        <action            android:id="@+id/action_listFragment_to_detailFragment"            app:destination="@id/detailFragment" />    </fragment>    <fragment        android:id="@+id/detailFragment"        android:name="com.example.app.DetailFragment"        android:label="Detalhe">        <argument            android:name="itemId"            app:argType="integer" />    </fragment></navigation>

4) Modelo de dados e repositório simples

Para a prática, use uma lista em memória:

data class Item(val id: Int, val title: String, val description: String)object ItemRepository {    private val items = List(30) { index ->        val id = index + 1        Item(            id = id,            title = "Item #$id",            description = "Descrição do item #$id"        )    }    fun getAll(): List<Item> = items    fun getById(id: Int): Item = items.first { it.id == id }}

5) Tela 1: Fragment de lista

Você pode implementar a lista com RecyclerView. Aqui vai um exemplo direto com um adapter simples.

5.1) Layout do Fragment de lista

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical">    <androidx.recyclerview.widget.RecyclerView        android:id="@+id/recyclerView"        android:layout_width="match_parent"        android:layout_height="match_parent" /></LinearLayout>

5.2) Layout de item da lista

<TextView xmlns:android="http://schemas.android.com/apk/res/android"    android:id="@+id/titleTextView"    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:padding="16dp"    android:textSize="18sp" />

5.3) Adapter

class ItemAdapter(    private val onClick: (Item) -> Unit) : RecyclerView.Adapter<ItemAdapter.VH>() {    private val data = mutableListOf<Item>()    fun submitList(items: List<Item>) {        data.clear()        data.addAll(items)        notifyDataSetChanged()    }    class VH(itemView: View) : RecyclerView.ViewHolder(itemView) {        val title: TextView = itemView.findViewById(R.id.titleTextView)    }    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {        val view = LayoutInflater.from(parent.context)            .inflate(R.layout.row_item, parent, false)        return VH(view)    }    override fun onBindViewHolder(holder: VH, position: Int) {        val item = data[position]        holder.title.text = item.title        holder.itemView.setOnClickListener { onClick(item) }    }    override fun getItemCount(): Int = data.size}

5.4) ListFragment com navegação para detalhe (Safe Args)

class ListFragment : Fragment(R.layout.fragment_list) {    private var _binding: FragmentListBinding? = null    private val binding get() = _binding!!    private lateinit var adapter: ItemAdapter    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {        super.onViewCreated(view, savedInstanceState)        _binding = FragmentListBinding.bind(view)        adapter = ItemAdapter { item ->            val directions = ListFragmentDirections                .actionListFragmentToDetailFragment(itemId = item.id)            findNavController().navigate(directions)        }        binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())        binding.recyclerView.adapter = adapter        adapter.submitList(ItemRepository.getAll())    }    override fun onDestroyView() {        super.onDestroyView()        _binding = null    }}

6) Tela 2: Fragment de detalhe

6.1) Layout do detalhe

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent">    <LinearLayout        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:orientation="vertical"        android:padding="16dp">        <TextView            android:id="@+id/titleTextView"            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:textSize="22sp" />        <TextView            android:id="@+id/descriptionTextView"            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:layout_marginTop="12dp"            android:textSize="16sp" />    </LinearLayout></ScrollView>

6.2) ViewModel do detalhe com SavedStateHandle (rotação)

Ao rotacionar, o Fragment é recriado, mas o ViewModel pode sobreviver. Com SavedStateHandle, você também consegue restaurar o argumento e qualquer estado adicional.

class DetailViewModel(    private val savedStateHandle: SavedStateHandle) : ViewModel() {    private val itemId: Int = savedStateHandle["itemId"] ?: error("itemId ausente")    val item: Item = ItemRepository.getById(itemId)}

Para que o itemId chegue ao SavedStateHandle, use o mesmo nome do argumento do Navigation (itemId). O Navigation integra automaticamente argumentos ao SavedStateHandle quando você usa by viewModels() no Fragment com as dependências corretas.

6.3) DetailFragment lendo args e renderizando

class DetailFragment : Fragment(R.layout.fragment_detail) {    private var _binding: FragmentDetailBinding? = null    private val binding get() = _binding!!    private val args: DetailFragmentArgs by navArgs()    private val viewModel: DetailViewModel by viewModels()    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {        super.onViewCreated(view, savedInstanceState)        _binding = FragmentDetailBinding.bind(view)        // Garante que o argumento existe (útil para debug)        val idFromArgs = args.itemId        val item = viewModel.item        binding.titleTextView.text = item.title        binding.descriptionTextView.text = item.description    }    override fun onDestroyView() {        super.onDestroyView()        _binding = null    }}

Observação: aqui usamos args.itemId e também o viewModel.item. Em um app real, você normalmente usaria apenas uma fonte (preferencialmente o ViewModel) e deixaria o Fragment apenas renderizar.

Tratamento de rotação: o que preservar e como

1) Dados do detalhe

Como o detalhe deriva de um itemId passado por argumento, ele é naturalmente reconstituível. O uso de ViewModel + SavedStateHandle evita recomputações e facilita manter estado adicional (ex.: “favoritado”).

Exemplo de estado adicional persistido:

class DetailViewModel(private val state: SavedStateHandle) : ViewModel() {    private val itemId: Int = state["itemId"] ?: error("itemId ausente")    val item: Item = ItemRepository.getById(itemId)    var isFavorite: Boolean        get() = state["isFavorite"] ?: false        set(value) { state["isFavorite"] = value }}

2) Posição de scroll/seleção na lista

O RecyclerView costuma restaurar posição de scroll automaticamente se ele mantiver o mesmo adapter e layout manager e se o estado for salvo/restaurado. Se você precisar garantir, pode salvar o estado do layout manager no onSaveInstanceState do Fragment e restaurar em onViewStateRestored.

class ListFragment : Fragment(R.layout.fragment_list) {    private var recyclerState: Parcelable? = null    override fun onSaveInstanceState(outState: Bundle) {        super.onSaveInstanceState(outState)        val lm = view?.findViewById<RecyclerView>(R.id.recyclerView)?.layoutManager        outState.putParcelable("recycler_state", lm?.onSaveInstanceState())    }    override fun onViewStateRestored(savedInstanceState: Bundle?) {        super.onViewStateRestored(savedInstanceState)        recyclerState = savedInstanceState?.getParcelable("recycler_state")    }    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {        super.onViewCreated(view, savedInstanceState)        val rv = view.findViewById<RecyclerView>(R.id.recyclerView)        rv.layoutManager = LinearLayoutManager(requireContext())        // ...set adapter e lista...        recyclerState?.let { state ->            rv.layoutManager?.onRestoreInstanceState(state)        }    }}

Checklist de responsabilidades (para evitar acoplamento e bugs)

ElementoResponsabilidade recomendadaEvite
ActivityHost de navegação, UI global (toolbar), integração com sistemaColocar lógica de tela específica de lista/detalhe
FragmentRenderizar uma tela, lidar com eventos de UI, navegarGuardar referência de view fora do ciclo de vida (sem limpar binding)
ViewModelEstado e dados da tela, sobrevivência à rotaçãoReferenciar Views/Context diretamente
Arguments (Safe Args)Entrada imutável para configurar a tela (ex.: itemId)Usar chaves string “na mão” sem necessidade

Agora responda o exercício sobre o conteúdo:

Em um app no padrão Single-Activity com Navigation Component, qual combinação de responsabilidades está mais alinhada ao uso de Activity e Fragment para manter baixo acoplamento?

Você acertou! Parabéns, agora siga para a próxima página

Você errou! Tente novamente.

No padrão Single-Activity, a Activity funciona como contêiner e integrações globais. Cada Fragment representa uma tela, infla e configura a UI no seu ciclo de vida e pode navegar via Navigation Component, evitando acoplamento forte com a Activity.

Próximo capitúlo

Navegação Android com Kotlin: Navigation Component e fluxo entre telas

Arrow Right Icon
Capa do Ebook gratuito Android para Iniciantes com Kotlin: construindo seu primeiro app moderno
33%

Android para Iniciantes com Kotlin: construindo seu primeiro app moderno

Novo curso

15 páginas

Baixe o app para ganhar Certificação grátis e ouvir os cursos em background, mesmo com a tela desligada.