在StreamBuilder中使用选择器(提供程序)时,无需重新构建小部件

时间:2020-01-02 21:08:03

标签: flutter dart flutter-web flutter-provider

我正在使用Selector,它会在Bloc中的数据更改时重建。可以正常工作,但是当数据更改时,它会重新加载整个树,而不仅仅是Selector内部的构建器。

就我而言,选择器位于StreamBuilder中。我需要这个,因为流已连接到API。因此,在流中,我正在构建一些小部件,其中之一是Selector。 Selector重建依赖于Stream中数据的窗口小部件。

这是我的代码。我不想一次又一次地调用Stream。另外,还会调用Stream,因为每次重建选择器小部件时都会调用build

main.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();
            },
          )
        ],
      ),
    );
  }
}

bloc.dart

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会重建?反正有什么可以防止的?

2 个答案:

答案 0 :(得分:1)

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

基于您的代码的代码示例

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

答案 1 :(得分:0)

阅读此blog post here后,我发现了问题。我缺少有关Provider lib的工作方式以及如何从继承的小部件中进行所有魔术操作的知识

解决这个问题的要点是。 (引自以上博客文章)

当小部件将自身注册为提供商的依赖项时 InheritedWidget,每次更改版本时都会重新构建该小部件 发生“提供的数据”(更确切地说,当notifyListeners() 被调用,或者StreamProvider的流发出新数据时,或者当 FutureProvider的未来完成了。

这意味着我要更改的变量和我要列出的Stream都存在于同一Bloc中!那是错误。因此,当我更改val并在单个块中调用notifyListener()时,所有内容都会重新加载,这是默认行为。

要解决此问题,我要做的就是创建另一个Bloc,并将Stream提取到该特定Bloc(我认为这也是一种良好做法)。现在notifyListener()对流没有影响。

data_bloc.dart

class DataBloc with ChangeNotifier {

  int _willChange = 0;

  String data = "";

  int get val => _willChange;

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

  Future<String> getData () async {
    return "Data";
  }

}

stream_bloc.dart

import 'package:flutter/foundation.dart';

class StreamBloc with ChangeNotifier {

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

}

问题解决了。现在,仅在流被调用时才调用流,而在data_bloc中变量更改时不会调用