Jak radzić sobie z niechcianą budową widgetu?

166

Z różnych powodów czasami buildmetoda moich widżetów jest wywoływana ponownie.

Wiem, że dzieje się tak, ponieważ rodzic się zaktualizował. Ale to powoduje niepożądane skutki. Typowa sytuacja, w której powoduje to problemy, ma miejsce podczas używania w FutureBuilderten sposób:

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

W tym przykładzie, jeśli metoda budowania miałaby zostać wywołana ponownie, wyzwoliłoby to kolejne żądanie HTTP. Co jest niepożądane.

Biorąc to pod uwagę, jak poradzić sobie z niechcianą kompilacją? Czy istnieje sposób, aby zapobiec wywołaniu kompilacji?

Rémi Rousselet
źródło
12
W dokumentacji dostawcy, do której odsyłasz tutaj, mówi się: „Zobacz tę odpowiedź dotyczącą przepełnienia stosu, która wyjaśnia bardziej szczegółowo, dlaczego używanie konstruktora .value do tworzenia wartości jest niepożądane”. Nie wspominasz jednak o konstruktorze wartości tutaj ani w swojej odpowiedzi. Czy chciałeś utworzyć link gdzieś indziej?
Suragch
@ Suragch to jest poprawny link. Problem nie dotyczy dostawcy, a problem z konstruktorem „.value” jest identyczny z tym, co opisano tutaj. To znaczy, zamień FutureBuilder na SomeProvider.value
Rémi Rousselet
8
Poleciłbym albo wyjaśnić niepożądane skutki uboczne bezpośrednio w dokumentacji (pierwszy wybór), albo dodać więcej wyjaśnień tutaj (drugi wybór). Nie wiem, czy jestem reprezentatywny dla przeciętnego użytkownika Dostawcy, czy nie, ale kiedy tu przychodzę, nadal nie rozumiem związku między używaniem .valuea niechcianą kompilacją widżetu lub buildmetodą, która musi być czysta.
Suragch

Odpowiedzi:

259

Sposób budowania jest zaprojektowany w taki sposób, aby był czysty / bez skutków ubocznych . Dzieje się tak, ponieważ wiele czynników zewnętrznych może wywołać nową kompilację widżetu, na przykład:

  • Trasa pop / push
  • Zmiana rozmiaru ekranu, zwykle ze względu na wygląd klawiatury lub zmianę orientacji
  • Widżet rodzica odtworzył swoje dziecko
  • InheritedWidget widget zależy od Class.of(context)zmiany ( wzorca)

Oznacza to, że buildmetoda nie powinna wyzwalać wywołania http ani modyfikować żadnego stanu .


Jak to się ma do pytania?

Problem, z którym się zmagasz, polega na tym, że metoda budowania ma skutki uboczne / nie jest czysta, co sprawia, że ​​obce wywołania kompilacji są kłopotliwe.

Zamiast blokować wywołanie build, powinieneś uczynić swoją metodę budowania czystą, aby można ją było wywołać w dowolnym momencie bez wpływu.

W przypadku Twojego przykład, można by przekształcić swoje widgetu do StatefulWidgetnastępnie wyodrębnić tego połączenia HTTP do initStatetwoich 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
      },
    );
  }
}

Już to wiem. Przyszedłem tutaj, ponieważ naprawdę chcę zoptymalizować przebudowy

Możliwe jest również stworzenie widgetu zdolnego do odbudowy bez zmuszania jego elementów podrzędnych do budowania.

Gdy instancja widgetu pozostaje taka sama; Flutter celowo nie odbuduje dzieci. Oznacza to, że można buforować części drzewa widżetów, aby zapobiec niepotrzebnym przebudowom.

Najłatwiej jest użyć constkonstruktorów rzutek :

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

Dzięki temu constsłowu kluczowemu instancja DecoratedBoxpozostanie taka sama, nawet jeśli build był wywoływany setki razy.

Ale możesz osiągnąć ten sam wynik ręcznie:

@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,
        ],
      );
    },
  );
}

W tym przykładzie, gdy StreamBuilder zostanie powiadomiony o nowych wartościach, subtreenie zostanie odbudowany, nawet jeśli StreamBuilder / Column. Dzieje się tak, ponieważ dzięki zamknięciu instancja MyWidgetnie uległa zmianie.

Ten wzór jest często używany w animacjach. Typowe zastosowania AnimatedBuilderi wszystkie przejścia, takie jak AlignTransition.

Możesz również przechowywać subtreew polu swojej klasy, chociaż jest to mniej zalecane, ponieważ łamie to funkcję hot-reload.

Rémi Rousselet
źródło
2
Czy możesz wyjaśnić, dlaczego przechowywanie subtreew polu klasy przerywa przeładowywanie na gorąco?
Michel Feinstein
6
Mam problem z StreamBuildertym, że kiedy pojawia się klawiatura, ekran się zmienia, więc trasy trzeba odbudować. Więc StreamBuilderjest odbudowywany i StreamBuildertworzony jest nowy i subskrybuje stream. Gdy StreamBuildersubskrybuje element stream, snapshot.connectionStatestaje się, ConnectionState.waitingco powoduje, że mój kod zwraca a CircularProgressIndicator, a następnie snapshot.connectionStatezmienia się, gdy są dane, a mój kod zwróci inny widżet, co powoduje migotanie ekranu przy różnych elementach.
Michel Feinstein
1
Postanowiłem zrobić StatefulWidget, zasubskrybować streamon initState()i ustawić currentWidgetz setState()jak streamwysyła nowe dane, przekazując currentWidgetdo build()metody. Czy jest lepsze rozwiązanie?
Michel Feinstein
1
Jestem trochę zdezorientowany. Odpowiadasz na własne pytanie, jednak z treści na to nie wygląda.
sgon00
11
Uh, stwierdzenie, że kompilacja nie powinna wywoływać metody HTTP całkowicie pokonuje bardzo praktyczny przykład pliku FutureBuilder.
TheGeekZn
14

Możesz zapobiec niechcianym wywołaniom kompilacji, używając tych sposobów

1) Utwórz podrzędną klasę Statefull dla indywidualnej małej części interfejsu użytkownika

2) Użyj biblioteki Provider , dzięki czemu możesz zatrzymać niechciane wywołanie metody budowania

W poniższej sytuacji wywołanie metody budowania

Sanjayrajsinh
źródło
2

Flutter też ValueListenableBuilder<T> class . Pozwala przebudować tylko niektóre widżety niezbędne do twojego celu i pominąć drogie widżety.

możesz zobaczyć dokumenty tutaj ValueListenableBuilder flutter docs
lub tylko przykładowy kod poniżej:

  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
źródło