Widget build(context) {
return Scaffold(
appBar: header(context, isApp: true, titleText: 'Instagram'),
body: StreamBuilder<QuerySnapshot>(
stream: userRef.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
circularProgress();
}
final List<Text> list = snapshot.data!.docs
.map((user) => Text(user['username']))
.toList();
return Container(
child: ListView(
children: list,
),
);
},
),
);
}
The error message appears to be referring to the null check operator in this line
final List<Text> list = snapshot.data!.docs
.map((user) => Text(user['username']))
.toList();
implying that snapshot.data is null at this point.
Clearly you don't intend for this code to be executed if no data has been returned.
Since this block of code
if (!snapshot.hasData) {
circularProgress();
}
has no return statement, execution continues down the block.
Changing it to
if (!snapshot.hasData) {
return circularProgress();
}
should solve your problem
Related
I have a collection called Todos in Firestore with 3 possible properties (id, text and checked).
So far i have succeeded on creating and saving todo's. Now i want them in a Listview but it returns an error on hot restart:
════════ Exception caught by widgets library ═══════════════════════════════════
type 'Null' is not a subtype of type 'String'
The relevant error-causing widget was
StreamBuilder<QuerySnapshot<Object?>>
My code for displaying the ListView:
final Stream<QuerySnapshot> _todostream =
FirebaseFirestore.instance.collection('Todos').snapshots();
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Styles.bgColor,
floatingActionButton: FloatingActionButton(
onPressed: createNewTask,
child: const Icon(Icons.add),
),
body: StreamBuilder(
stream: _todostream,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return const Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
}
return ListView(
children: snapshot.data!.docs.map((DocumentSnapshot document) {
Map<String, dynamic> data =
document.data()! as Map<String, dynamic>;
return ListTile(title: Text(data['text']));
}).toList(),
);
},
),
);
}
}
I hoped to see a listview with the results of my collection, it contains 2 items.
It would be better to accept null and check if it contains data or not.
body: StreamBuilder(
stream: _todostream,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return const Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
}
if (snapshot.hasData) {
final data = snapshot.data?.docs.map((e) => e.data()).toList();
if (data == null) {
return Text("got null data");
}
return ListView.builder(
itemCount: data.length,
itemBuilder: (context, index) {
final map = data[index] as Map?;
return ListTile(title: Text("${map?['text']}"));
});
}
return Text("NA");
},
),
product_list_screen.dart
import 'package:flutter/material.dart';
import '../blocs/cart_bloc.dart';
import '../models/cart.dart';
class ProductListScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("eCommerce"),
actions: [
IconButton(
icon: Icon(Icons.shopping_cart),
onPressed: () => Navigator.pushNamed(context, "/cart"),
)
],
),
body: buildProductList(),
);
}
buildProductList() {
return StreamBuilder(
initialData: productBloc.getAll(),
stream: productBloc.getStream,
builder: (context, snapshot) {
return snapshot.data.length > 0 //error
? buildProductListItems(snapshot)
: Center(
child: Text("No data"),
);
},
);
}
buildProductListItems(AsyncSnapshot<Object?> snapshot) {
return ListView.builder(
itemCount: snapshot.data.length, //error
itemBuilder: (BuildContext context, index) {
var list = snapshot.data;
return ListTile(
title: Text(list[index].name), //error
subtitle: Text(list[index].price.toString()), //error
trailing: IconButton(
icon: Icon(Icons.add_shopping_cart),
onPressed: () {
cartBloc.addToCart(Cart(List[index], 1)); //error
},
),
);
});
}
}
The property 'length' can't be unconditionally accessed because the receiver can be 'null'. (Documentation) Try making the access conditional (using '?.') or adding a null check to the target ('!').
I used '!' or '?' but its didn't work. Can you help me? Thanks.
You can try using null aware operator :
snapshot.data?.length ?? 0
Do not use StreamBuilder use FutureBuilder Example:
FutureBuilder<Object?>(
future: _fetchNetworkCall, // async work
builder: (BuildContext context, AsyncSnapshot<Object?>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}');
}
},
)
Try to press ctrl+. On windows or command+. On mac to get suggestions from your code
This will work in 90%of the times
It would be easier by providing return type on StreamBuilder. Since you like receive a list of Item, include
return StreamBuilder<List<ItemModelClass>?>(
builder: (context, snapshot) {
return snapshot.data != null && snapshot.data!.length > 0
? Text("replresenData")
: Text("NO data")
I prefer including error, hasData and empty data state separately.
Find more about StreamBuilder
FirebaseFirestore firestore = FirebaseFirestore.instance;
List dataListWidget(AsyncSnapshot snapshot) {
return snapshot.data.docs[0].map((document) {
return ListTile(
title: Text(document["Name"]),
subtitle: Text(document["City"]),
);
}).toList();
}
//my streamBuilder
StreamBuilder(
stream: firestore.collection('customers').snapshots(),
builder: (context, snapshot) {
return ListView(
children: dataListWidget(snapshot),
);
},
),`
The query which you just fired returns empty mean it doesn't have any response in it and without checking any condition you are trying to access its 0th element.
You should do something like this
StreamBuilder(
stream:firestore.collection('customers').snapshots(),
builder:(context,snapshot){
if(snapshot.hasData && !snapshot.hasError){
return ListView(children: dataListWidget(snapshot),);
}
return Container();
});
And your dataListWidget should Look like this
List dataListWidget(AsyncSnapshot snapshot){
if(snapshot.data.docs.isNotEmpty){
return snapshot.data.docs[0].map((document) {
return ListTile(
title: Text(document["Name"]), subtitle: Text(document["City"]),
);
}).toList();
}
else{
return [Container()];
}
}
I am using streambuilder to display snapshot data but it is not displaying. The screen is just blank but When I use the future builder with get() methode it display the data but I want realtime changes. I am new to flutter please help me with this. here is code.
class TalentScreen2 extends StatelessWidget {
final Query _fetchFavUser = FirebaseRepo.instance.fetchFavUsers();
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Column(
children: [
Text('Talent Screen 2(Favourites)'),
Expanded(child: _retrieveData(context))
],
),
),
);
}
Widget _retrieveData(BuildContext context) => StreamBuilder<QuerySnapshot>(
stream: _fetchFavUser.snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) return const Text('Something went wrong');
if (!snapshot.hasData) return const Text('Alas! No data found');
if (snapshot.connectionState == ConnectionState.waiting)
return Center(
child: CircularProgressIndicator(
strokeWidth: 2.0,
));
if (snapshot.connectionState == ConnectionState.done)
return theUserInfo(snapshot.data.docs);
return Container();
});
Widget theUserInfo(List<QueryDocumentSnapshot> data) {
return ListView.builder(
shrinkWrap: true,
itemCount: data.length,
itemBuilder: (BuildContext context, int index) {
var uid = data[index]['uid'];
TalentHireFavModel userData = TalentHireFavModel.fromMap(
data[index].data(),
);
return Card(
child: Column(
children: <Widget>[
Text(data[index]['orderBy']),
// 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(userData.uid, context),
],
),
);
});
}
Future<DocumentSnapshot> fetch(data) async =>
await FirebaseRepo.instance.fetchWorkerUserData(data);
Widget _iconButton(uid, context) {
return IconButton(
icon: Icon(Icons.favorite),
onPressed: () {
BlocProvider.of<TalentFavCubit>(context).removeTalentFav(uid);
});
}
}
and here is the firestore query methode where I am just applying simple query to fetch all documents and display them. I want real-time changes
Query fetchFavUsers() {
var data = _firestore
.collection('workerField')
.doc(getCurrentUser().uid)
.collection('favourites')
// .where('uid', isNotEqualTo: getCurrentUser().uid)
.orderBy('orderBy', descending: true);
return data;
}
The solution is to just return the function. Get that method out of if statement and place it in just return statement.
This example from the cloud_firestore documentation uses a StreamBuilder and the ConnectionState of an AsyncSnapshot to handle the stream in its different states. Is there a similar way to manage the ConnectionState when accessing the stream via a StreamProvider instead of a StreamBuilder? What is the best way of avoiding it to return null in the short while until it actually has documents from Firestore?
Here the example from the cloud_firestore docs with the StreamBuilder:
class BookList extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('books').snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.waiting: return new Text('Loading...');
default:
return new ListView(
children: snapshot.data.documents.map((DocumentSnapshot document) {
return new ListTile(
title: new Text(document['title']),
subtitle: new Text(document['author']),
);
}).toList(),
);
}
},
);
}
}
I have a rather basic stream:
List<AuditMark> _auditMarksFromSnapshot(QuerySnapshot qs) {
return qs.documents.map((DocumentSnapshot ds) {
return AuditMark.fromSnapshot(ds);
}).toList();
}
Stream<List<AuditMark>> get auditMarks {
return Firestore.instance
.collection('_auditMarks')
.snapshots()
.map(_auditMarksFromSnapshot);
}
This is accessed via a StreamProvider (have omitted other providers here):
void main() async {
runApp(MultiProvider(
providers: [
StreamProvider<List<AuditMark>>(
create: (_) => DatabaseService().auditMarks, ),
],
child: MyApp(),
));
}
I have tried somehow converting the QuerySnapshot to an AsyncSnapshot<QuerySnapshot> but probably got that wrong.
Could of course give the StreamProvider some initialData like so - but this is cumbersome, error prone and probably expensive:
initialData: <AuditMark>[
AuditMark.fromSnapshot(await Firestore.instance
.collection('_auditMarks')
.orderBy('value')
.getDocuments()
.then((value) => value.documents.first))
...but I am hoping there is a smarter way of managing the connection state and avoiding it to return null before it can emit documents?
I have been dealing with this and didn't want to declare an initialData to bypass this issue.
What I did was creating a StreamBuilder as the child of StreamProvider.
So that I could use the snapshot.connectionState property of StreamBuilder in the StreamProvider.
Here's the code:
return StreamProvider<List<AuditMark>>.value(
value: DatabaseService().auditMarks,
child: StreamBuilder<List<AuditMark>>(
stream: DatabaseService().auditMarks,
builder: (context, snapshot) {
if (!snapshot.hasError) {
switch (snapshot.connectionState) {
case ConnectionState.none: // if no connection
return new Text(
"Offline!",
style: TextStyle(fontSize: 24, color: Colors.red),
textAlign: TextAlign.center,
);
case ConnectionState.waiting
// while waiting the data, this is where you'll avoid NULL
return Center(child: CircularProgressIndicator());
default:
return ListView.builder(
// in my case I was getting NULL for itemCount
itemCount: logs.length,
itemBuilder: (context, index) {
return LogsTile(log: logs[index]);
},
);
}
}
else {
return new Text(
"Error: ${snapshot.error}",
style: TextStyle(fontSize: 17, color: Colors.red),
textAlign: TextAlign.center,
);
}
}
)
);
Probably not the most elegant solution, but I ended up using a simple bool variable which is true while not all StreamProviders have emitted values.
bool _waitForStreams = false;
if (Provider.of<List<AuditMark>>(context) == null) _waitForStreams = true;
if (Provider.of<...>>(context) == null) _waitForStreams = true;
(etc. repeat for every StreamProvider)
// show "loading..." message while not all StreamProviders have supplied values
if (_waitForStreams) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
SizedBox(height: 25.0),
Text('loading...'),
],
)
],
),
);
}
I don't know if it's correct but this is how I implement it.
Since streamProviser does not provide a connection state, I first use streamBuilder and then provider.value to distribute the data:
return StreamBuilder<BusinessM>(
stream: db.businessDetails(), //firebase stream mapped to business model class
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active)
return Provider<BusinessM>.value(
value: snapshot.data,
child: Businesspage(),
);
else
return Center(child: CircularProgressIndicator());
});
For someone who want to use StreamProvider but end up with no ConnectionState state to use. For some of the cases, null represent the state of "waiting for the first data", not "no data".
In StreamProvider, there is no build-in method to detect the state. But we can still warp the state outside of the data:
StreamProvider<AsyncSnapshot<QuerySnapshot?>>.value(
initialData: const AsyncSnapshot.waiting(),
value: FirebaseFirestore.instance
.collection('books')
.snapshots()
.map((snapshot) {
return AsyncSnapshot.withData(ConnectionState.active, snapshot);
}),
child: ...,
);
or for firebaseAuth:
StreamProvider<AsyncSnapshot<User?>>.value(
initialData: const AsyncSnapshot.waiting(),
value: FirebaseAuth.instance.userChanges().map(
(user) => AsyncSnapshot.withData(ConnectionState.active, user),
),
),