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:
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
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)
| Elemento | Responsabilidade recomendada | Evite |
|---|---|---|
| Activity | Host de navegação, UI global (toolbar), integração com sistema | Colocar lógica de tela específica de lista/detalhe |
| Fragment | Renderizar uma tela, lidar com eventos de UI, navegar | Guardar referência de view fora do ciclo de vida (sem limpar binding) |
| ViewModel | Estado e dados da tela, sobrevivência à rotação | Referenciar Views/Context diretamente |
| Arguments (Safe Args) | Entrada imutável para configurar a tela (ex.: itemId) | Usar chaves string “na mão” sem necessidade |