SetState is causing Futurebuilder to reload the data on every tap - firebase

I am using future builder and stream builder to fetch data from firebase and show them on screen.
I have favourite button as well. when I click on favourite_borderLine iconButton. It fetch data from firebase then change the state to favourite_border iconButton.
It also change the state of every other listview.Builder what I want is just to change the icon state on every click not fetching the whole data from database.
This is the initial state
when I tap on favourite icon, Suppose I tapped on first icon then it start loading.
and then all the icons are changed :(
I just want to change the clicked icon state not all icons and do not want the fetch data on click just change the state of button.Here is code.
class TalentScreen1 extends StatefulWidget {
#override
_TalentScreen1State createState() => _TalentScreen1State();
}
class _TalentScreen1State extends State<TalentScreen1> {
bool toggle = false;
#override
Widget build(BuildContext context) {
return BlocProvider<TalentFavCubit>(
create: (context) => TalentFavCubit(),
child: SafeArea(
child: Scaffold(
body: Padding(
padding: const EdgeInsets.all(20.0),
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Text('Talent Screen 1 home search'),
_retriveAllDocs,
],
),
),
),
),
),
);
}
Widget get _retriveAllDocs => FutureBuilder<QuerySnapshot>(
future: FirebaseRepo.instance.fetchWorkerFormFieldsData(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting)
return CircularProgressIndicator();
if (snapshot.hasError) {
return Text("Something went wrong");
}
if (!snapshot.hasData) {
return Text("Nothing to show");
}
if (snapshot.connectionState == ConnectionState.done) {
final List<DocumentSnapshot> data = snapshot.data.docs;
return theUserInfo(data);
}
return Text("loading");
});
Widget theUserInfo(List<DocumentSnapshot> data) {
return ListView.builder(
shrinkWrap: true,
itemCount: data.length,
itemBuilder: (context, index) {
return FutureBuilder<DocumentSnapshot>(
future: fetch(data[index]['uid']),
builder: (BuildContext context,
AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.hasError) {
return Text("Something went wrong");
}
if (snapshot.connectionState == ConnectionState.done) {
TalentHireFavModel userData = TalentHireFavModel.fromMap(
data[index].data(), snapshot.data.data());
return Card(
child: Column(
children: <Widget>[
Text(userData.name),
Text(userData.categories),
Text(userData.skills),
Text(userData.country),
Text(userData.phoneNo),
Text(userData.hourlyRate),
Text(userData.professionalOverview),
Text(userData.skills),
Text(userData.expert),
Text(userData.createdAt),
IconButton(
icon: toggle
? Icon(Icons.favorite_border)
: Icon(
Icons.favorite,
),
onPressed: () {
setState(() {
// Here we changing the icon.
toggle = !toggle;
});
}),
],
),
);
}
return Container();
});
});
}
//TODO: Implementation Fix Error
Widget _iconButton(uid) {
return StreamBuilder<QuerySnapshot>(
stream: FirebaseRepo.instance.fetchCurrentUserFavourites().snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
var data = snapshot.data.docs;
// print(snapshot.data.get('uid'));
if (snapshot.hasError) {
return Text('Something went wrong');
}
return IconButton(
icon: data.isEmpty == uid
? Icon(Icons.favorite)
: Icon(Icons.favorite_border),
onPressed: () =>
BlocProvider.of<TalentFavCubit>(context).addTalentFav(uid));
},
);
}
Future<DocumentSnapshot> fetch(data) async =>
await FirebaseRepo.instance.fetchWorkerUserData(data);
}

This is your broken line of code:
future: FirebaseRepo.instance.fetchWorkerFormFieldsData(),
The FutureBuilder documentation starts with:
The future must have been obtained earlier, e.g. during State.initState, State.didUpdateWidget, or State.didChangeDependencies. It must not be created during the State.build or StatelessWidget.build method call when constructing the FutureBuilder. If the future is created at the same time as the FutureBuilder, then every time the FutureBuilder's parent is rebuilt, the asynchronous task will be restarted.
A general guideline is to assume that every build method could get called every frame, and to treat omitted calls as an optimization.
And you broke the contract. I have a video that illustrates this in detail. https://www.youtube.com/watch?v=sqE-J8YJnpg
Do what the docs say. TL;DR: Do not create the Future in the parameter to FutureBuilder.

Related

How to retrieve data from Firebase Realtime to the flutter app in a lisview

I am looking to retrieve data stored in Firebase Realtime database and display it in a new page in a lisview, how can I achieve that. So far I can retrieve and print it out in a console terminal.
My code is below:
class BarcodesResultPreviewWidget extends StatelessWidget {
FirebaseDatabase.instance.reference().child('ScannedResults');
body: Column(
children: <Widget>[
previewView,
//printing scanned results
Expanded(
child: ListView.builder(
itemBuilder: (context, position) {
return BarcodeItemWidget(preview.barcodeItems[position]);
},
itemCount: preview.barcodeItems.length,
),
),
FlatButton(
color: Colors.grey,
child: Text('Save',),
onPressed: () {
databaseRef.push().set({
'ScannedItem': preview.barcodeItems
.map((barCodeItem) => barCodeItem.toJson())
.toString(),
});
},
),
To fetch the data into a new page and build listview, try something like this:
return Scaffold(
body: FutureBuilder(
future: databaseRef.once(),
// future: FirebaseDatabase.instance
// .reference()
// .child("ScannedResults")
// .once(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.waiting)
return new Text('Loading....');
if (snapshot.hasError) return new Text('Error: ${snapshot.error}');
List scannedItemsValues = [];
snapshot.data.value.forEach(
(_, values) => scannedItemsValues.add(values["ScannedItem"]));
print(scannedItemsValues);
return ListView.builder(
itemCount: scannedItemsValues.length,
itemBuilder: (BuildContext context, int index) {
// build your listView here
print(scannedItemsValues[index]);
return Text(scannedItemsValues[index]);
},
);
},
),
);

data in StreamBuilder keeps returning null outside of Firebase RTDB get() call

For some reason, my data variable keeps returning null outside of my .get() call. Within the .get() call, when I print data, I get the expected map. Any insight is appreciated! The part of the code that is giving me issues are in asterisks.
class _MessagesScreenState extends State<MessagesScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Expanded(
child: Container(
decoration: BoxDecoration(
color: Colors.white,
),
child: StreamBuilder(
stream: rtdb
.child('messages')
.child(widget.currentUserID)
.onValue,
builder: (context, snapshot) {
final messageList = <BuildMessages>[];
if (snapshot.hasData) {
final users = Map<String, dynamic>.from(
snapshot.data.snapshot.value);
users.forEach((key, value) {
**var data;**
**usersRef.child(key).get().then((value) {
data = new Map<String, dynamic>.from(value.value);
});**
final messageTile = BuildMessages(
name: data['userFirstName'] +
' ' +
data['userLastName'],
picture: data['userImageUrl'],
otherID: data['userID'],
);
;
messageList.add(messageTile);
});
}
return ListView.builder(
padding: EdgeInsets.only(top: 15.0),
itemCount: messageList.length,
itemBuilder: (context, index) {
return messageList[index];
},
);
}),
),
),
],
),
);
}
That is the expected behavior. The usersRef.child(key).get() returns a Future<DataSnapshot?>, so you'll need to wrap it into a FutureBuilder to use its value.
Based on this answer that'd be something like:
FutureBuilder<DataSnapshot?>(
future: usersRef.child(key).get(), // async work
builder: (BuildContext context, AsyncSnapshot<DataSnapshot?> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting: return Text('Loading....');
default:
if (snapshot.hasError)
return Text('Error: ${snapshot.error}');
else
return Text('Result: ${snapshot.data!.value}');
}
},
)

The getter 'uid' was called on null error appears between screens

I am getting an error
The getter 'uid' was called on null.
Receiver: null
Tried calling: uid
this is the code I am using, the problem is auth.currentUser.uid which is returning null when first called, from what I understand the auth.currentUser is null at first then it return the current user
but I don't get how to handle this situation
the error show on the emulator when between the login screen and home screen after I log in, it appear for a second then it's gone and the home screen appears normally
class HomePage extends StatelessWidget {
final FirebaseAuth auth = FirebaseAuth.instance;
static int count;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
GestureDetector(
child: Icon(Icons.logout),
onTap: () async {
await auth.signOut();
Navigator.of(context).push(MaterialPageRoute(builder: (context) {
return LoginPage();
}));
},
),
],
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
print('add button pressed');
Navigator.of(context).push(MaterialPageRoute(builder: (context) {
return NewNote();
}));
},
),
body: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Notes',
style: TextStyle(
fontSize: 18,
),
),
// Text(count != null ? count.toString() : '0'),
NotesCount(
auth1: auth,
),
Text(auth.currentUser.uid != null ? auth.currentUser.uid : 'No user id'),
SizedBox(
height: 20,
),
NotesGrid(
auth1: auth,
),
],
),
),
),
);
}
}
class NotesCount extends StatelessWidget {
final FirebaseAuth auth1;
NotesCount({this.auth1});
#override
Widget build(BuildContext context) {
CollectionReference notes = FirebaseFirestore.instance.collection('users').doc(auth1.currentUser.uid).collection('notes');
return StreamBuilder<QuerySnapshot>(
stream: notes.snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
return new Text(snapshot.data.docs.length.toString());
},
);
}
}
class NotesGrid extends StatelessWidget {
final FirebaseAuth auth1;
NotesGrid({this.auth1});
#override
Widget build(BuildContext context) {
CollectionReference notes = FirebaseFirestore.instance.collection('users').doc(auth1.currentUser.uid).collection('notes');
return StreamBuilder(
stream: notes.snapshots(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (!snapshot.hasData) {
return Center(child: const Text('Loading events...'));
}
if (snapshot.hasError) {
return Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
return GridView.builder(
shrinkWrap: true,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
itemBuilder: (BuildContext context, int index) {
return NoteCard(
note: Note(
title: snapshot.data.documents[index]['title'],
content: snapshot.data.documents[index]['content'],
datetime: snapshot.data.documents[index]['datetime'].toDate(),
id: snapshot.data.documents[index].documentID,
),
);
},
itemCount: snapshot.data.documents.length,
);
},
);
}
}
It's not a good idea to use currentUser as your primary way to find the signed in user account. It will always be null when the app is first launched. The previously signed in user object doesn't become available until some time later. It's better to follow the instructions in the documentation and set up an auth state listener so you can find out when the user object first becomes available.
FirebaseAuth.instance
.authStateChanges()
.listen((User user) {
if (user == null) {
print('User is currently signed out!');
} else {
print('User is signed in!');
}
});
Use this listener to respond to changes in auth state and trigger an update of your UI accordingly.
I tried adding if else before accessing to current user and the error is gone and the user data shows normally after loading it
Text(auth.currentUser != null ? auth.currentUser.uid : 'Loading...')

Firestore.instance.collection(collectio_name).document(document_name).get(), does not get any value from firebase in Flutter

So here I am trying to fetch the data from firebase by using DocumentSnapshot.
I want to print the value of document['display_name']
Collection = user_data
document = 3vIf92LIJQ7pu7MpUwH1
display_name = element of document.
Output: 'Error has Occured' on screen
class _HomeViewState extends State<HomeView> {
Future<DocumentSnapshot> getDocument() async {
return Firestore.instance
.collection('user_data')
.document('3vIf92LIJQ7pu7MpUwH1')
.get();
}
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: FutureBuilder(
future: getDocument(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasError) return Text('Error has occured');
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
}
if (snapshot.hasData) {
return Column(
children: <Widget>[
Text(snapshot.data['display_name']),
],
);
}
You probably have an error because of your firestore security rules. Also, you will need to access snapshot.data.data['display_name'] instread of snapshot.data['display_name'] to get the value from firestore.
if (snapshot.hasData) {
return Column(
children: <Widget>[
Text(snapshot.data.data['display_name']),
],
);
}
If hasError is true, you'll want to print snapshot.error to see what the actual problem is:
if (snapshot.hasError) return Text('Error has occurred: ${snapshot.error}');

FLUTTER: How to use navigator in streambuilder?

I am trying to navigate inside a streambuilder but I have this error:"setState() or markNeedsBuild() called during build.". If I call navigate inside an onpressed button it works but not by just use it inside a condition. I am stuck. There is some code to show you.
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder(
stream:
Firestore.instance.collection('rooms').document(pinid).snapshots(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
if ((snapshot.data['Votes'][0] + snapshot.data['Votes'][1]) >=
snapshot.data['joueurs']) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Results(),
));
}
}
return Center(
child: Text('VOUS AVEZ VOTE'),
);
},
),
);
}
That's because Flutter is triggering a frame build when you are trying to navigate to another screen, thus, that's not possible.
You can schedule a post frame callback so you can navigate as soon as Flutter is done with tree rebuilding for that widget.
import 'package:flutter/foundation.dart';
WidgetsBinding.instance.addPostFrameCallback(
(_) => Navigator.push(context,
MaterialPageRoute(
builder: (context) => Results(),
),
),
);
If navigation is the only thing happening on a button press, I wouldn't use a Bloc at all because Navigation is not business logic and should be done by the UI layer.
If you have business logic on a button press and need to navigate based on some dynamic information then I would do the navigation again in the presentation layer (widget) in response to a success state like below. You can also change navigation logic as per your requirement.
Widget loginButton(LoginBloc loginBloc) =>
StreamBuilder<List<UserLoginResultElement>>(
stream: loginBloc.loginStream,
builder:
(context, AsyncSnapshot<List<UserLoginResultElement>> snapshot) {
print(snapshot.connectionState);
Widget children;
if (snapshot.hasError) {
children = Padding(
padding: const EdgeInsets.only(top: 16),
child: Text('Error: ${snapshot.error}'),
);
} else {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
case ConnectionState.done:
case ConnectionState.active:
children = BlockButtonWidget(
text: Text(
"LOGIN",
style: TextStyle(color: Theme.of(context).primaryColor),
),
color: Theme.of(context).accentColor,
onPressed: () async {
try {
bloc.submit(_userNameController.value.text,
_passwordController.value.text, context);
} catch (ex) {
print(ex.toString());
}
},
);
break;
}
}
if (snapshot.data != null && snapshot.hasData) {
if (snapshot.data[0].code == "1") {
SchedulerBinding.instance.addPostFrameCallback((_) {
Navigator.pushReplacementNamed(context, "/HomeScreen");
});
} else {
print(Login Failed');
}
}
return children;
});

Resources