InheritedWidget en Flutter

La traducción del artículo ha sido preparada para estudiantes del curso Flutter Mobile Developer .










Las raíces de los árboles de widgets de Flutter pueden ser muy profundas ...







Muy profundas.



La naturaleza de los componentes de los widgets Flutter permite diseños de aplicaciones muy elegantes, modulares y flexibles. Sin embargo, esto también puede resultar en una gran cantidad de código repetitivo para pasar contexto. Vea lo que sucede cuando queremos pasar el accountId y el scopeId de la página al widget dos niveles a continuación:



class MyPage extends StatelessWidget {
  final int accountId;
  final int scopeId;
  
  MyPage(this.accountId, this.scopeId);
  
  Widget build(BuildContext context) {
    return new MyWidget(accountId, scopeId);
  }
}

class MyWidget extends StatelessWidget {
  final int accountId;
  final int scopeId;
  
  MyWidget(this.accountId, this.scopeId);
  
  Widget build(BuildContext context) {
    // -   
    new MyOtherWidget(accountId, scopeId);
    ...
  }
}

class MyOtherWidget extends StatelessWidget {
  final int accountId;
  final int scopeId;
  
  MyOtherWidget(this.accountId, this.scopeId);
  
  Widget build(BuildContext context) {
    //  
    ...


Si no se marca, este patrón puede deslizarse muy fácilmente por todo el código base. Hemos parametrizado personalmente más de 30 widgets de esta manera. Casi la mitad del tiempo de trabajo, el widget recibió parámetros solo para pasarlos más allá, como en MyWidgetel ejemplo anterior.



El estado de MyWidget es independiente de los parámetros y, sin embargo, se reconstruye cada vez que cambian los parámetros.


Por supuesto, tiene que haber una mejor manera ...



Presentando InheritedWidget .



En pocas palabras, es un tipo especial de widget que define el contexto en la raíz de un subárbol. Puede proporcionar este contexto de manera eficiente a cada widget en ese subárbol. El patrón de acceso debería resultar familiar para un desarrollador de Flutter:



final myInheritedWidget = MyInheritedWidget.of(context);


Este contexto es solo una clase de Dart. Por lo tanto, puede contener lo que quieras guardar allí. Muchos de los contextos Flutter de uso común, como Styleo MediaQuery, no son más que InheritedWidgets que viven en el nivel MaterialApp.



Si complementamos el ejemplo anterior usando InheritedWidget, esto es lo que obtenemos:



class MyInheritedWidget extends InheritedWidget {
  final int accountId;
  final int scopeId;

  MyInheritedWidget(accountId, scopeId, child): super(child);
  
  @override
  bool updateShouldNotify(MyInheritedWidget old) =>
    accountId != old.accountId || scopeId != old.scopeId;
}

class MyPage extends StatelessWidget {
  final int accountId;
  final int scopeId;
  
  MyPage(this.accountId, this.scopeId);
  
  Widget build(BuildContext context) {
    return new MyInheritedWidget(
      accountId,
      scopeId,
      const MyWidget(),
     );
  }
}

class MyWidget extends StatelessWidget {

  const MyWidget();
  
  Widget build(BuildContext context) {
    // -   
    const MyOtherWidget();
    ...
  }
}

class MyOtherWidget extends StatelessWidget {

  const MyOtherWidget();
  
  Widget build(BuildContext context) {
    final myInheritedWidget = MyInheritedWidget.of(context);
    print(myInheritedWidget.scopeId);
    print(myInheritedWidget.accountId);
    ...


Es importante tener en cuenta:



  • Los constructores son ahora los constque hacen que estos widgets se puedan almacenar en caché; lo que aumenta la productividad.
  • Cuando se actualizan los parámetros, se crea uno nuevo MyInheritedWidget. Sin embargo, a diferencia del primer ejemplo, el subárbol no se reconstruye. En cambio, Flutter mantiene un registro interno que realiza un seguimiento de los widgets que han accedido a esto InheritedWidgety reconstruye solo aquellos widgets que usan este contexto. En este ejemplo, lo es MyOtherWidget.
  • Si el árbol se está reconstruyendo por motivos distintos a los cambios de parámetros, como cambios de orientación, su código aún puede construir uno nuevo InheritedWidget. Sin embargo, dado que los parámetros siguen siendo los mismos, los widgets del subárbol no serán notificados. Este es el propósito de la función updateShouldNotifyimplementada por el suyo InheritedWidget.


Por último, hablemos de buenas prácticas.



InheritedWidget debe ser pequeño



Sobrecargarlos con mucho contexto conduce a la pérdida de la segunda y tercera ventajas mencionadas anteriormente, ya que Flutter no puede determinar qué parte del contexto se está actualizando y qué parte están utilizando los widgets. En lugar:



class MyAppContext {
  int teamId;
  String teamName;
  
  int studentId;
  String studentName;
  
  int classId;
  ...
}


Prefiero hacer:



class TeamContext {
  int teamId;
  String teamName;
}

class StudentContext {
  int studentId;
  String studentName;
}
 
class ClassContext {
  int classId;
  ...
}


Utilice const para crear sus widgets



Sin const, no hay reconstrucción selectiva del subárbol. Flutter crea una nueva instancia de cada widget en el subárbol y lo invoca build(), desperdiciando ciclos preciosos, especialmente si sus métodos de construcción son lo suficientemente pesados.



Esté atento al alcance de sus InheritedWidget-s



InheritedWidget-y se colocan en la raíz del árbol de widgets. Esto, de hecho, determina su alcance. En nuestro equipo, descubrimos que poder declarar contexto en cualquier parte del árbol de widgets era excesivo. Decidimos restringir nuestros widgets contextuales para aceptar solo Scaffold(o sus derivados) como hijos. De esta forma nos aseguramos de que el contexto más granular pueda estar en el nivel de la página y obtenemos dos ámbitos:



  • Widgets a nivel de aplicación como MediaQuery. Están disponibles para cualquier widget en cualquier página de su aplicación, ya que se encuentran en la raíz del árbol de widgets de su aplicación.
  • Widgets de nivel de página como MyInheritedWidgetel ejemplo anterior.


Debe elegir uno u otro dependiendo de dónde se aplique el contexto.



Los widgets a nivel de página no pueden atravesar un borde de ruta



Parece obvio. Sin embargo, esto tiene serias implicaciones porque la mayoría de las aplicaciones tienen más de un nivel de navegación. Así es como podría verse su aplicación:



> School App [App Context]
  > Student [Student Context]
    > Grades
    > Bio
  > Teacher [Teacher Context]
    > Courses
    > Bio


Esto es lo que ve Flutter:



> School App [App Context]
  > Student [Student Context]
  > Student Grades
  > Student Bio
  > Teacher [Teacher Context]
  > Teacher Courses
  > Teacher Bio


Desde la perspectiva de Flutter, no existe una jerarquía de navegación. Cada página (o andamio) es un árbol de widgets asociados con un widget de aplicación. Por lo tanto, cuando usa Navigator.pushestas páginas para mostrar, no heredan el widget que lleva el contexto principal. En el ejemplo anterior, deberá pasar explícitamente el contexto Studentde la página del estudiante a la página de la biografía del estudiante.



Si bien hay diferentes formas de pasar contexto, sugiero parametrizar las rutas a la antigua (por ejemplo, la codificación de URL si está utilizando rutas con nombre). También garantiza que las páginas se puedan crear basándose únicamente en la ruta sin tener que utilizar el contexto de su página principal.



¡Feliz codificación!



¡Llegue a tiempo para el curso!



All Articles