🦇 P12: Batcomputer (Injeção de Dependências com Hilt)

Bem-vindo à Fase 7: Arquitetura Avançada & Navegação! 🦇

Neste projeto de alto impacto, você aprenderá a implementar o padrão arquitetural de Injeção de Dependências (DI) utilizando o framework oficial recomendado pelo Google: o Hilt (baseado no poderoso Dagger). Criaremos o aplicativo de inteligência tática Batcomputer, acoplando repositórios de relatórios de crimes em Gotham diretamente em nossos ViewModels sem instanciá-los manualmente, atingindo os padrões profissionais de engenharia de software do mercado.


✅ Pré-requisitos

Antes de começar, certifique-se de já ter estudado:


🎯 Objetivo do Projeto

Estruturar um app robusto e altamente desacoplado contendo:

graph TD
    A[Início / Inicializar Batcomputer] --> B[Hilt Container detecta classes anotadas]
    B --> C[Instancia o CrimeRepositoryImpl de forma Singleton]
    C --> D[Injeta a instância no BatViewModel via construtor]
    D --> E[BatcomputerScreen recolhe crimes do ViewModel]
    E --> F[Renderizar relatórios de perigo no Compose]

📖 Dicionário do Projeto


🛠️ Passo 1: Configurando as Dependências do Hilt (Gradle)

  1. No arquivo build.gradle (Project :...) da raiz, configure o plugin de classpath do Hilt:
    buildscript {
    dependencies {
        classpath 'com.google.dagger:hilt-android-gradle-plugin:2.48'
    }
    }
    
  2. No arquivo build.gradle (Module :app), aplique o plugin do Hilt e adicione as dependências no bloco: ```gradle plugins { … id ‘kotlin-kapt’ id ‘dagger.hilt.android.plugin’ }

dependencies { // Hilt Dependency Injection implementation “com.google.dagger:hilt-android:2.48” kapt “com.google.dagger:hilt-compiler:2.48” implementation “androidx.hilt:hilt-navigation-compose:1.1.0” … }


---

## 🛠️ Passo 2: Criando a Classe Application (`BatcomputerApp.kt`)

Toda injeção com Hilt necessita de um container pai de inicialização global do app.

1.  Crie a classe `BatcomputerApp.kt` no seu pacote principal:
```kotlin
package br.com.curso.batcomputer

import android.app.Application
import dagger.hilt.android.HiltAndroidApp

@HiltAndroidApp
class BatcomputerApp : Application()
  1. Não se esqueça de registrar esta classe no seu AndroidManifest.xml dentro da tag <application>: ```xml

<application android:name=”.BatcomputerApp” … >


---

## 🧠 Passo 3: Declarando a Camada de Dados e Módulo Hilt

Criamos a interface de dados `CrimeRepository` e configuramos o módulo Hilt `AppModule` anotado com `@Module` e `@InstallIn(SingletonComponent::class)`. Esse módulo se responsabiliza por ensinar ao Hilt que, sempre que um ViewModel pedir a interface `CrimeRepository`, o Hilt deve criar e entregar a classe concreta `CrimeRepositoryImpl`.

---

## 🏆 Desafios para você (Upgrade!)

Se você terminou de programar a funcionalidade base e tudo está funcionando, experimente estes upgrades:
*   **Filtro por Nível de Perigo**: adicione um campo de busca ou `Chip`s de filtro para exibir apenas os `crimes` cujo `dangerLevel` corresponda à seleção.
*   **Novo Repositório Injetado**: crie uma interface `SuspectRepository` com sua implementação e um `@Provides` análogo no `AppModule`, exibindo uma segunda lista de suspeitos na tela.

---

# 📖 Gabarito Oficial de Código (Para Conferência)

Use os códigos Kotlin completos abaixo para validar sua implementação.

### 📄 Código de Lógica e Tela Completa (`MainActivity.kt`)
Disponível em: `app/src/main/java/br/com/curso/batcomputer/MainActivity.kt`

```kotlin
package br.com.curso.batcomputer

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.ViewModel
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.components.SingletonComponent
import javax.inject.Inject
import javax.inject.Singleton

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            BatcomputerTheme {
                BatcomputerScreen()
            }
        }
    }
}

@Composable
fun BatcomputerTheme(content: @Composable () -> Unit) {
    MaterialTheme(
        colorScheme = darkColorScheme(
            primary = Color(0xFFFFEB3B), // Amarelo Batman
            background = Color(0xFF09090A),
            surface = Color(0xFF16161A)
        ),
        content = content
    )
}

// 1. MODELO DE DADOS
data class CrimeReport(val id: Int, val title: String, val dangerLevel: String, val location: String)

// 2. INTERFACE DO REPOSITÓRIO
interface CrimeRepository {
    fun getActiveCrimes(): List<CrimeReport>
}

// 3. IMPLEMENTAÇÃO INJETADA PELO HILT
class CrimeRepositoryImpl @Inject constructor() : CrimeRepository {
    override fun getActiveCrimes(): List<CrimeReport> {
        return listOf(
            CrimeReport(1, "Invasão no Asilo Arkham", "MÁXIMO", "Norte de Gotham"),
            CrimeReport(2, "Roubo ao Banco de Gotham", "ALTO", "Centro Comercial"),
            CrimeReport(3, "Sinal do Charada no Metrô", "MÉDIO", "Subsolo Leste")
        )
    }
}

// 4. MÓDULO DE PROVISÃO DO HILT
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
    @Provides
    @Singleton
    fun provideCrimeRepository(): CrimeRepository {
        return CrimeRepositoryImpl()
    }
}

// 5. VIEWMODEL INJETADO
class BatViewModel(private val repository: CrimeRepository) : ViewModel() {
    val crimes = repository.getActiveCrimes()
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BatcomputerScreen() {
    val mockRepository = CrimeRepositoryImpl()
    val viewModel = BatViewModel(mockRepository)

    Scaffold(
        containerColor = MaterialTheme.colorScheme.background,
        topBar = {
            CenterAlignedTopAppBar(
                title = { Text("BATCOMPUTER v2.0", fontWeight = FontWeight.ExtraBold, letterSpacing = 3.sp) },
                colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
                    containerColor = Color(0xFF16161A),
                    titleContentColor = Color(0xFFFFEB3B)
                )
            )
        }
    ) { padding ->
        LazyColumn(modifier = Modifier.padding(padding).fillMaxSize().padding(16.dp)) {
            items(viewModel.crimes) { crime ->
                Card(
                    modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp),
                    colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
                    shape = RoundedCornerShape(12.dp)
                ) {
                    Column(modifier = Modifier.padding(16.dp)) {
                        Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
                            Text(crime.title, fontWeight = FontWeight.Bold, color = Color.White, fontSize = 16.sp)
                            Text(
                                text = crime.dangerLevel,
                                color = if (crime.dangerLevel == "MÁXIMO") Color.Red else Color(0xFFFFEB3B),
                                fontWeight = FontWeight.ExtraBold,
                                fontSize = 12.sp
                            )
                        }
                        Spacer(modifier = Modifier.height(8.dp))
                        Text("Localização: ${crime.location}", color = Color.Gray, fontSize = 14.sp)
                    }
                }
            }
        }
    }
}