dart - setup - flutter start
Aleteo: ¿Cómo usar correctamente un widget heredado? (3)
¿Cuál es la forma correcta de usar un widget heredado? Hasta ahora entendí que te da la oportunidad de propagar datos por el árbol de widgets. En extremo, si lo coloca como RootWidget, será accesible desde todos los Widgets en el árbol en todas las Rutas, lo cual está bien porque de alguna manera tengo que hacer que mi ViewModel / Model sea accesible para mis Widgets sin tener que recurrir a globals o Singletons.
PERO InheritedWidget es inmutable, entonces, ¿cómo puedo actualizarlo? Y más importante, ¿cómo se activan mis Widgets con estado para reconstruir sus subárboles?
Desafortunadamente, la documentación aquí es muy poco clara y después de una discusión con muchos, nadie parece saber realmente cuál es la forma correcta de usarla.
Agrego una cita de Brian Egan:
Sí, lo veo como una forma de propagar datos por el árbol. Lo que encuentro confuso, de los documentos de la API:
"Los widgets heredados, cuando se hace referencia de esta manera, harán que el consumidor se reconstruya cuando el widget heredado cambie de estado".
Cuando leí esto por primera vez, pensé:
Podría rellenar algunos datos en el InheritedWidget y mutarlos más tarde. Cuando ocurra esa mutación, reconstruirá todos los Widgets que hacen referencia a mi InheritedWidget Lo que encontré:
Para mutar el estado de un widget heredado, debe envolverlo en un widget de estado. Luego, en realidad mutará el estado del widget de estado y pasará estos datos al widget de herencia, que transfiere los datos a todos sus elementos secundarios. Sin embargo, en ese caso, parece reconstruir todo el árbol debajo del StatefulWidget, no solo los Widgets que hacen referencia al InheritedWidget. ¿Es eso correcto? ¿O de alguna manera sabrá cómo omitir los Widgets que hacen referencia al InheritedWidget si updateShouldNotify devuelve falso?
De los docs :
[BuildContext. o el widget desaparece), este contexto de compilación se reconstruye para que pueda obtener nuevos valores de ese widget.
Esto normalmente se llama implícitamente de los métodos estáticos de (), por ejemplo, Theme.of.
Como señaló el OP, una instancia de
InheritedWidget
no cambia ... pero se puede reemplazar con una nueva instancia en la misma ubicación en el árbol de widgets.
Cuando eso sucede, es posible que los widgets registrados necesiten ser reconstruidos.
El método
InheritedWidget.updateShouldNotify
hace esta determinación.
(Ver:
docs
)
Entonces, ¿cómo podría reemplazarse una instancia?
Una instancia de
InheritedWidget
puede estar contenida por un
StatefulWidget
, que puede reemplazar una instancia anterior con una nueva instancia.
El problema proviene de su presupuesto, que es incorrecto.
Como dijiste, los widgets heredados son, como otros widgets, inmutables. Por lo tanto, no se actualizan . Se crean de nuevo.
La cuestión es:
InheritedWidget es solo un widget simple que no hace más que mantener datos
.
No tiene ninguna lógica de actualización ni ninguna.
Pero, como cualquier otro widget, está asociado a un
Element
.
Y adivina qué ?
¡Esto es mutable y el aleteo lo reutilizará siempre que sea posible!
La cita corregida sería:
InheritedWidget, cuando se hace referencia de esta manera, hará que el consumidor se reconstruya cuando InheritedWidget asociado a un InheritedElement cambie.
Se habla mucho sobre cómo los widgets / elementos / renderbox se conectan entre sí . Pero en resumen, son así (la izquierda es su widget típico, el medio es ''elementos'' y la derecha son ''cuadros de renderizado''):
La cuestión es: cuando crea una instancia de un nuevo widget; flutter lo comparará con el anterior. Reutilice su "Elemento", que apunta a un RenderBox. Y mutar las propiedades de renderbox.
Okey, pero ¿cómo responde esto a mi pregunta?
Bueno, es facil.
Al crear una instancia de un InheritedWidget y luego llamar a
context.inheritedWidgetOfExactType
(o
MyClass.of
que es básicamente lo mismo);
lo que está implícito es que escuchará el
Element
asociado con su
InheritedWidget
.
Y cada vez que ese
Element
obtenga un nuevo widget, forzará la actualización de cualquier widget que haya llamado al método anterior.
En resumen, cuando reemplaza un
InheritedWidget
existente por uno nuevo;
aleteo verá que ha cambiado.
Y notificará a los widgets vinculados de una posible modificación.
Si entendiste todo, ya deberías haber adivinado la solución:
¡Envuelva su
InheritedWidget
dentro de un
StatefulWidget
que creará un nuevo
InheritedWidget
cada vez que algo cambie!
En esta situación, se recomienda que sus datos de
InheritedWidget
sean solo la instancia de su
StatefulWidget
y luego hagan que
InheritedWidget
privado.
Para evitar copiar y pegar innecesarios y posibles errores.
El resultado final en el código real sería:
class MyInherited extends StatefulWidget {
Widget child;
MyInherited({this.child});
@override
MyInheritedState createState() => new MyInheritedState();
static MyInheritedState of(BuildContext context) {
return (context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited).data;
}
}
class MyInheritedState extends State<MyInherited> {
String _myField;
// only expose a getter to prevent bad usage
String get myField => _myField;
void onMyFieldChange(String newValue) {
setState(() {
_myField = newValue;
});
}
@override
Widget build(BuildContext context) {
return new _MyInherited(
data: this,
child: widget.child,
);
}
}
/// Only has MyInheritedState as field.
class _MyInherited extends InheritedWidget {
final MyInheritedState data;
_MyInherited({Key key, this.data, Widget child}) : super(key: key, child: child);
@override
bool updateShouldNotify(_MyInherited old) {
return true;
}
}
¿Pero no crear un nuevo InheritedWidget reconstruiría todo el árbol?
No, no será necesario. Como su nuevo InheritedWidget puede tener exactamente el mismo hijo que antes. Y por exacto, me refiero a la misma instancia. Los widgets que tienen la misma instancia que tenían antes no se reconstruyen.
Y en la mayoría de las situaciones (Tener un widget heredado en la raíz de su aplicación), el widget heredado es constante . Entonces no hay reconstrucción innecesaria.
TL; DR
No use cálculos pesados dentro del método updateShouldNotify y use const en lugar de new al crear un widget
En primer lugar, debemos entender qué son los objetos Widget, Element y Render.
- Los objetos de representación son lo que realmente se representa en la pantalla. Son mutables , contienen la pintura y la lógica de diseño. El árbol Render es muy similar al Modelo de objetos de documento (DOM) en la web y puede ver un objeto render como un nodo DOM en este árbol
- Widget : es una descripción de lo que se debe representar. Son inmutables y baratos. Entonces, si un widget responde la pregunta "¿Qué?" (Enfoque declarativo), entonces un objeto Render responde a la pregunta "¿Cómo?" (Enfoque imperativo). Una analogía de la web es un "DOM virtual".
- Element / BuildContext : es un proxy entre los objetos Widget y Render . Contiene información sobre la posición de un widget en el árbol * y cómo actualizar el objeto Render cuando se cambia un widget correspondiente.
Ahora estamos listos para sumergirnos en InheritedWidget y el método de BuildContext heredarFromWidgetOfExactType .
Como ejemplo, recomiendo que consideremos este ejemplo de la documentación de Flutter sobre InheritedWidget:
class FrogColor extends InheritedWidget {
const FrogColor({
Key key,
@required this.color,
@required Widget child,
}) : assert(color != null),
assert(child != null),
super(key: key, child: child);
final Color color;
static FrogColor of(BuildContext context) {
return context.inheritFromWidgetOfExactType(FrogColor);
}
@override
bool updateShouldNotify(FrogColor old) {
return color != old.color;
}
}
InheritedWidget: solo un widget que implementa en nuestro caso un método importante: updateShouldNotify . updateShouldNotify : una función que acepta un parámetro oldWidget y devuelve un valor booleano: verdadero o falso.
Al igual que cualquier widget, InheritedWidget tiene un objeto Element correspondiente. Es un elemento heredado . InheritedElement call updateShouldNotify en el widget cada vez que creamos un nuevo widget (llame a setState en un antepasado). Cuando updateShouldNotify devuelve verdadero InheritedElement itera a través de dependencias (?) Y el método de llamada didChangeDependencies en él.
¿Dónde InheritedElement obtiene dependencias ? Aquí deberíamos mirar el método heredarFromWidgetOfExactType .
inheritFromWidgetOfExactType : este método definido en BuildContext y cada elemento implementa la interfaz BuildContext (Element == BuildContext). Entonces cada elemento tiene este método.
Veamos el código de heredarFromWidgetOfExactType:
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
if (ancestor != null) {
assert(ancestor is InheritedElement);
return inheritFromElement(ancestor, aspect: aspect);
}
Aquí intentamos encontrar un antepasado en _inheritedWidgets mapeado por tipo. Si se encuentra el antepasado, entonces llamamos herencia de origen .
El código para inheritFromElement :
InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect }) {
assert(ancestor != null);
_dependencies ??= HashSet<InheritedElement>();
_dependencies.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}
- Agregamos ancestro como una dependencia del elemento actual (_dependencies.add (ancestor))
- Agregamos el elemento actual a las dependencias de ancestro (ancestor.updateDependencies (este aspecto))
- Devolvemos el widget de antepasado como resultado de inheritFromWidgetOfExactType (return ancestor.widget)
Entonces, ahora sabemos de dónde obtiene InheritedElement sus dependencias.
Ahora veamos el método didChangeDependencies . Cada elemento tiene este método:
void didChangeDependencies() {
assert(_active); // otherwise markNeedsBuild is a no-op
assert(_debugCheckOwnerBuildTargetExists(''didChangeDependencies''));
markNeedsBuild();
}
Como podemos ver, este método solo marca un elemento como sucio y este elemento debe reconstruirse en el siguiente cuadro. Reconstruir significa construir un método de llamada en el elemento del widget correspondiente.
Pero, ¿qué pasa con "Reconstrucciones de subárboles completos cuando reconstruyo InheritedWidget?". Aquí debemos recordar que los widgets son inmutables y si crea un nuevo widget, Flutter reconstruirá el subárbol. ¿Cómo podemos arreglarlo?
- Widgets de caché a mano (manualmente)
- Use const porque const crea la única instancia de valor / clase