Search code examples
flutterdartgoogle-cloud-firestoregeofire

Flutter Snapshot of Streambuilder has no data after condition is met


I have a class Home with a PageView as body and a BottomNavigationBar. In this class the current user and the current location of the user is loaded. When the user and the location is known, a global variable is set to true

On the first tab icon of the BottomNavigationBar there is a feed of nearby locations coded in class Feed

Now the issue. When I start the app for the first time or make a hot reload geoQuery() returns the circular spinner. When the current user is loaded it returns the text "No Data" instead of showing the events. The user needs to change the tab of BottomNavigationBar from feed to something else and back to feed to refresh the streambuilder. After that it works as expected.

When I use the streambuilder without the condition (currentLocationloaded && currentUserloaded == true) it works as expected but sometimes it throws an error as the user is not loaded fast enough.

What can I do to get it work with condition?

Update

Workflow logged in: RootPage -> Logged in? -> Home

RootPage

enum AuthStatus {
  NOT_DETERMINED,
  NOT_LOGGED_IN,
  LOGGED_IN,
}

class RootPage extends StatefulWidget {
  RootPage({this.auth});

  final BaseAuth auth;

  @override
  State<StatefulWidget> createState() => new _RootPageState();
}

class _RootPageState extends State<RootPage> {
  AuthStatus authStatus = AuthStatus.NOT_DETERMINED;
  String _userID = "";

  @override
  void initState() {
    super.initState();
    widget.auth.getCurrentUser().then((user) {
      setState(() {
        if (user != null) {
          _userID = user?.uid;
        }
        authStatus =
            user?.uid == null ? AuthStatus.NOT_LOGGED_IN : AuthStatus.LOGGED_IN;
      });
    });
  }

  void loginCallback() {
    widget.auth.getCurrentUser().then((user) {
      setState(() {
        _userID = user.uid.toString();
      });
    });
    setState(() {
      authStatus = AuthStatus.LOGGED_IN;
    });
  }

  void logoutCallback() {
    setState(() {
      authStatus = AuthStatus.NOT_LOGGED_IN;
      _userID = "";
    });
  }

  Widget buildWaitingScreen() {
    return Scaffold(
      body: Container(
        alignment: Alignment.center,
        child: CircularProgressIndicator(),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    switch (authStatus) {
      case AuthStatus.NOT_DETERMINED:
        return buildWaitingScreen();
        break;
      case AuthStatus.NOT_LOGGED_IN:
        return new StartPage(
          auth: widget.auth,
          loginCallback: loginCallback,
        );
        break;
      case AuthStatus.LOGGED_IN:
        if (_userID.length > 0 && _userID != null) {
          return new Home(
            userID: _userID,
            auth: widget.auth,
            logoutCallback: logoutCallback,
          );
        } else
          return buildWaitingScreen();
        break;
      default:
        return buildWaitingScreen();
    }
  }
}

Home

User currentUser;
bool currentUserloaded = false;
bool currentLocationloaded = false;

class Home extends StatefulWidget {
  final BaseAuth auth;
  final VoidCallback logoutCallback;
  final String userID;

  const Home({Key key, this.auth, this.logoutCallback, this.userID})
      : super(key: key);

  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State<Home> {
  final _scaffoldKey = GlobalKey<ScaffoldState>();
  PageController pageController;
  int pageIndex = 0;
  double longitude;
  double latitude;

  //INIT
  @override
  void initState() {
    super.initState();
    loadCurrentUser();
    getCurrentLocation();
    pageController = PageController();
  }


  //LOAD current user
  loadCurrentUser() async {
    print("Current User ${widget.userID}");
    DocumentSnapshot doc = await userRef.document(widget.userID).get();
    currentUser = User.fromDocument(doc);
    setState(() {
      currentUserloaded = true;
      print("User loaded $currentUserloaded");
    });
  }

  //get current location
  getCurrentLocation() async {
    var currentLocationCoordinates = await Geolocator()
        .getCurrentPosition(desiredAccuracy: LocationAccuracy.high);
    List<Placemark> place = await Geolocator().placemarkFromCoordinates(
        currentLocationCoordinates.latitude,
        currentLocationCoordinates.longitude);
    latitude = currentLocationCoordinates.latitude;
    longitude = currentLocationCoordinates.longitude;
    setState(() {
      currentLocationloaded = true;
      print("Got location $currentLocationloaded");
    });
  }

  //DISPOSE
  @override
  void dispose() {
    pageController.dispose();
    super.dispose();
  }

  //Pageview
  onPageChanged(int pageIndex) {
    setState(() {
      this.pageIndex = pageIndex;
    });
  }

  //On Tap of ButtomTabbar => Jump to next Page
  onTap(int pageIndex) {
    if (currentUserloaded && currentLocationloaded) {
      pageController.jumpToPage(pageIndex);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: _scaffoldKey,
      body: PageView(
        children: <Widget>[
          Feed(userID: widget.userID, latitude: latitude, longitude: longitude),
          SearchView(),
          ChatHome(),
          Profile(
              uid: currentUser?.uid,
              auth: widget.auth,
              logoutCallback: widget.logoutCallback),
        ],
        controller: pageController,
        onPageChanged: onPageChanged,
        physics: NeverScrollableScrollPhysics(),
      ),
      bottomNavigationBar: CupertinoTabBar(
          currentIndex: pageIndex,
          inactiveColor: Colors.white,
          backgroundColor: Colors.blue,
          activeColor: Colors.orange,
          onTap: onTap,
          items: [
            BottomNavigationBarItem(
              icon: Icon(Icons.home, size: 20),
              title: Text("Home"),
            ),
            BottomNavigationBarItem(
              icon: Icon(Icons.search, size: 20),
              title: Text("Search"),
            ),
            BottomNavigationBarItem(
              icon: Icon(Icons.chat, size: 20),
              title: Text("chat"),
            ),
            BottomNavigationBarItem(
              icon: Icon(Icons.profil, size: 20),
              title: Text("Profil"),
            ),
          ]),
    );
  }
}

Feed

class Feed extends StatefulWidget {
  final String userID;
  final double latitude;
  final double longitude;

  const Feed({Key key, this.userID, this.latitude, this.longitude})
      : super(key: key);

  @override
  _FeedState createState() => _FeedState();
}

class _FeedState extends State<Feed> {
  final _scaffoldKey = GlobalKey<ScaffoldState>();
  List<Event> events = [];

  var radius = BehaviorSubject<double>.seeded(50.0);
  Stream<List<DocumentSnapshot>> stream;
  Geoflutterfire geo;

  @override
  void initState() {
    super.initState();
    geo = Geoflutterfire();
    GeoFirePoint center = geo.point(
        latitude: widget.latitude,
        longitude: widget
            .longitude); 
    stream = radius.switchMap((rad) {
      var collectionReference =
          eventRef.where("event", isEqualTo: "festival");
      return geo.collection(collectionRef: collectionReference).within(
          center: center, radius: rad, field: 'position', strictMode: true);
    });
  }

  //GEOQUERY

  Widget geoQuery() {
    if (currentLocationloaded && currentUserloaded) {
      return Column(
        children: <Widget>[
          StreamBuilder(
            stream: stream,
            builder: (BuildContext context,
                AsyncSnapshot<List<DocumentSnapshot>> snapshot) {
              if (!snapshot.hasData) {
                Text("No data");
              }
              events =
                  snapshot.data.map((doc) => Event.fromDocument(doc)).toList();
              events.sort((a, b) {
                var aDate = a.timestamp;
                var bDate = b.timestamp;
                return aDate.compareTo(bDate);
              });
              if (events.isEmpty) {
                return Text("No events");
              }
              return Flexible(
                child: ListView.builder(
                  itemCount: snapshot.data.length,
                  itemBuilder: (context, index) {
                    return buildEvent(index);
                  },
                ),
              );
            },
          )
        ],
      );
    } else {
      return circularProgress();
    }
  }


  @override
  Widget build(BuildContext context) {
    SizeConfig().init(context);
    return Scaffold(
        key: _scaffoldKey,
        appBar: AppBar(
          centerTitle: true,
          title: Text("Feed"),
          backgroundColor: Colors.blue,
        ),
        body: geoQuery(),
        );
  }
}

Update 2

If I use hard coded latitude and longitude for GeoFirePoint center = geo.point(latitude: 37.773972, longitude: -122.431297); it works! Looks like an issue with passing the current user location. Any suggestions?


Solution

  • The issue was that the location of current user was not passed on time. Just put

    GeoFirePoint center = geo.point(
            latitude: widget.latitude,
            longitude: widget
                .longitude); 
        stream = radius.switchMap((rad) {
          var collectionReference =
              eventRef.where("event", isEqualTo: "festival");
          return geo.collection(collectionRef: collectionReference).within(
              center: center, radius: rad, field: 'position', strictMode: true);
        });
    

    from initState to geoQuery()