Correcting Null Safety issue with StreamBuilder in Flutter app - flutter

I have an null safety issue with my StreamBuilder in my flutter app.
On the open bracket "{" of the builder: property I am getting this error
The body might complete normally, causing 'null' to be returned, but the return type is a potentially non-nullable type.
Here is the code for the StreamBuilder.
StreamBuilder (
stream: _db.collection('agency').doc(globals.agencyId).
collection('trxns').doc(globals.currentTrxnId).snapshots(),
builder: (BuildContext context, AsyncSnapshot trxnSnapshot) {
if (trxnSnapshot.hasData) {
var outPut = (trxnSnapshot.data() as QueryDocumentSnapshot);
clientFNameController.text = trxnSnapshot.data.data['clientFName'] ?? "";
}
),
I tried to add a type like this: StreamBuilder (
but I get this error: The argument type 'Stream<DocumentSnapshot<Map<String, dynamic>>>' can't be assigned to the parameter type 'Stream<QuerySnapshot<Object?>>?'.
Now I change the type to match the statement above and now I am back at the original error message. Here is what I changed the type to.
StreamBuilder <DocumentSnapshot<Map<String, dynamic>>>(
stream: _db.collection('agency').doc(globals.agencyId).
collection('trxns').doc(globals.currentTrxnId).snapshots(),
builder: (BuildContext context, AsyncSnapshot trxnSnapshot) {
if (trxnSnapshot.hasData) {
var outPut = (trxnSnapshot.data() as QueryDocumentSnapshot);
clientFNameController.text = trxnSnapshot.data.data['clientFName'] ?? "";
}
),
I don't know what is wrong or how to fix it. I know I need either this "!" or this "?" but I don't know which one or where to put it.
I would really appreciate some help here.

A StreamBuilder must return a Widget in its builder parameter. If you don't need to show any Widget (just do some background update), you can use a StreamSubscription instead:
class _MyWidgetState extends State<MyWidget> {
late final StreamSubscription<DocumentSnapshot> _subscription;
#override
void initState() {
super.initState();
final Stream<DocumentSnapshot> stream = _db
.collection('agency')
.doc(globals.agencyId)
.collection('trxns')
.doc(globals.currentTrxnId)
.snapshots();
_subscription = stream.listen((data) {
if (data == null) return;
setState(() => clientFNameController.text = data['clientFName'] ?? "");
});
}
#override
void dispose() {
_subscription.cancel();
super.dispose();
}
}
However, if you want to keep using StreamBuilder, you can
just return an empty Widget (not really a good practice):
StreamBuilder(
stream: _db
.collection('agency')
.doc(globals.agencyId)
.collection('trxns')
.doc(globals.currentTrxnId)
.snapshots(),
builder: (BuildContext context, AsyncSnapshot trxnSnapshot) {
if (trxnSnapshot.hasData) {
var outPut = (trxnSnapshot.data as QueryDocumentSnapshot);
clientFNameController.text = outPut.data['clientFName'] ?? "";
}
return SizedBox.shrink();
},
),
return a meaningful Widget based on each operation:
StreamBuilder(
stream: _db
.collection('agency')
.doc(globals.agencyId)
.collection('trxns')
.doc(globals.currentTrxnId)
.snapshots(),
builder: (BuildContext context, AsyncSnapshot trxnSnapshot) {
if (trxnSnapshot.hasData) {
var outPut = (trxnSnapshot.data as QueryDocumentSnapshot);
clientFNameController.text = outPut.data['clientFName'] ?? "";
return Text("client name updated");
}
return Text("client name not updated");
},
),

Related

Realtime Update of UI with StreamBuilder and bool -ERROR Expected a value of type 'Map<dynamic, dynamic>', but got one of type '_JsonDocumentSnapshot'

In the title I explained what I want to do. I have a bool value named
'turnInvitingPlayer' stored somewhere in a document field in Firestore. The location of the document I know exactly from the instance Variables of GameTable.
This is what i tried:
class GameTable extends StatefulWidget {
GameTable({Key? key,
required this.player,
required this.invitationID,
required this.invitationIdPlayerInvited,
required this.invitationIdPlayerInviting})
: super(key: key);
final Player? player;
final String invitationIdPlayerInvited;
final String invitationIdPlayerInviting;
/// the invitation ID is the doc name of the gambling Table
final String invitationID;
#override
State<GameTable> createState() => _GameTableState();
}
class _GameTableState extends State<GameTable> {
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: FirebaseFirestore.instance
.collection('GameTables')
.doc(widget.invitationID)
.snapshots(),
builder: (context, snapshot) {
if (snapshot.hasData) {
var dataGameTable = snapshot.data! as Map;
var turnInvitingPlayer =
dataGameTable['turnInvitingPlayer'] as bool;
if (turnInvitingPlayer == true) {
return Container(color: Colors.blue);
} else {
return Container(color: Colors.red);
}
} else if (!snapshot.hasData) {
return Container(
child: Text('There is no data'),
);
}
return CircularProgressIndicator();
});
}
}
I am getting the following error when I run the App
Expected a value of type 'Map<dynamic, dynamic>', but got one of type '_JsonDocumentSnapshot'
Can somebody show me a way how I can simple access the bool value of the stream and use it in if Clauses?
Thank's to everybody who will help.
Modify your stream builder as follows:
return StreamBuilder<Map<dynamic,dynamic>>(
stream: FirebaseFirestore.instance
.collection('GameTables')
.doc(widget.invitationID)
.snapshots(),
builder: (context, snapshot) {
if (snapshot.hasData) {
var dataGameTable = snapshot.data! as Map;
var turnInvitingPlayer =
dataGameTable['turnInvitingPlayer'] as bool;
if (turnInvitingPlayer == true) {
return Container(color: Colors.blue);
} else {
return Container(color: Colors.red);
}
} else if (!snapshot.hasData) {
return Container(
child: Text('There is no data'),
);
}
return CircularProgressIndicator();
});
I found the solution i have done following changes:
var dataGameTable = snapshot.data!; // remove as Map
var turnInvitingPlayer = dataGameTable['turnInvitingPlayer'] as bool; // remove this line
Now I have access to the boolean value with simple dataGameTable['turnInvitingPlayer'].

Show this text when a document field is empty

I am having trouble in the documentSnapshot. I want to show "No data" when the document field is empty. And show "has data" when the document field has something inside. The problem is that either when the document field has something inside or not it’s always showing "no data"
class _TestState extends State<Test> {
String userUid = FirebaseAuth.instance.currentUser!.uid;
#override
Widget build(BuildContext context) {
return StreamBuilder<DocumentSnapshot<Map<String, dynamic>>>(
stream: FirebaseFirestore.instance
.collection("users")
.doc(userUid)
.snapshots(),
builder: (context, snapshot) {
if (snapshot.data?.data()?.containsValue('groupId') ==null) {
return const Text("no data");
} else {
//It’s always show this
return const Text('has data');
}
});
}
}
you are checking here if the document field is null or not but you need to check whether the field is empty string("") or not since thats how picture shows.
change this line
if (snapshot.data?.data()?.containsValue('groupId') == null)
containsValue is checking if the object from firebase which is something like key and value pair, contains the given [value], in this case 'groupId'. but groupId is key not value from picture shown in your case
try this
String userUid = FirebaseAuth.instance.currentUser!.uid;
bool checkEmpty = false;
StreamBuilder<DocumentSnapshot<Map<String, dynamic>>>(
stream: firestore
.collection('users')
.doc("userUid")
.snapshots(),
builder: (context, snapshot) {
snapshot.data!.data()?.forEach((key, value) {
if (key == 'groupId') {
checkEmpty = value == '';
}
});
return checkEmpty?
const Text('no data'):
const Text('has data')

Firestore Class 'QuerySnapshot' has no instance method '[]'

I want a ListView to show the names of the users. I am using a cloudfunction with the admin sdk to return a list of all the users with the corresponding user IDs. When I want to pass that uid to a Widget with a streambuilder, it gives me the error:
Class 'QuerySnapshot' has no instance method '[]'.
Receiver: Instance of 'QuerySnapshot'
Tried calling: []("firstName")
This is the function I am calling while building the ListView for the title:
Widget getFirstName(uid, item) {
return StreamBuilder(
stream: Firestore.instance
.collection('users')
.document('HzBUMs06BAPHK0Y7m5kfOmUzawC2')
.collection('userInfo')
.snapshots(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (!snapshot.hasData) {
return Text('${item['email']}');
} else {
return Text('${snapshot.data.documents['firstName']}');
}
},
);
}
I am not using the uid which I will pass to it yet, as the User ID that I hardcoded right now is the only one with the firstName data in it.
When I feed it a non-existing userID, it still seems to think it has data in it and tries to return its (non-existent) data.
What am I doing wrong here?
I managed to fix it by using this piece of code:
Widget fullNameWidget(uid) {
return FutureBuilder(
future: fullName(uid),
builder: (context, snapshot) {
return Text('${snapshot.data}');
},
);
}
Future fullName(uid) async {
return Firestore.instance
.collection("users")
.document('$uid')
.collection("userInfo")
.getDocuments()
.then((querySnapshot) {
print(querySnapshot.documents[0]['firstName']);
if (querySnapshot == 'null') {
} else {
return '${querySnapshot.documents[0]['firstName']} ${querySnapshot.documents[0]['lastName']}';
}
// querySnapshot.documents.where((element) {
// print(element.documentID == 'firstName');
// });
});
}

Flutter - How do I use await inside the streambuilder?

I want to use await inside streambuilder. However, if you use async inside, you get an error. On the code below !!!!!!!! That's the part I want to solve. Thank you very much if I can tell you how.
class _MemoStreamState extends State<MemoStream> {
final _fireStore = Firestore.instance;
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: _fireStore
.collection(widget.logInUsrEmail)
.orderBy('id', descending: false)
.snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) return LinearProgressIndicator();
final memos = snapshot.data.documents;
List<MemoMaterial> memoList = [];
for (var memo in memos) {
final memoDocumentID = memo.documentID;
final memoTitle = await PlatformStringCryptor().decrypt(memo.data['title'], _key); !!!!!!!!!!
final memoUsrID = memo.data['usrID'];
final memoUsrPW = memo.data['usrPW'];
final memoText = memo.data['text'];
final memoCreateTime = memo.data['createTime'];
final memoMaterial = MemoMaterial(
logInUsrEmail: widget.logInUsrEmail,
doc: memoDocumentID,
title: memoTitle,
usrID: memoUsrID,
usrPW: memoUsrPW,
text: memoText,
createTime: memoCreateTime,
);
memoList.add(memoMaterial);
}
return Expanded(
child: new ListView.builder(
You should do something like this :
Stream<List<MemoMaterial>> memosStream;
Future<MemoMaterial> generateMemoMaterial(Memo memo) async {
final memoTitle =
await PlatformStringCryptor().decrypt(memo.data['title'], _key);
return MemoMaterial(
logInUsrEmail: widget.logInUsrEmail,
doc: memo.documentID,
title: memoTitle,
usrID: memo.data['usrID'],
usrPW: memo.data['usrPW'],
text: memo.data['text'];,
createTime: memo.data['createTime'],
);
}
#override
void initState() {
memosStream = _fireStore
.collection(widget.logInUsrEmail)
.orderBy('id', descending: false)
.snapshots()
.asyncMap((memos) => Future.wait([for (var memo in memos) generateMemoMaterial(memo)]));
super.initState();
}
#override
Widget build(BuildContext context) {
return StreamBuilder<List<MemoMaterial>>(
stream: memosStream // Use memostream here
asyncMap() will "transform" every new set of Documents into a list of MemoMaterial, and emit this list into the stream when the action is performed.
Future.wait() allows to perform multiple async requests simultaneously.
You can do it using FutureBuilder inside StreamBuilder in following way.
Stream<List<int>> callme() async* {
yield [1, 2, 3, 4, 5, 6];
}
buildwidget() async {
await Future.delayed(Duration(seconds: 1));
return 1;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: StreamBuilder(
stream: callme(),
builder: (_, sna) {
if (sna.hasData) {
return FutureBuilder(
future: buildwidget(),
builder: (_, snap) {
if (snap.hasData) {
return ListView.builder(
itemCount: sna.data.length,
itemBuilder: (_, index) {
return Text("${sna.data[index]} and ${snap.data}");
},
);
} else {
return CircularProgressIndicator();
}
},
);
} else {
return CircularProgressIndicator();
}
}),
),
);
}
I will prefer to use Getx or Provider State management to Handle the UI if it depends on the async function.
Suppose you want to fetch data from firebase using StreamBuilder() which returns some docs which contains image links then you want to download these images and show from storage. Obviously downloading the image is async type of work. Then you will get error if you show the images with the links you get direct from StreamBuilder().
What you can do is set a variable in getx or provider to show or hide the image Widget. If the Image is being downloaded or not downloaded then set the variable to hide/show the image when the async type of function is completed.

`Future<Stream>` is not a subtype of type `Stream`

I made simple singleton class to use on flutter StreamBuilder widget, for example:
StreamBuilder<User>(
stream: MydbModel.create().then((dao) => dao.getUserStream()) as Stream<User>,
builder: (_, snapshot) {
...
},
)
in that
MydbModel.create().then((dao) => dao.getUserStream())
return Future<Stream<User>> and my cast don't work to have only Stream<User>.
Error:
> type 'Future<Stream<User>>' is not a subtype of type 'Stream<User>' in type cast
how can i resolve this problem?
my completed class:
class MydbModel{
UserDao _userDao;
MydbModel._(this._userDao);
static Future<MydbModel> create() async => MydbModel._(await initialDatabase());
static Future<UserDao> initialDatabase() async {
var db = await $FloorAppDatabase.databaseBuilder('flutter_database.db').build();
return db.userDao;
}
Stream<User> getUserStream(){
return userDao.getUserInfo();
}
UserDao get userDao=>_userDao;
}
You need to await the Future first:
FutureBuilder(
future: MydbModel.create(),
builder: (BuildContext context, AsyncSnapshot<MydbModel> snapshot) {
if (!snapshot.hasData) return Container();
return StreamBuilder(
stream: snapshot.data.getUserStream(),
builder: ..,
);
},
);
I used a FutureBuilder to await the Future and to create a StreamBuilder when the future is loaded.