O que e Property Delegate em Kotlin?

Property Delegate (delegacao de propriedade) e um mecanismo do Kotlin que permite delegar a logica de leitura (get) e escrita (set) de uma propriedade para outro objeto. Em vez de implementar a logica diretamente na propriedade, voce usa a palavra-chave by para apontar para um objeto delegado que controla o comportamento.

Os delegates mais conhecidos da biblioteca padrao sao lazy, observable e vetoable, mas voce pode criar os seus proprios para qualquer necessidade.

Sintaxe basica

class Exemplo {
    // Delegando para lazy: inicializa na primeira leitura
    val configuracao: String by lazy {
        println("Calculando...")
        "Valor configurado"
    }
}

fun main() {
    val obj = Exemplo()
    println(obj.configuracao) // Imprime "Calculando..." e "Valor configurado"
    println(obj.configuracao) // Imprime apenas "Valor configurado" (ja calculado)
}

A palavra-chave by indica que a propriedade esta delegando suas operacoes de get (e set, se for var) para o objeto a direita.

Como funciona internamente

Quando voce escreve val x by delegate, o compilador gera codigo equivalente a:

// Voce escreve:
class Exemplo {
    val x: String by MeuDelegate()
}

// O compilador gera algo como:
class Exemplo {
    private val x_delegate = MeuDelegate()

    val x: String
        get() = x_delegate.getValue(this, ::x)
}

Para propriedades var, o compilador tambem gera o setter:

// var y by delegate gera:
var y: String
    get() = y_delegate.getValue(this, ::y)
    set(value) { y_delegate.setValue(this, ::y, value) }

Delegates da biblioteca padrao

lazy

Inicializa o valor na primeira leitura e cacheia para acessos subsequentes:

val dadosPesados: List<String> by lazy {
    println("Carregando dados pesados...")
    carregarDoBanco()
}

Modos de thread safety do lazy:

// Thread-safe (padrao): sincronizado
val a by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { calcular() }

// Sem sincronizacao: mais rapido, mas nao thread-safe
val b by lazy(LazyThreadSafetyMode.NONE) { calcular() }

// Publication: permite calculo duplicado, mas garante que todos veem o mesmo valor
val c by lazy(LazyThreadSafetyMode.PUBLICATION) { calcular() }

observable

Notifica quando o valor muda:

import kotlin.properties.Delegates

var nome: String by Delegates.observable("Inicial") { propriedade, valorAntigo, valorNovo ->
    println("${propriedade.name} mudou de '$valorAntigo' para '$valorNovo'")
}

fun main() {
    nome = "Ana"    // Imprime: nome mudou de 'Inicial' para 'Ana'
    nome = "Bruno"  // Imprime: nome mudou de 'Ana' para 'Bruno'
}

vetoable

Permite rejeitar mudancas de valor:

var idade: Int by Delegates.vetoable(0) { _, _, novoValor ->
    novoValor >= 0 // So aceita valores nao-negativos
}

fun main() {
    idade = 25   // Aceito
    println(idade) // 25
    idade = -5   // Rejeitado
    println(idade) // 25 (nao mudou)
}

notNull

Similar ao lateinit, mas funciona com tipos primitivos delegados:

var contador: Int by Delegates.notNull<Int>()

fun main() {
    // println(contador) // IllegalStateException!
    contador = 42
    println(contador)  // 42
}

Criando um delegate customizado

Para criar um delegate, implemente os operadores getValue e opcionalmente setValue:

import kotlin.reflect.KProperty

class LoggingDelegate<T>(private var valor: T) {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
        println("Lendo ${property.name}: $valor")
        return valor
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, novoValor: T) {
        println("Escrevendo ${property.name}: $valor -> $novoValor")
        valor = novoValor
    }
}

class Usuario {
    var nome: String by LoggingDelegate("Sem nome")
    var idade: Int by LoggingDelegate(0)
}

fun main() {
    val u = Usuario()
    u.nome = "Ana"     // Escrevendo nome: Sem nome -> Ana
    println(u.nome)    // Lendo nome: Ana
    u.idade = 30       // Escrevendo idade: 0 -> 30
}

Exemplo pratico: SharedPreferences delegate

Um caso de uso real no Android e delegar propriedades para SharedPreferences:

class PreferenceDelegate(
    private val prefs: SharedPreferences,
    private val chave: String,
    private val padrao: String
) {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return prefs.getString(chave, padrao) ?: padrao
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, valor: String) {
        prefs.edit().putString(chave, valor).apply()
    }
}

fun SharedPreferences.string(chave: String, padrao: String = "") =
    PreferenceDelegate(this, chave, padrao)

// Uso
class Configuracoes(prefs: SharedPreferences) {
    var tema: String by prefs.string("tema", "claro")
    var idioma: String by prefs.string("idioma", "pt-BR")
    var nomeUsuario: String by prefs.string("nome_usuario", "")
}

fun main() {
    val config = Configuracoes(obterPrefs())
    config.tema = "escuro"           // Salva automaticamente no SharedPreferences
    println(config.tema)             // Le automaticamente do SharedPreferences
}

Delegate para Map

Kotlin tem suporte embutido para delegar propriedades a um Map:

class Usuario(mapa: Map<String, Any?>) {
    val nome: String by mapa
    val idade: Int by mapa
    val email: String by mapa
}

fun main() {
    val dados = mapOf(
        "nome" to "Ana",
        "idade" to 30,
        "email" to "ana@email.com"
    )
    val usuario = Usuario(dados)
    println(usuario.nome)  // Ana
    println(usuario.idade) // 30
}

Para propriedades mutaveis, use MutableMap:

class Configuracao(mapa: MutableMap<String, Any?>) {
    var tema: String by mapa
    var fontSize: Int by mapa
}

Interfaces ReadOnlyProperty e ReadWriteProperty

Para maior clareza, seus delegates podem implementar interfaces oficiais:

import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

class ValidatedString(private var valor: String) : ReadWriteProperty<Any?, String> {
    override fun getValue(thisRef: Any?, property: KProperty<*>): String = valor

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        require(value.isNotBlank()) { "${property.name} nao pode ser vazio" }
        valor = value
    }
}

class Formulario {
    var nome: String by ValidatedString("")
    var email: String by ValidatedString("")
}

Quando usar Property Delegates

  • Inicializacao preguicosa: lazy para valores calculados sob demanda.
  • Observacao de mudancas: observable e vetoable para reagir a alteracoes de estado.
  • Persistencia transparente: delegar para SharedPreferences, banco de dados ou cache.
  • Validacao automatica: verificar restricoes a cada escrita sem repetir codigo.
  • Logging e auditoria: registrar acessos e modificacoes de propriedades.

Erros comuns

  1. Nao entender que lazy e thread-safe por padrao: o modo SYNCHRONIZED tem overhead. Use NONE quando thread safety nao e necessaria.

  2. Criar delegates com efeitos colaterais pesados no getValue: o getter e chamado toda vez que a propriedade e lida. Operacoes caras devem ser cacheadas.

  3. Esquecer de implementar setValue para var: se o delegate nao tem setValue, ele so funciona com val.

  4. Confundir delegacao de propriedade com delegacao de classe: class A by b delega a interface de uma classe; val x by delegate delega uma propriedade. Sao mecanismos diferentes.

  5. Usar lazy em propriedades que mudam: lazy e para val. Se voce precisa de inicializacao tardia com var, use lateinit ou Delegates.notNull.

Termos relacionados

  • lazy: delegate da biblioteca padrao para inicializacao preguicosa.
  • lateinit: alternativa ao delegate notNull para inicializacao tardia.
  • Delegation: conceito mais amplo que inclui delegacao de classes e interfaces.
  • observable/vetoable: delegates para monitorar e controlar mudancas de valor.
  • val/var: delegates para val precisam apenas de getValue; delegates para var precisam tambem de setValue.
  • KProperty: objeto de reflexao que representa a propriedade sendo delegada.

Property Delegates sao um dos recursos mais elegantes do Kotlin, permitindo separar a logica transversal (logging, persistencia, validacao) da logica de negocio de forma limpa e reutilizavel. Dominar esse conceito abre portas para APIs expressivas e codigo sem boilerplate.