Sistema de almoxarifado no Android
Para criar um sistema de almoxarifado no Android, a estrutura principal deve focar em gestão de estoque (entradas e saídas), cadastro de produtos e rastreabilidade.
Arquitetura Recomendada
Para um aplicativo moderno e escalável em Kotlin:
- Interface (UI): Jetpack Compose (design moderno e reativo).
- Banco de Dados Local: Room Database (armazenamento de dados local e offline).
- Scanner: CameraX + ML Kit Barcode Scanning (para leitura de códigos de barras/QR Code de produtos).
- Arquitetura: MVVM (Model-View-ViewModel).
Módulos Principais do Sistema
- Cadastro de Produtos: Nome, código de barras/SKU, categoria, quantidade mínima, localização (prateleira/corredor).
- Movimentação de Estoque:
- Entrada: Adição de novos itens ao estoque.
- Saída: Baixa de itens para uso interno ou entrega.
- Scanner de Código de Barras: Para rápida conferência e busca de produtos.
- Relatórios e Alertas: Notificação ou destaque visual quando um produto atinge o estoque mínimo.
Estrutura de Banco de Dados (Room)
Kotlin
@Entity(tableName = "produtos")
data class Produto(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
val codigoBarras: String,
val nome: String,
val quantidade: Int,
val quantidadeMinima: Int,
val localizacao: String
)
@Entity(tableName = "movimentacoes")
data class Movimentacao(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
val produtoId: Int,
val tipo: String, // "ENTRADA" ou "SAIDA"
val quantidade: Int,
val dataHora: Long = System.currentTimeMillis()
)
Passos para Desenvolvimento
- Configuração Inicial: Crie um projeto em Kotlin no Android Studio usando o template Empty Compose Activity.
- Dependências: Adicione no
build.gradle.kts:androidx.room:room-runtimeeandroidx.room:room-ktxcom.google.mlkit:barcode-scanningandroidx.camera:camera-vieweandroidx.camera:camera-lifecycle
- Persistência Local: Crie a estrutura de DAOs (Data Access Objects) para inserir, atualizar e listar os produtos no Room.
- Interface: Desenvolva telas simples:
- Lista de estoque (com busca por nome e filtro por estoque baixo).
- Formuário de cadastro/edição de produtos.
- Tela de leitura de código de barras para dar baixa/entrada rápida.Aqui está uma implementação completa e estruturada utilizando Room, Kotlin Coroutines / Flow e a arquitetura recomendada MVVM para o sistema de almoxarifado.
1. Entidades do Banco de Dados (
Entity)Primeiro, definimos a tabela de produtos:Kotlinimport androidx.room.Entity import androidx.room.PrimaryKey @Entity(tableName = "produtos") data class Produto( @PrimaryKey(autoGenerate = true) val id: Int = 0, val codigoBarras: String, val nome: String, val quantidade: Int, val quantidadeMinima: Int, val localizacao: String )2. Data Access Object (
DAO)O DAO contém as operações de leitura, escrita e atualização no banco de dados local. UsamosFlowpara que a interface atualize automaticamente sempre que os dados mudarem.Kotlinimport androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query import androidx.room.Update import kotlinx.coroutines.flow.Flow @Dao interface ProdutoDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun salvar(produto: Produto) @Update suspend fun atualizar(produto: Produto) @Query("SELECT * FROM produtos ORDER BY nome ASC") fun listarTodos(): Flow<List<Produto>> @Query("SELECT * FROM produtos WHERE codigoBarras = :codigo LIMIT 1") suspend fun buscarPorCodigoBarras(codigo: String): Produto? @Query("SELECT * FROM produtos WHERE quantidade <= quantidadeMinima") fun listarEstoqueBaixo(): Flow<List<Produto>> @Query("UPDATE produtos SET quantidade = quantidade + :qtd WHERE id = :id") suspend fun registrarEntrada(id: Int, qtd: Int) @Query("UPDATE produtos SET quantidade = quantidade - :qtd WHERE id = :id AND quantidade >= :qtd") suspend fun registrarSaida(id: Int, qtd: Int): Int // Retorna quantidade de linhas afetadas }3. Repositório (
Repository)O repositório atua como a única fonte da verdade, abstraindo o DAO do restante da aplicação.Kotlinimport kotlinx.coroutines.flow.Flow class AlmoxarifadoRepository(private val produtoDao: ProdutoDao) { val todosOsProdutos: Flow<List<Produto>> = produtoDao.listarTodos() val produtosEstoqueBaixo: Flow<List<Produto>> = produtoDao.listarEstoqueBaixo() suspend fun salvarProduto(produto: Produto) { produtoDao.salvar(produto) } suspend fun buscarPorCodigo(codigo: String): Produto? { return produtoDao.buscarPorCodigoBarras(codigo) } suspend fun darEntrada(produtoId: Int, quantidade: Int) { produtoDao.registrarEntrada(produtoId, quantidade) } suspend fun darSaida(produtoId: Int, quantidade: Int): Boolean { val linhasAfetadas = produtoDao.registrarSaida(produtoId, quantidade) return linhasAfetadas > 0 // Retorna true se deu baixa com sucesso } }4. ViewModel (
ViewModel)O ViewModel gerencia os estados da interface e executa as regras de negócio em segundo plano usando Coroutines.Kotlinimport androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch class AlmoxarifadoViewModel( private val repository: AlmoxarifadoRepository ) : ViewModel() { // Transforma o Flow em StateFlow para fácil consumo no Jetpack Compose val listaProdutos: StateFlow<List<Produto>> = repository.todosOsProdutos .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), initialValue = emptyList() ) val alertaEstoqueBaixo: StateFlow<List<Produto>> = repository.produtosEstoqueBaixo .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), initialValue = emptyList() ) fun CadastrarOuAtualizar(produto: Produto) { viewModelScope.launch { repository.salvarProduto(produto) } } fun realizarEntrada(produtoId: Int, quantidade: Int) { viewModelScope.launch { if (quantidade > 0) { repository.darEntrada(produtoId, quantidade) } } } fun realizarSaida(produtoId: Int, quantidade: Int, onSuccess: () -> Unit, onError: (String) -> Unit) { viewModelScope.launch { if (quantidade <= 0) { onError("A quantidade deve ser maior que zero") return@launch } val sucesso = repository.darSaida(produtoId, quantidade) if (sucesso) { onSuccess() } else { onError("Quantidade indisponível no estoque") } } } }