🎯 O desafio

Você vai receber um componente React Native que faz tudo num arquivo só — busca de dados, estado e UI misturados. Sua missão é refatorá-lo nas 3 camadas do MVVM que vimos nos slides 8 a 10, sem mudar o comportamento do app.

Não é construir tela nova (isso você já fez em UX) e não é escrever testes (isso é a aula 7). É reorganizar código para separar responsabilidades.

1Setup

Se já tem o app do grupo (Aventura), use-o e só crie as pastas. Senão, crie um projeto Expo do zero:

# Projeto novo (pule se já tem o app do grupo)
npx create-expo-app aventura-mvvm --template blank-typescript
cd aventura-mvvm

# Crie as pastas das 3 camadas
mkdir model viewmodels views

# Rode no celular com o Expo Go
npx expo start

2Ponto de partida — o código "ANTES" 😱

Copie este componente para o seu projeto (ex.: App.tsx ou views/EspetaculosScreen.tsx). Repare como o fetch, o estado e a UI estão todos no mesmo lugar:

EspetaculosScreen.tsx
// EspetaculosScreen.tsx — tudo num arquivo só 😱
import { useEffect, useState } from 'react';
import { FlatList, Text, View, ActivityIndicator } from 'react-native';

export default function EspetaculosScreen() {
  const [items, setItems] = useState<any[]>([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch('https://api.aventura.app/espetaculos')   // fetch dentro da View
      .then(r => r.json())
      .then(d => { setItems(d); setLoading(false); });
  }, []);

  if (loading) return <ActivityIndicator size="large" />;
  return (
    <FlatList
      data={items}
      keyExtractor={(i) => i.id}
      renderItem={({ item }) => (
        <View style={{ padding: 12 }}>
          <Text>{item.nome} ★ {item.rating}</Text>
        </View>
      )}
    />
  );
}
🔌 Sem backend rodando? O endpoint acima é fictício. Use este mock no lugar do fetch para o app renderizar mesmo offline — depois é só mover ele para o Model:
const MOCK = [
  { id: '1', nome: 'O Auto da Compadecida', categoria: 'Teatro', rating: 4.8 },
  { id: '2', nome: 'Cirque Aventura',        categoria: 'Circo',  rating: 4.5 },
  { id: '3', nome: 'Sinfônica ao Pôr do Sol', categoria: 'Música', rating: 4.9 },
];
// troque o fetch por: Promise.resolve(MOCK)

3Sua missão — separe nas 3 camadas

Mova cada responsabilidade para o arquivo certo. Use estes stubs como ponto de partida (estão vazios de propósito — a implementação é o desafio):

Model
model/Espetaculo.ts
// model/Espetaculo.ts
// Dados + acesso à API. SEM React, SEM UI.

export type Espetaculo = {
  // TODO: id, nome, categoria, rating
};

export async function fetchEspetaculos(): Promise<Espetaculo[]> {
  // TODO: mova o fetch da View para cá
  // TODO: trate !response.ok lançando um Error
}
ViewModel
viewmodels/useEspetaculosViewModel.ts
// viewmodels/useEspetaculosViewModel.ts
// Estado + ações da tela. SEM JSX.
import { useEffect, useState } from 'react';
import { Espetaculo, fetchEspetaculos } from '../model/Espetaculo';

export function useEspetaculosViewModel() {
  // TODO: estado items, loading, error
  // TODO: load() com try / catch / finally
  // TODO: useEffect(() => { load(); }, [])
  // TODO: return { items, loading, error, reload: load }
}
View
views/EspetaculosScreen.tsx
// views/EspetaculosScreen.tsx
// Só renderiza. Consome o hook do ViewModel.
import { FlatList, Text, View, ActivityIndicator } from 'react-native';
import { useEspetaculosViewModel } from '../viewmodels/useEspetaculosViewModel';

export default function EspetaculosScreen() {
  // TODO: const { items, loading, error, reload } = useEspetaculosViewModel();
  // TODO: trate loading e error
  // TODO: FlatList — SEM fetch e SEM useEffect aqui!
}
⚠️ Regra de ouro: a View não pode ter fetch nem useEffect de dados. Se ela tem, a responsabilidade está na camada errada. A View só conhece o ViewModel; o ViewModel conhece o Model; o Model não conhece ninguém.

4Entrega — Merge Request individual

💡 Dicas

🔗 Liga com a aula 7: o useEspetaculosViewModel que você isolar aqui é exatamente o que vamos testar com TDD na próxima aula — sem montar a UI. Hoje você o torna testável; lá escrevemos os testes antes do código.