Moviendo rápido con valores inmutables en Dart
built_value de Dart proporciona valores inmutables potentes y convenientes para Dart. Los valores pueden ser inmutables, pero el paquete no ha sido…
El paquete built_value de Dart proporciona valores inmutables potentes y convenientes para Dart. Los valores pueden ser inmutables, ¡pero el paquete no se ha quedado quieto! En los seis meses desde que escribí por última vez sobre built_value ha habido diez mejoras importantes en el paquete.
También detecté algunos usos interesantes de built_value en la comunidad de Dart:
-
El paquete built_redux es un paquete inspirado en redux que usa built_value. Incluye bindings y ejemplos para flutter, angular2 y react-dart.
-
El paquete built_highcharts es un wrapper de highcharts que usa built_value. La demo es realmente bastante bonita.
-
Y, aquí hay otra forma de usar built_value con Flutter.
En fin, sin más dilación, aquí están todas las grandes cosas nuevas en built_value.
1. Clases genéricas
#Los modelos de objetos sofisticados usan genéricos, así que por supuesto built_value necesita genéricos. Ahora puedes declarar tu tipo usando genéricos y la clase builder generada también los soportará:
abstract class GenericValue<T>
implements Built<GenericValue<T>, GenericValueBuilder<T>> {
T get value;
...
}
var value = new GenericValue<String>((b) => b..value = 'string');
Los tipos de valor que usan genéricos son serializables, como de costumbre.
2. Plugins de serialización
#Una nueva API de SerializerPlugin te permite hacer modificaciones transversales a la serialización, ejecutando código arbitrario antes y después de que cada objeto sea serializado o deserializado.
Podrías, por ejemplo, usar esto para mapear entre dos versiones incompatibles de un protocolo. Pero un mejor ejemplo es la siguiente mejora…
3. Plugin de serialización “Standard JSON”
#Por defecto, built_value serializa hacia y desde su propio formato JSON basado en listas, por rendimiento y flexibilidad.
Pero mucha gente necesita interoperar con datos y APIs JSON existentes. ¿Qué deberían hacer?
Ahora puedes instalar StandardJsonPlugin y cambiar a un formato JSON estándar basado en mapas.
final standardSerializers =
(serializers.toBuilder()
..addPlugin(new StandardJsonPlugin())).build();
Mira el ejemplo (hacia el final).
4. Memoización de getters
#Las clases inmutables son geniales — pero ¿qué deberías hacer con los campos derivados? Si un campo derivado es costoso de calcular, entonces las clases inmutables dan dos malas opciones: calcularlos por adelantado y siempre pagar una penalización posiblemente innecesaria, o recalcularlos bajo demanda y potencialmente pagar ese mismo gran costo muchas veces.
Entremos los getters @memoized. Añade esta anotación a un getter de built_value y se calculará de forma perezosa, pero como mucho una vez; luego se almacena en un campo oculto en la instancia.
Desde la versión 1.1.2 el propio built_value es un gran ejemplo de getters @memoized. Se usa un built_value para envolver un
Element del analyzer, y el cómputo se hace de forma perezosa en los getters:
abstract class ValueSourceClass
implements Built<ValueSourceClass, ValueSourceClassBuilder> {
ClassElement get element;
@memoized
BuiltList<String> get genericParameters =>
new BuiltList<String>(element.typeParameters
.map((element) => element.computeNode().toString()));
@memoized
BuiltList<ValueSourceField> get fields =>
ValueSourceField.fromClassElements(element, builderElement);
// And many more @memoized getters.
}
5. Generación de código más rápida
#…y como resultado de mover a getters memoizados, built_value es ahora significativamente más rápido generando código.
6. Factories más simples
#Antes de la versión 0.5.5 cada clase built_value tenía que tener un método factory siguiendo una plantilla estándar. Este requisito se ha relajado. Aquí hay algunas buenas formas de usar esta mayor libertad.
Para clases con uno o dos campos, simplemente toma exactamente esos campos en el constructor:
abstract class Value implements Built<Value, ValueBuilder> {
ClassElement get element;
factory Value(ClassElement element) =>
new _$Value._(element: element);
}
void main() {
var element = getElement();
var value = new Value(element);
}
O, donde quieres establecer valores predeterminados para los campos, ahora puedes hacerlo en la factory, en lugar de tener que escribir tu propia clase builder:
abstract class Value implements Built<Value, ValueBuilder> {
int get x;
int get y;
bool get awesome;
factory Value([updates(ValueBuilder b)]) =>
new _$Value((b) => b
..awesome = True
..update(updates));
}
void main() {
var value = new Value((b) => b
..x = 10
..y = 20);
}
Finalmente, si te gustan los argumentos nombrados, puedes exponer el constructor basado en argumentos nombrados en lugar del constructor basado en builder:
abstract class Value implements Built<Value, ValueBuilder> {
int get x;
int get y;
factory Value({int x, int y}) = _$Value._;
void main() {
var value = new Value(x: 10, y: 20);
}
7. Salida toString más bonita y personalizable
#Antes de la versión 1.0.0 la salida de toString de built_value era bastante plana. Ahora, por defecto está indentada, así que es fácil de leer:
CompoundValue {
simpleValue=SimpleValue {
anInt=1,
},
}
Pero también es personalizable, mediante una variable global de nivel superior newBuiltValueToStringHelper. Si preferías el formato antiguo:
newBuiltValueToStringHelper =
(className) => new FlatBuiltValueToStringHelper(className);
Entonces:
CompoundValue {simpleValue=SimpleValue {anInt=1}}
Y si quieres escribir la tuya propia — por supuesto que puedes. La clase BuiltValueToStringHelper es muy fácil de implementar:
abstract class BuiltValueToStringHelper {
void add(String field, Object value);
String toString();
}
8. DateTime y JsonObject serializables
#La clase DateTime del SDK ahora es serializable — siempre que uses UTC en lugar de zona horaria local. (¡Serializar con zona horaria local es una receta para el desastre!)
Y, para ayudar más con la interoperabilidad con datos y APIs JSON existentes, ahora hay una clase JsonObject
que envuelve valores json, listas y mapas. Los campos de tipo JsonObject se serializan directamente hacia y desde JSON crudo.
9. Matcher personalizado para tests
#Los métodos operator== generados en built_value ya son muy útiles para tests, pero hay algo que no te dan: mensajes de error fácilmente legibles. En su lugar, cuando una comprobación de ‘equals’ en un test falla, obtendrás el
toString completo de los valores esperado y actual.
¡Ya no más! Ahora puedes usar built_value_test, un nuevo paquete de pub. Si lo usas en un test
ahora verás errores como este:
was <3> instead of <5> at location ['simpleValue']['anInt']
Si esto se parece mucho al mensaje que obtienes cuando una comparación de mapas falla en un test, es porque es lo mismo: está implementado convirtiendo los valores a mapas y luego comparándolos.
10. Código compatible con implicit-dynamic e implicit-cast; mejores mensajes de error
#El código generado por built_value se ha limpiado significativamente para hacerlo pasar dos lints muy estrictos: ya no usa
dynamic implícitamente en ningún sitio, y ya no depende de casts implícitos.
Estas dos mejoras pueden (o no) llevar a mejor código por parte del compilador.
Y se añadieron mejores mensajes de error para cuando declaras un campo de serializador con el tipo equivocado.
¡Eso fue todo! Gracias por leer. Si tienes alguna idea o petición de funcionalidad para built_value, dirígete al issue tracker.
Más de Dart
Historia del interop con JS en Dart
El soporte para Wasm acaba de llegar al beta actual de Flutter, gracias a un emocionante hito de interop con JavaScript alcanzado en Dart 3.3. Para…
Dart DevTools: Analizando el rendimiento de la aplicación con el CPU Profiler
Ya seas un desarrollador de backend escribiendo una herramienta de línea de comandos con Dart, o un ingeniero de UX construyendo aplicaciones con Flutter, el rendimiento del programa es crítico para el éxito de tu proyecto. Las herramientas de línea de comandos deberían minimizar la latencia y las apps deberían ser responsivas y ágiles sin fotogramas perdidos. Como desarrolladores, hacemos todo lo posible para escribir código performante pero a veces no está claro por qué nuestro código no funciona tan bien como esperaríamos.