Ótimas extensões pra Objetos em Dart & Flutter
Em Dart, existem várias características interessantes, e vou explicar extensões e genéricos para que as sugestões no final deste artigo sejam melhor compreendidas. Se você já está familiarizado com eles, pode pular para a última parte.
Extensões:
Em Dart, há uma característica maravilhosa chamada extensão. Essa funcionalidade permite adicionar métodos e parâmetros a instâncias de uma classe sem a necessidade de estender, implementar ou misturar usando with.
Aqui está um exemplo de uma extensão em int para adicionar um getter que responde se o número inteiro é divisível por 3.
extension EvenInteger on int {
bool get isDivisibleBy3 => this % 3 == 0;
}
Com essa extensão, você pode fazer algo como o seguinte:
void function(int parameter) {
// Algum código.
if (parameter.isDivisibleBy3) {
print('$parameter é divisível por 3');
}
// Algum código.
}
A propósito, os nomes das extensões não são necessários, mas se você quiser adicioná-los a um arquivo separado e importá-los, eles devem ter um nome e não devem começar com um _ (sublinhado), pois isso os tornará visíveis apenas para sua biblioteca.
Genericos:
Em Dart, também existe outra característica chamada genéricos, representada pelos sinais <>
, e eles existem para criar um tipo mais específico.
A maioria das pessoas que usa Flutter já os viu com Columns
e Rows
tendo uma propriedade children
que recebe um List<Widget>
, mas você também pode criar uma lista de inteiros, por exemplo:
List<int> lista1 = [1, 2, 3];
Eles também podem ser usados para representar um tipo mais abstrato, então podemos criar uma lista de números como:
List<num> lista2 = [1, 0.2, 3];
Qualquer número pode ser adicionado a esta lista2
, mas apenas inteiros na primeira (lista1
).
Extensões e usos:
Agora que todos já estão familiarizados com ambos os conceitos que vamos usar, vamos direto ao ponto!
Isso não é nulo?
Você já teve a experiência de ter um retorno profundamente aninhado de uma variável/método, que você precisava salvar em uma variável para testar se o retorno não é nulo, para passá-lo para uma nova chamada de função/método. E se esse resultado fosse nulo, não fazer nada?
final result1 = obj1.propety.method(variable);
if (result != null) return function(result1);
return null;
Principalmente no Flutter, vejo isso de vez em quando, e como são todos widgets, tenho que usar o operador ternário ou de alguma forma criar uma variável em algum lugar para fazer este teste.
Solução
Você pode criar uma extensão em todos os Object
s que resolva seu problema!
extension ObjectsExtensions<T extends Object> on T {
R apply<R>(R Function(T self) f) => f(this);
}
Deixe-me explicar. Quando você cria uma classe/mixin/extensão, você pode adicionar os mesmos <>
para se referir a um tipo desconhecido com um nome para esse tipo, neste caso, T
. Isso permite que você crie uma variável com esse tipo dentro de sua classe, mesmo que ainda não conheça o tipo exato.
Você pode ser mais específico com esse tipo se disser que ele deve estender algum tipo específico, neste caso, eu disse que T
deve estender Object
.
Isso implica que qualquer objeto não nulo em seu programa que possa ver esta extensão, possui este método.
Por que eu criei isso com genéricos e também, por que eu não simplesmente usei Object
nos tipos de retorno e parâmetro?
Bem, isso tem a ver com o método que estamos criando. Isso faz com que quando chamamos o método apply
, a função interna nos dê um objeto do tipo T
como parâmetro, e não um objeto do tipo Object
. Isso mantém nosso código coerente com o tipo e temos certeza qual é o tipo da variável dada mesmo dentro de nossa função interna.
Com isso, você pode facilmente resolver o problema acima com apenas esta linha:
return obj1.propety.method(variable)?.apply((self) => function(self));
Mesmo problema, teste diferente
Você já teve o mesmo problema descrito acima, mas com outro teste? Como se um número é par, ou qualquer outro teste?
final result1 = obj1.propety.method(variable);
if (result?.isEven ?? false) return function(result1);
if (result?.isOdd ?? false) return function(result1 * 2);
return null;
Solução
extension ObjectsExtensions<T extends Object> on T {
T? when(bool Function(T self) predicate, {T? Function(T self)? otherwise}) {
if (predicate(this)) return this;
return otherwise?.call(this);
}
}
O que isso nos permite fazer é o seguinte:
return obj1.propety.method(variable)?.when(
(self) => self.isEven,
otherwise: (self) => self * 2,
)?.apply((self) => function(self));
Conclusão e créditos
Essas extensões me ajudaram a programar muito, várias vezes e realmente espero que também possam ajudá-lo!
Também existem mais duas extensões, não tão úteis, que eu criei com base nesta resposta https://stackoverflow.com/a/69114947/9576870, que realmente resolvem os testes de tipo usando "métodos" em vez de operadores:
extension NullableObjectsExtensions<T> on T {
bool isSubtypeOf<S>() => <T>[] is List<S>;
bool isSupertypeOf<S>() => <S>[] is List<T>;
}
Creditos
Apply: https://github.com/dart-lang/language/issues/361#issuecomment-1347411411
When: https://github.com/dart-lang/language/issues/2440