Bagaimana cara menangani pembangunan widget yang tidak diinginkan?

143

Karena berbagai alasan, terkadang buildmetode widget saya dipanggil lagi.

Saya tahu itu terjadi karena orang tua diperbarui. Tetapi ini menyebabkan efek yang tidak diinginkan. Situasi umum yang menyebabkan masalah adalah saat menggunakan FutureBuildercara ini:

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

Dalam contoh ini, jika metode build dipanggil lagi, itu akan memicu permintaan http lain. Yang tidak diinginkan.

Mempertimbangkan hal ini, bagaimana cara menangani bangunan yang tidak diinginkan? Apakah ada cara untuk mencegah panggilan membangun?

Rémi Rousselet
sumber
4
Dalam dokumentasi penyedia Anda menautkan di sini mengatakan "Lihat jawaban stackoverflow ini yang menjelaskan secara lebih rinci mengapa menggunakan konstruktor .value untuk membuat nilai tidak diinginkan." Namun, Anda tidak menyebutkan konstruktor nilai di sini atau dalam jawaban Anda. Apakah Anda bermaksud menautkan tempat lain?
Suragch

Jawaban:

225

The build Metode ini dirancang sedemikian rupa bahwa itu harus murni / tanpa efek samping . Ini karena banyak faktor eksternal dapat memicu pembangunan widget baru, seperti:

  • Rute pop / push
  • Ubah ukuran layar, biasanya karena tampilan keyboard atau perubahan orientasi
  • Widget induk menciptakan ulang anaknya
  • Widget InheritedWidget tergantung pada Class.of(context)perubahan ( pola)

Ini berarti bahwa buildmetode ini tidak boleh memicu panggilan http atau memodifikasi keadaan apa pun .


Bagaimana ini terkait dengan pertanyaan?

Masalah yang Anda hadapi adalah bahwa metode build Anda memiliki efek samping / tidak murni, membuat panggilan build tambahan merepotkan.

Alih-alih mencegah panggilan build, Anda harus membuat metode build Anda murni, sehingga dapat dipanggil kapan saja tanpa dampak.

Dalam kasus contoh Anda, Anda akan mengubah widget Anda menjadi StatefulWidgetkemudian ekstrak bahwa panggilan HTTP ke initStatedari Anda 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
      },
    );
  }
}

Saya sudah tahu ini. Saya datang ke sini karena saya benar - benar ingin mengoptimalkan pembangunan kembali

Dimungkinkan juga untuk membuat widget yang mampu membangun kembali tanpa memaksa anak-anaknya untuk membangun juga.

Ketika instance widget tetap sama; Flutter dengan sengaja tidak akan membangun kembali anak-anak. Ini menyiratkan bahwa Anda dapat melakukan cache bagian pohon widget Anda untuk mencegah pembangunan kembali yang tidak perlu.

Cara termudah adalah dengan menggunakan constkonstruktor panah :

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

Berkat constkata kunci itu, instance dari DecoratedBoxakan tetap sama meskipun build disebut ratusan kali.

Tetapi Anda dapat mencapai hasil yang sama secara manual:

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

Dalam contoh ini ketika StreamBuilder diberitahu tentang nilai-nilai baru, subtreetidak akan membangun kembali bahkan jika StreamBuilder / Kolom melakukannya. Itu terjadi karena, berkat penutupan, contoh MyWidgettidak berubah.

Pola ini banyak digunakan dalam animasi. Kegunaan umum adalah AnimatedBuilderdan semua transisi seperti AlignTransition.

Anda juga dapat menyimpan subtreeke bidang kelas Anda, meskipun kurang direkomendasikan karena merusak fitur hot-reload.

Rémi Rousselet
sumber
2
Bisakah Anda menjelaskan mengapa menyimpan subtreedi bidang kelas memecah hot-reload?
mFeinstein
4
Masalah yang saya alami StreamBuilderadalah ketika keyboard muncul layar berubah, sehingga rute harus dibangun kembali. Jadi StreamBuilderdibangun kembali dan yang baru StreamBuilderdibuat dan berlangganan stream. Ketika StreamBuilderberlangganan stream, snapshot.connectionStatemenjadi ConnectionState.waitingyang membuat kode saya kembali CircularProgressIndicator, dan kemudian snapshot.connectionStateberubah ketika ada data, dan kode saya akan mengembalikan widget yang berbeda, yang membuat layar berkedip dengan hal-hal yang berbeda.
mFeinstein
1
Saya memutuskan untuk membuat StatefulWidget, berlangganan streampada initState()dan mengatur currentWidgetdengan setState()sebagai streammengirimkan data baru, melewati currentWidgetke build()metode. Apakah ada solusi yang lebih baik?
mFeinstein
1
Saya sedikit bingung. Anda menjawab pertanyaan Anda sendiri, namun dari kontennya, sepertinya tidak.
sgon00
8
Eh, mengatakan bahwa build seharusnya tidak memanggil metode HTTP sepenuhnya mengalahkan contoh yang sangat praktis dari a FutureBuilder.
TheGeekZn
6

Anda dapat mencegah panggilan membangun yang tidak diinginkan, menggunakan cara ini

1) Buat kelas Statefull anak untuk setiap bagian kecil UI

2) Gunakan perpustakaan Provider , jadi menggunakannya Anda dapat menghentikan pemanggilan metode build yang tidak diinginkan.

Dalam pemanggilan metode build situasi berikut ini

  • Setelah memanggil initState
  • Setelah menelepon didUpdateWidget
  • ketika setState () dipanggil.
  • saat keyboard terbuka
  • ketika orientasi layar berubah
  • Widget induk dibuat lalu widget anak juga dibangun kembali
Sanjayrajsinh
sumber
0

Flutter juga punya ValueListenableBuilder<T> class . Ini memungkinkan Anda untuk membangun kembali hanya beberapa widget yang diperlukan untuk tujuan Anda dan melewatkan widget mahal.

Anda dapat melihat dokumen-dokumen di sini ValueListenableBuilder bergetar dokumen
atau hanya kode sampel di bawah ini:

  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
sumber