¿Cómo lidiar con la creación de widgets no deseados?

143

Por varias razones, a veces buildse vuelve a llamar al método de mis widgets.

Sé que sucede porque un padre actualizó. Pero esto causa efectos no deseados. Una situación típica en la que causa problemas es cuando se usa de FutureBuilderesta manera:

@override
Widget build(BuildContext context) {
  return FutureBuilder(
    future: httpCall(),
    builder: (context, snapshot) {
      // create some layout here
    },
  );
}

En este ejemplo, si se volviera a llamar al método de compilación , desencadenaría otra solicitud http. Lo cual no es deseado.

Teniendo en cuenta esto, ¿cómo lidiar con la construcción no deseada? ¿Hay alguna forma de evitar la llamada de compilación?

Rémi Rousselet
fuente
44
En la documentación del proveedor que vincula aquí, dice "Vea esta respuesta de stackoverflow que explica con más detalles por qué no es deseable usar el constructor .value para crear valores". Sin embargo, no menciona el constructor de valores aquí ni en su respuesta. ¿Querías vincular a otro lugar?
Suragch

Respuestas:

224

El método de construcción está diseñado de tal manera que debe ser puro / sin efectos secundarios . Esto se debe a que muchos factores externos pueden desencadenar una nueva compilación de widgets, como:

  • Ruta pop / push
  • Cambiar el tamaño de la pantalla, generalmente debido a la apariencia del teclado o al cambio de orientación
  • El widget principal recreó a su hijo
  • Un widget heredado el widget depende del Class.of(context)cambio ( patrón)

Esto significa que el buildmétodo no debe activar una llamada http ni modificar ningún estado .


¿Cómo se relaciona esto con la pregunta?

El problema que enfrenta es que su método de compilación tiene efectos secundarios / no es puro, lo que hace que las llamadas de compilación extrañas sean problemáticas.

En lugar de evitar la llamada de compilación, debe hacer que su método de compilación sea puro, para que pueda llamarse en cualquier momento sin impacto.

En el caso de su ejemplo, transformaría su widget en un StatefulWidgetextracto y luego extraería esa llamada HTTP a initStatesu State:

class Example extends StatefulWidget {
  @override
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends State<Example> {
  Future<int> future;

  @override
  void initState() {
    future = Future.value(42);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: future,
      builder: (context, snapshot) {
        // create some layout here
      },
    );
  }
}

Ya se esto. Vine aquí porque realmente quiero optimizar las reconstrucciones

También es posible hacer un widget capaz de reconstruir sin obligar a sus hijos a construir también.

Cuando la instancia de un widget permanece igual; El aleteo a propósito no reconstruirá a los niños. Implica que puede almacenar en caché partes de su árbol de widgets para evitar reconstrucciones innecesarias.

La forma más fácil es usar constconstructores de dardos :

@override
Widget build(BuildContext context) {
  return const DecoratedBox(
    decoration: BoxDecoration(),
    child: Text("Hello World"),
  );
}

Gracias a esa constpalabra clave, la instancia de DecoratedBoxpermanecerá igual incluso si se llamara build cientos de veces.

Pero puede lograr el mismo resultado manualmente:

@override
Widget build(BuildContext context) {
  final subtree = MyWidget(
    child: Text("Hello World")
  );

  return StreamBuilder<String>(
    stream: stream,
    initialData: "Foo",
    builder: (context, snapshot) {
      return Column(
        children: <Widget>[
          Text(snapshot.data),
          subtree,
        ],
      );
    },
  );
}

En este ejemplo, cuando se notifica a StreamBuilder sobre nuevos valores, subtreeno se reconstruirá incluso si lo hace StreamBuilder / Column. Ocurre porque, gracias al cierre, la instancia de MyWidgetno cambió.

Este patrón se usa mucho en animaciones. Los usos típicos son AnimatedBuildery todas las transiciones como AlignTransition.

También puede almacenar subtreeen un campo de su clase, aunque es menos recomendable ya que rompe la función de recarga en caliente.

Rémi Rousselet
fuente
2
¿Podría explicar por qué el almacenamiento subtreeen un campo de clase interrumpe la recarga en caliente?
mFeinstein
44
Un problema que tengo StreamBuilderes que cuando aparece el teclado, la pantalla cambia, por lo que las rutas deben reconstruirse. Entonces StreamBuilderse reconstruye y StreamBuilderse crea un nuevo y se suscribe al stream. Cuando se StreamBuildersuscribe a a stream, se snapshot.connectionStateconvierte en lo ConnectionState.waitingque hace que mi código devuelva a CircularProgressIndicator, y luego snapshot.connectionStatecambia cuando hay datos, y mi código devolverá un widget diferente, lo que hace que la pantalla parpadee con diferentes cosas.
mFeinstein
1
Decidí hacer un StatefulWidget, suscribirme al streamencendido initState()y configurarlo currentWidgetcon setState()el streamenvío de nuevos datos, pasando currentWidgetal build()método. ¿Hay alguna solución mejor?
mFeinstein
1
Estoy un poco confundido. Estás respondiendo tu propia pregunta, sin embargo, desde el contenido, no parece.
sgon00
8
Uh, decir que una compilación no debería llamar a un método HTTP derrota por completo el ejemplo muy práctico de a FutureBuilder.
TheGeekZn
6

Puede evitar llamadas de compilación no deseadas, de esta manera

1) Cree una clase Statefull secundaria para una pequeña parte individual de la IU

2) Uso de Proveedores de la biblioteca, por lo que su utilización, usted puede detener no deseados método de aumento de llamada

En estos a continuación situación llamada método de aumento

  • Después de llamar a initState
  • Después de llamar a didUpdateWidget
  • cuando se llama a setState () .
  • cuando el teclado está abierto
  • cuando la orientación de la pantalla cambió
  • El widget primario se construye, luego el widget secundario también se reconstruye
Sanjayrajsinh
fuente
0

Flutter también tiene ValueListenableBuilder<T> class . Le permite reconstruir solo algunos de los widgets necesarios para su propósito y omitir los caros widgets.

puede ver los documentos aquí ValueListenableBuilder flutter docs
o simplemente el código de muestra a continuación:

  return Scaffold(
  appBar: AppBar(
    title: Text(widget.title)
  ),
  body: Center(
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Text('You have pushed the button this many times:'),
        ValueListenableBuilder(
          builder: (BuildContext context, int value, Widget child) {
            // This builder will only get called when the _counter
            // is updated.
            return Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: <Widget>[
                Text('$value'),
                child,
              ],
            );
          },
          valueListenable: _counter,
          // The child parameter is most helpful if the child is
          // expensive to build and does not depend on the value from
          // the notifier.
          child: goodJob,
        )
      ],
    ),
  ),
  floatingActionButton: FloatingActionButton(
    child: Icon(Icons.plus_one),
    onPressed: () => _counter.value += 1,
  ),
);
Taba
fuente