Reputation: 564
I have screens in a TabBarView
that share data so I decided to wrap the TabBarView
in a FutureBuilder
and pass the data from that into both screens. The Future
is called fetchUsersAndPosts()
and it is meant to return [[*users], [*posts*]]
but the snapshot
of the FutureBuilder
looks like this [[], []]
. In the Future
I print both lists before returning them so I know they aren't empty. Why are they returning as empty? Additionally, the print statements are continuous which tells me the methods are repeating (I think the FutureBuilder
is calling the Future
endlessly) and I went to check Firebase and it shows 700 reads today because of the repeated functions. How do I fix that?
Future
:
Future<List<QueryDocumentSnapshot>> getUserDocs() async {
List<QueryDocumentSnapshot<Object?>> userDocs = [];
try {
final userRef = FirebaseFirestore.instance.collection('users');
final QuerySnapshot result = await userRef.get();
userDocs = result.docs;
} on FirebaseException catch (e) {
print(e.message);
}
print('length: ${userDocs.length}');
return Future.value(userDocs);
}
Future<List<QueryDocumentSnapshot>> getPostDocs(List<QueryDocumentSnapshot> userDocs) async {
List<QueryDocumentSnapshot> postsDocs = [];
for (final resultValue in userDocs) {
try {
final postsRef = FirebaseFirestore.instance
.collection('users')
.doc(resultValue.id)
.collection('posts');
final QuerySnapshot postsResult = await postsRef.get();
final postDocs = postsResult.docs;
for (var post in postDocs) {
postsDocs.add(post);
}
} on FirebaseException catch (e) {
print(e.message);
}
}
return Future.value(postsDocs);
}
Future<List> fetchUsersAndPosts() async {
List<QueryDocumentSnapshot> userDocs = await getUserDocs();
List<QueryDocumentSnapshot> postsDocs = await getPostDocs(userDocs);
print('userDocs: $userDocs');
print('postsDocs: $postsDocs');
List<UserSearchResult> usersList = [];
List<Post> postsList = [];
for (var postDoc in postsDocs) {
final post = Post.fromJson(postDoc.data() as Map<String, dynamic>);
if (!postsList.contains(post)) {
postsList.add(post);
}
}
for (final userDoc in userDocs) {
final profileInfo =
ProfileInfoObject.fromJson(userDoc.data() as Map<String, dynamic>);
Profile profile = Profile(
profileInfo, postsList.where((p) => p.uid == userDoc.id).toList());
UserSearchResult user = (UserSearchResult(profile, userDoc.id));
if (usersList.where((u) => u.uid == user.uid).toList().isEmpty) {
usersList.add(user);
print('usersList: $usersList');
}
}
print('usersList: $usersList');
print('postsList: $postsList');
postsList.sort((a, b) {
return b.date.compareTo(a.date);
});
return [usersList, postsList];
}
FutureBuilder
:
class FloatingTabBarView extends StatefulWidget {
const FloatingTabBarView({Key? key}) : super(key: key);
@override
State<FloatingTabBarView> createState() => _FloatingTabBarViewState();
}
class _FloatingTabBarViewState extends State<FloatingTabBarView>
with TickerProviderStateMixin {
late AnimationController _animationController;
late Animation _animation;
List<Post> currentUserPosts = [];
Profile currentProfile = Profile(
ProfileInfoObject(
FirebaseAuth.instance.currentUser!.photoURL!,
FirebaseAuth.instance.currentUser!.displayName!,
),
[],
);
void fetchCurrentUserPosts() async {
final postsRef = FirebaseFirestore.instance
.collection('users')
.doc(FirebaseAuth.instance.currentUser!.uid)
.collection('posts');
final postsResult = await postsRef.get();
final postDocs = postsResult.docs.asMap();
List<Post> posts = [];
postDocs.forEach((index, value) {
final post = Post.fromJson(value.data());
posts.add(post);
setState(() {
currentUserPosts.add(post);
currentProfile = Profile(
ProfileInfoObject(
FirebaseAuth.instance.currentUser!.photoURL!,
FirebaseAuth.instance.currentUser!.displayName!,
),
currentUserPosts,
);
});
});
}
final DataProvider dataProvider = DataProvider();
late Future<List> _futureLists;
@override
void initState() {
fetchCurrentUserPosts();
_animationController =
AnimationController(vsync: this, duration: const Duration(seconds: 2));
_animationController.repeat(reverse: true);
_animation = Tween(begin: 2.0, end: 15.0).animate(_animationController)
..addListener(() {
setState(() {});
});
_futureLists = dataProvider.fetchUsersAndPosts();
super.initState();
}
@override
void dispose() {
super.dispose();
}
Widget floatingTabBarPageView() {
List<TabItem> tabList(List<UserSearchResult> users, List<Post> posts) {
List<TabItem> list = [
TabItem(
icon: const Icon(Icons.home_outlined, size: 35),
selectedIcon: const Icon(Icons.home_rounded, size: 35),
label: "Home",
tabWidget: HomeScreen(usersMap: users, postsList: posts),
),
TabItem(
icon: const Icon(Icons.travel_explore_outlined, size: 35),
selectedIcon: const Icon(Icons.travel_explore, size: 35),
label: "Explore",
tabWidget: ExploreScreen(usersMap: users, postsList: posts),
),
if (!kIsWeb)
if (Platform.isIOS || Platform.isAndroid)
const TabItem(
icon: Icon(Icons.post_add_outlined, size: 35),
selectedIcon: Icon(Icons.post_add, size: 35),
label: "Post",
tabWidget: CreatePostScreen(),
),
TabItem(
icon: const Icon(Icons.account_circle_outlined, size: 35),
selectedIcon: const Icon(Icons.account_circle_rounded, size: 35),
label: "Account",
tabWidget: ProfileScreen(
uid: FirebaseAuth.instance.currentUser!.uid,
profile: currentProfile,
),
),
];
return list;
}
return FutureBuilder(
future: _futureLists,
builder: (context, AsyncSnapshot<List<dynamic>> snapshot) {
List<UserSearchResult> users = [];
if (snapshot.data?[0] != null) {
users = snapshot.data![0];
}
List<Post> posts = [];
if (snapshot.data?[1] != null) {
posts = snapshot.data![1];
}
print('Snapshots: $users, $posts');
if (!snapshot.hasData) {
return Scaffold(
body: Center(
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: const Color.fromARGB(255, 27, 28, 30),
boxShadow: [
BoxShadow(
color: const Color.fromARGB(255, 178, 215, 223),
blurRadius: _animation.value,
spreadRadius: _animation.value,
),
],
),
child: Image.asset('assets/logo.png'),
),
),
);
} else {
return FloatingTabBar(
activeColor: const Color(0xFF7157A0),
inactiveColor: const Color(0xFFadc0ff),
tabItemList: tabList(users, posts),
isFloating: true,
titleTapNavigationRouteWidget: HomeScreen(
usersMap: users,
postsList: posts,
),
title: 'GLOBE',
showTabLabelsForFloating: true,
);
}
});
}
@override
Widget build(BuildContext context) {
return floatingTabBarPageView();
}
}
Upvotes: 1
Views: 274
Reputation: 837
It's the animation! AnimationController triggers the build method, which involves FutureBuilder as well, so everytime a new FutureBuilder is created it invokes a new call to Firebase.
You have to separate the animation logic and data fetching logic into different widget trees
My recommendation is to refactor this into its own widget and place the animation controller and all inside it, as this is the only widget using the _animtion.value
return Scaffold(
body: Center(
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: const Color.fromARGB(255, 27, 28, 30),
boxShadow: [
BoxShadow(
color: const Color.fromARGB(255, 178, 215, 223),
blurRadius: _animation.value,
spreadRadius: _animation.value,
),
],
),
child: Image.asset('assets/logo.png'),
),
),
);
Upvotes: 2