Firestore: StreamBuilder是如何工作的,以及如何高效使用它。

huangapple go评论76阅读模式
英文:

Firestore: How does StreamBuilder work and how to use it efficiently

问题

我想知道:

假设我在类似于聊天的应用程序中有一个StreamBuilder,那么如果用户不在具有StreamBuilder的屏幕上,并且添加了新的文档,它是否会自动计为已读?

如何正确使用StreamBuilder,然后在用户离开屏幕时关闭它,以消耗最少的读取次数。

谢谢您。

英文:

I want to know:

Suppose I have a streamBuilder in an application like Chat, so if a user is not on the screen that has the streamBuilder and a new doc is added would it automatically count as a read?

How to properly use StreamBuilder and then Close it when the user leaves the screen for consuming the least amount of reads.

Thank You

答案1

得分: 1

根据定义:
StreamBuilder是一个基于数据流构建其输出的小部件。数据流可以是任何随时间发出数据的东西,比如Firestore集合。当数据流发出新数据时,StreamBuilder将重新构建其输出以反映新数据。

StreamBuilder接受两个参数:数据流和构建器函数。构建器函数将在数据流发出新数据时调用。

以下代码显示了如何使用StreamBuilder来显示Firestore文档列表:

class _HomePageState extends State<HomePage> {
  Stream<QuerySnapshot> stream; // 声明数据流

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

    stream = FirebaseFirestore.instance
        .collection('documents')
        .snapshots();
  }

  @override
  void dispose() {
    // 要关闭StreamBuilder,可以使用dispose()方法。
    stream.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('StreamBuilder with Debounce and Throttle'),
      ),
      body: StreamBuilder<QuerySnapshot>(
        stream: stream,
        builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
          if (snapshot.hasError) return Text('Something went wrong');
          if (snapshot.connectionState == ConnectionState.waiting) return Text("Loading");
          List<DocumentSnapshot> documents = snapshot.data!.docs;
          return ListView.builder(
            itemCount: documents.length,
            itemBuilder: (BuildContext context, int index) {
              DocumentSnapshot document = documents[index];
              return ListTile(
                title: Text(document['title']),
                subtitle: Text(document['body']),
              );
            },
          );
        },
      ),
    );
  }
}
  • 当向集合添加新文档时,StreamBuilder将重新构建文档列表。这将被视为对Firestore的读取操作。

  • 但当用户导航到新屏幕时,数据流将被释放。这是因为数据流仅在页面可见时才监听数据的更改。当您转到下一页时,数据流将不再监听更改,因此您将不会被收取任何读取费用。

  • 如果回到上一页,数据流将被重新创建,您将被收取费用以重建StreamBuilder。
    因此,只有在需要显示随时间变化的数据(实时变化)时才使用StreamBuilder。如果只需显示静态数据,可以使用ListView、FutureBuilder或其他不需要数据流的小部件。

简而言之:在您的应用程序上进行一些研究。如果确实需要文档的实时更新,那么要小心处理这些StreamBuilder,并确保在用户导航到新屏幕时释放这些数据流。

更新:在使用streamController时,我们还需要使用StreamSubscription.cancel()或调用streamController.cancel()来释放它,如下所示:

class _MyWidgetState extends State<MyWidget> {
  final StreamController<DocumentSnapshot> _streamController =
      StreamController.broadcast();

  StreamSubscription _subscription;

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

    // 监听数据流并在添加新文档时更新UI。
    _subscription = _streamController.stream.listen((document) {
      setState(() {
        // 在此处更新UI。
      });
    });

  }

  @override
  void dispose() {
    // 在小部件被释放时释放数据流控制器。
    _streamController.close(); // 当只使用StreamController时
    _subscription.cancel(); // 当使用StreamSubscription时
    super.dispose();
  }
}

参考:

  1. 一次性读取
  2. 实时变化
  3. StreamBuilder<T> 类
英文:

By definition :

A StreamBuilder is a widget that builds its output based on a stream of data. The stream can be anything that emits data over time, such as a Firestore collection. When the stream emits new data, the StreamBuilder will rebuild its output to reflect the new data.

The StreamBuilder takes two arguments: the stream and a builder function. The builder function will be called whenever the stream emits new data.

The following code shows how to use a StreamBuilder to display a list of Firestore documents:

class _HomePageState extends State&lt;HomePage&gt; {
  Stream&lt;QuerySnapshot&gt; stream; // Declaring Stream 

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

    stream = FirebaseFirestore.instance
        .collection(&#39;documents&#39;)
        .snapshots();
  }

  @override
  void dispose() {
// To close a StreamBuilder, you can use the dispose() method. 
    stream.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(&#39;StreamBuilder with Debounce and Throttle&#39;),
      ),
      body: StreamBuilder&lt;QuerySnapshot&gt;(
        stream: stream,
        builder: (BuildContext context, AsyncSnapshot&lt;QuerySnapshot&gt; snapshot) {
         if (snapshot.hasError) return Text(&#39;Something went wrong&#39;);
        if (snapshot.connectionState == ConnectionState.waiting) return Text(&quot;Loading&quot;);
        List&lt;DocumentSnapshot&gt; documents = snapshot.data!.docs;
        return ListView.builder(
            itemCount: documents.length,
            itemBuilder: (BuildContext context, int index) {
              DocumentSnapshot document = documents[index];
              return ListTile(
                title: Text(document[&#39;title&#39;]),
                subtitle: Text(document[&#39;body&#39;]),
              );
            },
          );
        },
      ),
    );
  }
}
  • When a new document is added to a collection, the StreamBuilder will rebuild the list of documents. This will count as a read from Firestore.

  • But when the user navigates to the new screen the stream will dispose. This is because the stream is only listening for changes to the data while the page is in view. When you go to the next page, the stream will no longer be listening for changes, and you will not be charged for any reads.

  • If you come back to the last page, the stream will be re-created and you will be charged for the reads to rebuild the StreamBuilder.
    So only use a StreamBuilder if you need to display data that is changing over time(realtime changes). If you only need to display data that is static, you can use a ListView or FutureBuilder or other widget that does not require a stream.

TLDR : Do some research on your app. Do you really need real-time updates of documents if you do then handle those StreamBuilders cautiously and make sure to dispose of those streams once the user navigates to the new screen.

Update : When using streamController we also need to dispose it using StreamSubscription.cancel() or calling streamController.cancel() as follows:

class _MyWidgetState extends State&lt;MyWidget&gt; {
  final StreamController&lt;DocumentSnapshot&gt; _streamController =
      StreamController.broadcast(); 

  StreamSubscription _subscription;

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

    // Listen to the stream and update the UI when a new document is added.
    _streamController.stream.listen((document) {
      setState(() {
        // Update the UI here.
      });
    },
    onDispose: () {
      // Dispose of the stream controller when the subscription is disposed.
      _streamController.dispose();
    });

  }

  @override
  void dispose() {
    // Dispose of the stream controller when the widget is disposed.
    _streamController.dispose(); // when used just StreamController
    _subscription.cancel(); // when used StreamSubscription 
    super.dispose();
  }
}

Reference :

  1. One-time Read
  2. Realtime changes
  3. StreamBuilder<T> class

huangapple
  • 本文由 发表于 2023年5月25日 16:14:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/76330199.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定