Dividir los Widgets en Métodos es un patrón anti rendimiento.
--
Nota: Esta es una traducción del articulo original Splitting widgets to methods is a performance antipattern por iirokrankka.com y es la continuación de Poniendo los métodos build a dieta: trucos y consejos para un código de UI mas limpio en Flutter traducido por mi, también
Han pasado casi 6 meses desde que escribí el articulo código de UI mas limpio en Flutter (las traducciones tienen solo 2 meses de separación).
En ese articulo doy varios tips en como organizar tu código de UI en Flutter para una mejor lectura. Y aun es un articulo bastante popular. Como sea, ahí había un tip que incitaba a hacer algo que resulto no ser algo bueno.
Para escapar del infierno de llaves estilo Lisp, incito a dividir los métodos Build en muchos métodos mas pequeños. El siguiente código es un ejemplo sin sentido pero pretendamos que hace algo bastante útil.
Por ejemplo, si tenemos un widget que luce así:
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
int _counter = 0;
@override
Widget build(BuildContext context) {
return Row(
children: [
Text('Counter: $_counter'),
Container(
child: Column(
children: [
Text('Hello'),
Row(
children: [
Text('there'),
Text('world!'),
],
),
],
),
),
],
);
}
}
Viendo el código, te puedes dar cuenta que los niveles de anidado se ponen algo locos.
Y los hacen. seria increíble reducir el nivel de indentacion un poco. Ya que los widgets pueden ser algo repetitivos, la primera solución que nos llega a la mente es el separar la parte anidado en un método aparte.
Nuestra primera intuición seria lago así:
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
int _counter = 0;
Widget _buildNonsenseWidget() {
return Container(
child: Column(
children: [
Text('Hello'),
Row(
children: [
Text('there'),
Text('world!'),
],
),
],
),
);
}
@override
Widget build(BuildContext context) {
return Row(
children: [
Text('Counter: $_counter'),
/// The deeply nesting widget is now refactored into a
/// separate method and we have a cleaner build method. Yay!
_buildNonsenseWidget(),
],
);
}
}
Problema resuelto ¿Cierto? Es momento de decir “lo hicimos” y nos vamos a casa.
Bueno, no realmente.
El problema de separar los widgets en métodos
A primera vista, dividir los largos métodos build en funciones mas pequeñas tiene todo el sentido, y puedes estar seguro que es un patrón que se utiliza muy seguido. Y se usa bastante en el código base de Reflectry, y créeme, tenemos mucho código UI ahí.
Ahora, 6 meses después (2 para las traducciones) de mi articulo inicial, estoy aquí para decirte que no deberías hacerlo. Si tomaste este mal habito después de leer mi articulo, deberás estar enojado conmigo en este momento, eres bienvenido a estarlo. Eso es lo que hacen los amigos. Lo siento mucho.
Este problema fue traído a mi atención gracias a Wm Leler en la sección de comentarios del ya mencionado articulo.
Traducción del comentario: “Si, debemos tener mas información sobre las buenas practicas para los diseños en Flutter. Debe estar documentado en algún lado, pero lo único que pude encontrar fue “Dividir métodos build en múltiples métodos es una mala practica (si un método build es muy grande, este deberá ser separado en diferentes widgets, no métodos, para un mejor desempeño)”
EL problema es que los métodos build son llamados (y el árbol de widgets reconstruido) solo si su estado asociado cambia. Entre mas finamente desgranados sean los métodos build, Flutter tendrá mayor control sobre que se reconstruye”
Para aquellos que no lo saben, Wm es un desarrollador que aboga por Flutter.
Algunos de ustedes al leer el comentario de Wm tendrán un momento “¡a-ja!”. Como sea, algunos de nosotros, me incluyo, no lo tendremos. Y eso está bien, aprenderemos que es lo que está pasando aquí.
¿Cual es el problema, enserio?
Cada vez que el valor de _counter cambia, el framework llama al método build. Esto hace que el widget se reconstruya a si mismo. El problema es que _buildNonsenseWidget() es llamado cada vez que el valor de _counter cambia, lo que ocasiona que el árbol de widgets se reconstruya de nuevo y de nuevo.
Reconstruyendo para nada
En este caso no hay razón para reconstruir ese árbol de widgets en particular.
El árbol de widgets retornado por _buildNonsenseWidget() es stateless por naturaleza, solo se necesita construir una vez. Tristemente, como ese árbol es construido por el método _buildNonsenseWidget() el framework de Flutter lo reconstruye cada vez que el padre se reconstruye.
Esencialmente, estamos gastando preciosos ciclos de CPU en reconstruir algo que no necesita ser reconstruido. Esto pasa porque desde la perspectiva del framework, no hay diferencia entre un método build enorme y un método build dividido en pequeños métodos. Esto tiene un gran impacto en aplicaciones mas complejas.
Separando grandes métodos build - revisión
La solución es realmente simple, aunque resulta en un par mas de lineas de código. En lugar de dividir los método build en métodos mas pequeños los separamos en widgets, StatelessWidgets.
Cuando refactorizamos el ejemplo anterior, quedaremos con algo así:
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
int _counter = 0;
@override
Widget build(BuildContext context) {
return Row(
children: [
Text('Counter: $_counter'),
/// The deeply nesting widget is now refactored into a
/// stateless widget. No more needless rebuilding!
const _NonsenseWidget(),
],
);
}
}
class _NonsenseWidget extends StatelessWidget {
const _NonsenseWidget();
@override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
Text('Hello'),
Row(
children: [
Text('there'),
Text('world!'),
],
),
],
),
);
}
}
Aunque es un poco mas de código, es mucho mejor código.
Ahora el _NonsenseWidget es construido una sola vez, todas las reconstrucciones innecesarias desaparecen. El widget padre se puede reconstruir a si mismo múltiples veces, pero a _NonsenseWidget no le importa, solo se construye una vez.
Dividir los widgets en widgets mas pequeños - ejemplos mas complejos
Estarás pensado que el ejemplo de arriba fue bastante simple y no representa la complejidad de una app real. Y estas en lo cierto.
Recientemente actualice el código abierto de inKino app para seguir el consejo de este articulo. Por ejemplo, creo que esto es un buen ejemplo de como dividir widgets en widgets mas pequeños StatelessWidgets en una aplicación mas grande.
Conclusion
En lugar de de dividir los métodos build en métodos mas pequeños divídelos en StatelessWidgets. De esta forma no reconstruirás tus arboles de widgets estáticos múltiples veces para nada mas que gastar ciclos de CPU. Cuando se trata de optimizar el rendimiento de aplicaciones Flutter, este es de los consejos mas importantes.
Si realmente prefieres construir tus arboles de widgets con métodos, quizá te interese mirar el paquete llamado functional_widget by Remi Rousselet. Este resuelve el problema que viene con construir arboles de widgets con métodos usando generación de código.
Le agradezco mucho al autor original por permitirme traducir su articulo y espero que le sirva esta información a la comunidad de habla española.
Link al articulo original, perfil de twitter el autor y su pagina.