首页 > 解决方案 > 在 StreamBuilder 中使用 Selector (Provider) 时不必要的小部件重建

问题描述

Selector当 Bloc 中的数据发生变化时,我正在使用重建。哪个很好,但是当数据更改时,它会重新加载整个树,而不仅仅是 Selector 中的构建器。

在我的例子中,选择器位于 StreamBuilder 中。我需要这个,因为流连接到 API。所以在流中我正在构建一些小部件,其中之一是选择器。Selector重建依赖于来自 Stream 的数据的小部件。

这是我的代码。我不希望 Stream 被一次又一次地调用。Stream 也被调用,因为build每次选择器小部件重建时都会调用。

主要.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_test/data_bloc.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MultiProvider(providers: [
        ChangeNotifierProvider<DataBloc>(
          create: (_) => DataBloc(),
        )
      ], child: ProviderTest()),
    );
  }
}

class ProviderTest extends StatefulWidget {
  @override
  _ProviderTestState createState() => _ProviderTestState();
}

class _ProviderTestState extends State<ProviderTest> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: <Widget>[
          Text("Outside Stream Builder"),
          StreamBuilder(
            stream: Provider.of<DataBloc>(context).getString(),
            builder: (_, AsyncSnapshot<String> snapshot) {
              if (snapshot.hasData) {
                return Column(
                  children: <Widget>[
                    Text("Widget Generated by Stream Data"),
                    Text("Data From Strem : " + snapshot.data),
                    RaisedButton(
                        child: Text("Reload Select"),
                        onPressed: () {
                          Provider.of<DataBloc>(context, listen: false).changeValue(5);
                        }),
                    Selector<DataBloc, int>(
                        selector: (_, val) =>
                            Provider.of<DataBloc>(context, listen: false).val,
                        builder: (_, val, __) {
                          return Container(
                            child: Text(val.toString()),
                          );
                        }),
                  ],
                );
              }

              return Container();
            },
          )
        ],
      ),
    );
  }
}

块飞镖

import 'package:flutter/foundation.dart';

class DataBloc with ChangeNotifier {

  int _willChange = 0;

  int get val => _willChange;

  void changeValue(int val){
    _willChange++;
    notifyListeners();
  }

  Stream<String> getString() {
    print("Stream Called");
    return Stream.fromIterable(["one", "two", "three"]);
  }

}

此外,如果我删除 StreamBuilder,那么Selector行为就像它假设的那样。为什么在这种情况下 StreamBuilder 会重建?有没有办法防止这种情况发生?

标签: flutterdartflutter-webflutter-provider

解决方案


根据您共享的代码,您可以在您的 initState 上为您的 Stream 创建一个侦听器,该侦听器更新一个保留最新版本数据的变量,然后使用该变量填充您的小部件。这样,Stream 只会在第一次加载 Widget 时被订阅,而不是在重建时订阅。我无法直接测试它,因为我没有你的项目。但请尝试一下。

基于您的代码的代码示例

class ProviderTest extends StatefulWidget {
  @override
  _ProviderTestState createState() => _ProviderTestState();
}

class _ProviderTestState extends State<ProviderTest> {
  String _snapshotData;

  @override
  void initState() {
    listenToGetString();
    super.initState();
  }

  void listenToGetString(){
    Provider.of<DataBloc>(context).getString().listen((snapshot){
      setState(() {
        _snapshotData = snapshot.data;
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: <Widget>[
          Text("Outside Stream Builder"),
          Column(
            children: <Widget>[
              Text("Widget Generated by Stream Data"),
              Text("Data From Strem : " + _snapshotData),
              RaisedButton(
                child: Text("Reload Select"),
                onPressed: () {
                  Provider.of<DataBloc>(context, listen: false).changeValue(5);
                }
              ),
              Selector<DataBloc, int>(
                selector: (_, val) =>
                  Provider.of<DataBloc>(context, listen: false).val,
                builder: (_, val, __) {
                  return Container(
                    child: Text(val.toString()),
                  );
                }
              ),
            ],
          )
        ],
      ),
    );
  }
}

推荐阅读