You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Flutter中使用StreamBuilder从Firestore获取数据?

Fixing StreamBuilder to Fetch Firestore Data in Flutter

Hey there! I see you're trying to use StreamBuilder to pull data from Firestore and display it with your Lost_Card widget. Let's walk through fixing your code step by step to make it work properly.

First, Let's Address the Issues in Your Original Code

  • Your map() usage is incorrect—you need to iterate over each document in the snapshot and extract fields properly.
  • You're trying to return a single Lost_Card, but your widget is built to display a grid, so we need to generate multiple cards from all Firestore documents.
  • You aren't handling snapshot states like loading or errors, which is critical for a smooth user experience.

Modified Complete Code

Here's the updated code that properly fetches and displays Firestore data:

import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';

class LostPage extends StatefulWidget {
  @override
  _LostPage createState() => new _LostPage();
}

class _LostPage extends State<LostPage> {
  //temp vars
  final String firebasetest = "Test";
  //firestore vars
  final DocumentReference documentReference = Firestore.instance.document("Items/Rusty");

  //CRUD operations
  void _add() {
    Map<String, String> data = <String, String>{
      "name": firebasetest,
      "desc": "Flutter Developer",
      "url": "https://i.imgur.com/FtaGNck.jpg", // Added URL for testing
      "noro": "old" // Added noro field for card display
    };
    documentReference.setData(data).whenComplete(() {
      print("Document Added");
    }).catchError((e) => print(e));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: StreamBuilder(
        stream: Firestore.instance.collection('Items').snapshots(),
        builder: (_, AsyncSnapshot<QuerySnapshot> snapshot) {
          // Handle loading state
          if (snapshot.connectionState == ConnectionState.waiting) {
            return const Center(child: CircularProgressIndicator());
          }
          // Handle error state
          if (snapshot.hasError) {
            return Center(child: Text('Error: ${snapshot.error}'));
          }
          // Handle empty state
          if (!snapshot.hasData || snapshot.data.documents.isEmpty) {
            return const Center(child: Text('No items found'));
          }

          // Extract documents from snapshot
          final items = snapshot.data.documents;

          // Return GridView with Lost_Card for each Firestore item
          return GridView.count(
            shrinkWrap: true,
            crossAxisCount: 2,
            children: items.map((doc) {
              // Extract fields from Firestore document (add defaults for safety)
              final url = doc['url'] ?? '';
              final title = doc['name'] ?? 'No Title';
              final noro = doc['noro'] ?? 'New';

              return Lost_Card(
                headImageAssetPath: url,
                title: title,
                noro: noro,
              );
            }).toList(),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.add),
        onPressed: _add,
      ),
    );
  }
}

class Lost_Card extends StatelessWidget {
  //All the card variables
  final String headImageAssetPath;
  final IconData icon;
  final Color iconBackgroundColor;
  final String title;
  final String noro;
  final int price;
  final ShapeBorder shape;

  Lost_Card({
    this.headImageAssetPath,
    this.icon,
    this.iconBackgroundColor,
    this.title,
    this.noro,
    this.price,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Expanded(
            child: Stack(
              fit: StackFit.expand,
              children: <Widget>[
                Container(
                  height: MediaQuery.of(context).size.height / 4,
                  width: MediaQuery.of(context).size.height / 2.5,
                  child: DecoratedBox(
                    decoration: BoxDecoration(
                      image: DecorationImage(
                        image: NetworkImage(headImageAssetPath),
                        fit: BoxFit.cover,
                      ),
                    ),
                  ),
                ),
                Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: Align(
                    alignment: FractionalOffset.topLeft,
                    child: CircleAvatar(
                      backgroundColor: Colors.redAccent,
                      radius: 15.0,
                      child: Text(
                        noro,
                        textScaleFactor: 0.5,
                      ),
                    ),
                  ),
                ),
                Align(
                  alignment: FractionalOffset.topRight,
                  child: Container(
                    color: Colors.blueAccent,
                    height: 35.0,
                    width: 35.0,
                    child: const Center(
                      child: Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: <Widget>[
                          Icon(Icons.account_circle),
                          Text(
                            "1P",
                            textScaleFactor: 0.5,
                          ),
                        ],
                      ),
                    ),
                  ),
                ),
              ],
            ),
          ),
          Center(
            child: Container(
              padding: const EdgeInsets.all(8.0),
              alignment: FractionalOffset.bottomCenter,
              child: Text(
                title,
                style: const TextStyle(
                  fontWeight: FontWeight.w700,
                ),
              ),
            ),
          ),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: <Widget>[
              FlatButton(
                child: Text(
                  "Add To Cart",
                  style: TextStyle(color: Colors.grey[500]),
                ),
                onPressed: () => null,
              ),
              const Text(
                "\$5",
                style: TextStyle(color: Colors.grey[500]),
              )
            ],
          )
        ],
      ),
    );
  }
}

Key Changes Explained

  1. Snapshot State Handling: Added checks for loading, error, and empty states to keep the UI responsive and informative.
  2. Document Iteration: Used items.map() to loop through each Firestore document, extract fields like url, name, and noro, and create a Lost_Card for each entry.
  3. GridView Structure: Moved the GridView logic from Lost_Card to the StreamBuilder's builder function—this lets us generate multiple cards dynamically from Firestore data.
  4. Field Safety: Used ?? to provide default values if a field is missing in a Firestore document, preventing unexpected crashes.

Firestore Database Structure

Firestore Database Structure

Expected App Output

App Running Screenshot

内容的提问来源于stack exchange,提问作者JackyBoi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 07:08:13