StreamBuilder ListView возвращает пустой список из Firestore при первой загрузке - PullRequest
1 голос
/ 26 февраля 2020

В приложении, которое я создаю в рамках процесса регистрации, для каждого документа «Пользователь» создается подколлекция, содержащая до 100 документов.

Я пытаюсь показать эти суб документы в формате StreamBuilder.

У меня есть любопытная ошибка, которую я не могу устранить. StreamBuilder не отображает данные, когда пользователь впервые просматривает их. Вместо этого он возвращает пустой список.

Я вижу, что документы были правильно созданы в под-коллекции. Данные устанавливаются на странице перед страницей с StreamBuilder. Даже если бы были задержки, я бы подумал, что новые документы только начали бы появляться в StreamBuilder. Представление консоли Firebase

StreamBuilder отображает ожидаемых данных при перезапуске приложения или при выходе пользователя из системы и повторном входе в систему.

Ниже приведен код, который я использую:

Stream<QuerySnapshot> provideActivityStream() {
    return Firestore.instance
        .collection("users")
        .document(widget.userId)
        .collection('activities')
        .orderBy('startDate', descending: true)     
        .snapshots();
  }
...
Widget activityStream() {
  return Container(
      padding: const EdgeInsets.all(20.0),
      child: StreamBuilder<QuerySnapshot>(
      stream: provideActivityStream(),
      builder: (BuildContext context,
          AsyncSnapshot<QuerySnapshot> snapshot) {
        if (snapshot.hasError)
          return new Text('Error: ${snapshot.error}');
        if(snapshot.data == null) {
          return CircularProgressIndicator();
        }
        if(snapshot.data.documents.length < 1) {
          return new Text(
            snapshot.data.documents.toString()
            );
        }
        if (snapshot != null) {
          print('$currentUser.userId');
        }
        if (
          snapshot.hasData && snapshot.data.documents.length > 0
          ) {
          print("I have documents");
          return new ListView(
              children: snapshot.data.documents.map((
                  DocumentSnapshot document) {
                  return new PointCard(
                    title: document['title'],
                    type: document['type'],
                  );
                }).toList(),
            );
        }
      } 
    )
  );
}

Редактирование: добавление основной сборки согласно запросу на комментарий

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 3,
      child: Scaffold(
        appBar: AppBar(
          title: Text("Home"),
          actions: <Widget>[
          ],
          bottom: TabBar(
            tabs: [
              Text("Account"),
              Text("Activity"),
              Text("Links"),
            ],
          ),
        ),
        body: TabBarView(
          children: [
            accountStream(),
            activityStream(),
            linksStream()
            ]
          )
        ),
      );
    }
  }

Попытки, которые я сделал для решить

Сначала я думал, что это ошибка соединения, поэтому создал серию дел на основе switch (snapshot.connectionState). Я вижу, что ConnectionState.active = true, поэтому мысль о том, что добавление нового документа в Firestore может иметь эффект, но ничего не дает.

Я попробовал следующее, чтобы сделать первоначальный конструктор потока асинхронным. Не удается загрузить какие-либо данные.

Stream<QuerySnapshot> provideActivityStream() async* {
    await Firestore.instance
        .collection("users")
        .document(widget.userId)
        .collection('activities')
        .orderBy('startDate', descending: true)     
        .snapshots();
  }

Я пытался удалить элемент tabcontroller - например, просто иметь одну страницу - но это тоже не помогает.

Я пробовал доступ к данным с использованием DocumentSnapshot и QuerySnapshot. У меня проблема с обоими.

Я уверен, что это очень просто, но застрял на этом. Любая помощь с благодарностью. Спасибо!

Ответы [ 3 ]

0 голосов
/ 26 февраля 2020

Мы также можем попробовать следующее

 QuerySnapshot qs;
 Stream<QuerySnapshot> provideActivityStream() async{
    qs= await Firestore.instance
             .collection("users")
             .document(widget.userId)
             .collection('activities')
            .orderBy('startDate', descending: true)     
            .snapshots();

      return qs;
  }//this should work

, но в соответствии с основами streambuilder, если вышеприведенный фрагмент не сработал, есть еще

     QuerySnapshot qs;
 Stream<QuerySnapshot> provideActivityStream() async* {
    qs= await Firestore.instance
             .collection("users")
             .document(widget.userId)
             .collection('activities')
            .orderBy('startDate', descending: true)     
            .snapshots();

      yield qs;
  }//give this a try
0 голосов
/ 03 марта 2020

tl; dr

  • Необходимо использовать setState, чтобы Firebase currentUser uid был доступен для виджетов
  • Необходимо использовать AutomaticKeepAliveClientMixin для правильной работы с TabBar
  • Я думаю, что использование пакета Provider может быть лучшим способом сохранения состояния пользователя, но не при решении этой проблемы

Объяснение

Мой код получает currentUser uid с будущим. Согласно SO-ответ здесь , это проблема, потому что все виджеты будут созданы до того, как FirebaseAuth сможет вернуть uid. Первоначально я пытался использовать initState, чтобы получить uid, но у него точно такая же синхронная проблема. Вызов setState из функции для вызова FirebaseAuth.instance позволил обновить дерево виджетов.

Я помещаю этот виджет в виджет TabBar. Насколько я понимаю, каждый раз, когда вкладка удаляется из поля зрения, она удаляется и восстанавливается при возврате. Это вызывало дальнейшие государственные проблемы. Документы API для миксина AutomaticKeepAlive здесь

Код решения

С добавленными комментариями в надежде, что они полезны для чьего-либо понимания (или кто-то может исправить мое недоразумение)

activitylist.dart

class ActivityList extends StatefulWidget {
// Need a stateful widget to use initState and setState later
  @override
  _ActivityListState createState() => _ActivityListState();
}

class _ActivityListState extends State<ActivityList> 
  with AutomaticKeepAliveClientMixin<ActivityList>{
  // `with AutomaticKeepAliveClientMixin` added for TabBar state issues

  @override
  bool get wantKeepAlive => true;
  // above override required for mixin

  final databaseReference = Firestore.instance;

  @override
    initState() {
      this.getCurrentUser(); // call the void getCurrentUser function
      super.initState();
  }

  FirebaseUser currentUser;

  void getCurrentUser() async {
    currentUser = await FirebaseAuth.instance.currentUser();
    setState(() {
      currentUser.uid;
    });
    // calling setState allows widgets to access uid and access stream
  }

  Stream<QuerySnapshot> provideActivityStream() async* {
    yield* Firestore.instance
        .collection("users")
        .document(currentUser.uid)
        .collection('activities')
        .orderBy('startDate', descending: true)     
        .snapshots();
  }

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return Container(
                  padding: const EdgeInsets.all(20.0),
                  child: StreamBuilder<QuerySnapshot>(
                  stream: provideActivityStream(),
                  builder: (BuildContext context,
                  AsyncSnapshot<QuerySnapshot> snapshot) {
                    if(snapshot.hasError) return CircularProgressIndicator();
                    if(snapshot.data == null) return CircularProgressIndicator();
                    else if(snapshot.data !=null) {
                      return new ListView(
                          children: snapshot.data.documents.map((
                              DocumentSnapshot document) {
                            return new ActivityCard(
                              title: document['title'],
                              type: document['type'],
                              startDateLocal: document['startDateLocal'],
                            );
                          }).toList(),
                        );
                    }
                  },
                )
            );
  }
}

home.dart

...
@override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 3,
      child: Scaffold(
        appBar: AppBar(
          title: Text("Home"),
          actions: <Widget>[
          ],
          bottom: TabBar(
            tabs: [
              Text("Account"),
              Text("Activity"),
              Text("Links"),
            ],
          ),
        ),
        body: TabBarView(
          children: [
            accountStream(),
            ActivityList(), // now calling a stateful widget in an external file
            linksStream()
            ]
          )
        ),
      );
    }
  }
0 голосов
/ 26 февраля 2020

Он не извлекается с использованием какого-либо из этих снимков запроса и снимка документа

Сначала необходимо выполнить запрос с использованием Querysnapshot, а затем извлечь информацию в Documentsnapshot. Да, для загрузки файла может потребоваться несколько секунд. документ правильное решение, что вы должны asyn c и ждать функции

вместо streamBuilder, я предлагаю вам использовать прямой снимок

мы можем загрузить снимок документа в начальном состоянии StateFullWidget работает когда ваш класс является statefullWidget и проблема также связана с состоянием

...

 bool isLoading;
 List<DocumentSnapshot> activity =[];
 QuerySnapshot user;
 @override
 void initState() {
  print("in init state");
  super.initState();
  getDocument();
 ``    } 
  getDocument() async{

 setState(() {
   isLoading = true;
 });
 user= await Firestore.instance
    .collection("users")
    .document(widget.userId)
    .collection('activities')
    .orderBy('startDate', descending: true)     
    .getDocuments();

  activity.isEmpty ? activity.addAll(user.documents) : null;

    setState(() {
  isLoading = false;
       });

     }

//inside  Widget build(BuildContext context) { return  Scaffold( in your body 
//section of scaffold in the cointainer
Container(
padding: const EdgeInsets.all(20.0),
child: isLoading ?
         CircularProgressIndicator(),
        :ListView.builder(

                          itemCount: global.category.length,
                          itemBuilder: (context, index) {
                            return  PointCard(
                                    title: activity[index].data['title'],
                                      type: activity[index].data['type'],
                                    );//pointcard
                             }
                            ),//builder
                     ),//container
...