Trying to load the data as I scroll my listview - firebase

I am trying to make the page load data as i scroll, so I made a scroll listener in my list builder then i made a fetch next function in my future builder.
But how can i call my fetchNext function which is in my future builder to call in my void scrollListener() {}.
Is it even right to paginate the data like this. Please help.
This is the list builder
class PostGridScreen extends StatefulWidget {
final String type, city, state;
PostGridScreen({this.type, this.city, this.state});
#override
_PostGridScreenState createState() => _PostGridScreenState();
}
class _PostGridScreenState extends State<PostGridScreen>
with AutomaticKeepAliveClientMixin {
Future _getPosts;
int documentLimit = 5;
bool _hasNext = true;
bool _isFetchingUsers = false;
final _usersSnapshot = <DocumentSnapshot>[];
final scrollController = ScrollController();
var refreshKey = GlobalKey<RefreshIndicatorState>();
#override
void initState() {
_fetchPosts();
// scrollController.addListener(scrollListener);
super.initState();
}
#override
void dispose() {
scrollController.dispose();
super.dispose();
}
void scrollListener() {}
Future _fetchPosts() {
return _getPosts = Provider.of<BaseProvider>(context, listen: false)
.getPosts(widget.type, widget.city, widget.state, documentLimit);
}
Future<Null> refreshList() async {
refreshKey.currentState?.show(atTop: false);
await Future.delayed(Duration(seconds: 2));
setState(() {
_fetchPosts();
});
return null;
}
#override
Widget build(BuildContext context) {
super.build(context);
return RefreshIndicator(
onRefresh: () => refreshList(),
child: FutureBuilder(
key: PageStorageKey('Page1'),
future: _getPosts,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return ListView.builder(
controller: scrollController,
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
//Fetch Next
Future fetchNextUsers() async {
if (_isFetchingUsers) return;
_isFetchingUsers = true;
try {
_getPosts =
Provider.of<BaseProvider>(context, listen: false)
.getPosts(
widget.type,
widget.city,
widget.state,
documentLimit,
startAfter: _usersSnapshot.isNotEmpty
? _usersSnapshot.last
: null);
_usersSnapshot.addAll(snapshot.data);
if (snapshot.data.length < documentLimit)
_hasNext = false;
} catch (e) {}
_isFetchingUsers = false;
}
//Tile
return GestureDetector(
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => PostDetailScreen(
post: snapshot.data[index],
),
));
},
child: Padding(
padding: EdgeInsets.only(
top: 10,
bottom: 3.0,
right: 20.0,
left: 20.0,
),
child: Container(
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.black12,
offset: const Offset(
5.0,
5.0,
),
blurRadius: 10.0,
spreadRadius: 2.0,
), //BoxShadow
],
color: Colors.blue,
borderRadius: BorderRadius.circular(20.0),
image: DecorationImage(
image: NetworkImage(
snapshot.data[index]["imageUrl"],
),
fit: BoxFit.cover,
),
),
height: 190.0,
width: 100,
),
),
);
},
);
} else {
return Center(
child: Text("Loading..."),
);
}
}),
);
}
#override
bool get wantKeepAlive => true;
}
This is the base provider
class BaseProvider with ChangeNotifier {
final CollectionReference postsCollection =
FirebaseFirestore.instance.collection("posts");
//Fetching Data
Future getPosts(String type, String city, String state, int limit,
{DocumentSnapshot startAfter}) async {
try {
if (startAfter == null) {
QuerySnapshot<Map<String, dynamic>> response = await FirebaseFirestore
.instance
.collection("posts")
.where('type', isEqualTo: type)
.where('sellercity', isEqualTo: city)
.where('sellerstate', isEqualTo: state)
.limit(limit)
.get();
return response.docs;
} else {
QuerySnapshot<Map<String, dynamic>> response = await FirebaseFirestore
.instance
.collection("posts")
.where('type', isEqualTo: type)
.where('sellercity', isEqualTo: city)
.where('sellerstate', isEqualTo: state)
.startAfterDocument(startAfter)
.limit(limit)
.get();
return response.docs;
}
} catch (e) {
print(e.toString());
return e;
}
}
}

if I understood correctly you want that your app doesn't load and render everything in a list at once and render them just in time it is shown to the user.
for achieving this you have to implement lazy loading which ListView.builder do this completely on its own so you don't need to worry about it

Related

Refreshes the page when Slider values ​change - Flutter, BloC

I'm new to Flutter, I started making a Slider through bloc and writing data to the fireStore, but I ran into a problem - when I change the value in the Slider, the page refreshes and freezes at the Loading stage, this is profile_screen. How can I fix the problem so that the page is not updated in real time, but only by clicking on the Save button?
profile_screen
double _currentSliderValue = 1;
#override
Widget build(BuildContext context) {
// final ProfileBloc infoBloc = context.read<ProfileBloc>();
ProfileCubit cubit = ProfileCubit.get(context);
return Scaffold(
body: Container(
child: Padding(
padding: const EdgeInsets.all(50),
child: BlocBuilder<ProfileCubit, ProfileState>(
builder: (context, state) {
if (state is ProfileInitial) {
return const Center(
child: Text('No Data'),
);
}
if (state is ProfileLoadingState) {
return CircularProgressIndicator();
}
if (state is ProfileLoadedState) {
return FutureBuilder<DocumentSnapshot>(
future: cubit.getInfo(),
builder: (BuildContext context,
AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.hasError) {
return Text("Something went wrong");
}
if (snapshot.hasData && !snapshot.data!.exists) {
return _userNoInfo(cubit);
}
if (snapshot.connectionState == ConnectionState.done) {
Map<String, dynamic> data =
snapshot.data!.data() as Map<String, dynamic>;
return _userInfo(data, cubit);
}
return Text('Loading');
},
);
}
if (state is ProfileErrorState) {
return Center(
child: Text(state.error.toString()),
);
}
return Container();
})),
));
}
Widget _userInfo(data, ProfileCubit cubit) {
return Column(mainAxisAlignment: MainAxisAlignment.center, children: [
Form(
key: _formKeyTwo,
child: Column(children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: MediaQuery.of(context).size.width * 0.2,
child: Slider(
value: _currentSliderValue,
max: 100,
divisions: 100,
label: _currentSliderValue.toString(),
onChanged: (value) {
setState(() {
_currentSliderValue = value;
});
}),
),
SizedBox(width: 100),
_cardData(),
],
),
const SizedBox(height: 50.0),
Center(
child: SizedBox(
width: MediaQuery.of(context).size.width * 0.2,
child: ElevatedButton(
onPressed: () {
cubit.addAndUpdateInfo(
_bisNameContr.text,
_bisAddressContr.text,
_contactNameContr.text,
_contactEmailContr.text,
_phoneNumberContr.text,
_progNameContr.text,
_currentSliderValue.toString());
profile_cubit
class ProfileCubit extends Cubit<ProfileState> {
final Database _firestoreRepo;
ProfileCubit(this._firestoreRepo) : super(ProfileInitial()) {
getInfo();
}
static ProfileCubit get(context) => BlocProvider.of(context);
Future<DocumentSnapshot<Object?>> getInfo() {
try {
emit(ProfileLoadingState());
final Future<DocumentSnapshot<Object?>> infoData =
_firestoreRepo.getData();
emit(ProfileLoadedState(infoData));
return infoData;
} catch (e) {
emit(ProfileErrorState(e.toString()));
throw Exception(e);
}
}
Future<void> addAndUpdateInfo(
final String bisName,
final String bisAddress,
final String contactName,
final String contactEmail,
final String phoneNumber,
final String progName,
final String progYears,
) async {
await _firestoreRepo.addAndUpdateInfo(bisName, bisAddress, contactName,
contactEmail, phoneNumber, progName, progYears);
}
}
cubit_state
abstract class ProfileState extends Equatable {
const ProfileState();
#override
List<Object> get props => [];
}
class ProfileInitial extends ProfileState {}
class ProfileLoadingState extends ProfileState {}
class ProfileLoadedState extends ProfileState {
final Future<DocumentSnapshot<Object?>> dataInfo;
const ProfileLoadedState(this.dataInfo);
}
class ProfileErrorState extends ProfileState {
final String error;
const ProfileErrorState(this.error);
#override
List<Object> get props => [error];
}
If you don't want to build everytime, you can use buildWhen which is available inside BlocBuilder. if the value is true, it will rebuild everytime and if it's false, it will never rebuild. So you can keep a condition over there based on your requirements.

Stream Builder load more data

I am trying to load more data once I reach the end of the stream builder, everytime I reach the end, the stream builder reloads and bounces back to the beginning, and it does not allow me to scroll down ( keep reload and bounce back to the top).
What I tried is once I reach the end, increase the limit from the firebase, but I am not sure what the problem is.. anyone can help me with this?
Here is the sample code for what I want to do
_onEndScroll(ScrollMetrics metrics) {
//loadToTrue();
setState(() {
documentLimit = documentLimit + 10;
});
}
StreamBuilder(
initialData: cache,
stream: FirebaseFirestore.instance
.collection("timeline")
.doc(widget.currentUser.id)
.collection('timelinePosts')
.orderBy('timestamp', descending: true)
.limit(documentLimit)
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> streamSnapshot) {
items = streamSnapshot.data != null &&
streamSnapshot.data.docs != null
? streamSnapshot.data.docs
: [];
List<Post> posts =
items.map((doc) => Post.fromDocument(doc)).toList();
cache = streamSnapshot.data;
return !streamSnapshot.hasData ||
streamSnapshot.connectionState ==
ConnectionState.waiting
? Center(
child: CircularProgressIndicator(),
)
: NotificationListener<ScrollNotification>(
onNotification: (scrollNotification) {
if (scrollNotification is ScrollEndNotification) {
_onEndScroll(scrollNotification.metrics);
}
},
child: Container(
height: MediaQuery.of(context).size.height - 200,
margin: EdgeInsets.only(bottom: 1),
child: ListView.builder(
physics: BouncingScrollPhysics(
parent: ScrollPhysics()),
shrinkWrap: true,
itemCount: items.length,
itemBuilder: (_, i) =>
timelineDecision == "follow"
? posts[i]
: postsLocal[i])));
})
and this is code for all
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:fluttershare/pages/home.dart';
import 'package:fluttershare/models/user.dart';
import 'package:fluttershare/pages/search.dart';
import 'package:fluttershare/pages/upload.dart';
import 'package:fluttershare/pages/upload_limit.dart';
import 'package:fluttershare/widgets/post.dart';
import 'package:latlong/latlong.dart';
final usersRef = FirebaseFirestore.instance.collection('users');
class Timeline extends StatefulWidget {
final User currentUser;
Timeline({this.currentUser});
#override
_TimelineState createState() => _TimelineState();
}
class _TimelineState extends State<Timeline> {
QuerySnapshot cache;
List<Post> posts;
List<Post> postsLocal;
List<DocumentSnapshot> items;
List<String> followingList = [];
String timelineDecision = "local";
String address = "Norman";
ScrollController listScrollController;
int documentLimit = 5;
void initState() {
super.initState();
getFollowing();
}
getFollowing() async {
QuerySnapshot snapshot = await followingRef
.doc(currentUser.id)
.collection('userFollowing')
.get();
if (mounted) {
setState(() {
followingList = snapshot.docs.map((doc) => doc.id).toList();
});
}
}
setTimeline(String timelineDecision) {
setState(() {
this.timelineDecision = timelineDecision;
});
}
buildToggleTimeline() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
TextButton(
onPressed: () => setTimeline("follow"),
child: Text('Following',
style: TextStyle(
color: timelineDecision == 'follow'
? Theme.of(context).primaryColor
: Colors.grey)),
style: TextButton.styleFrom(
textStyle: const TextStyle(fontSize: 20),
),
),
TextButton(
onPressed: () => setTimeline("local"),
child: Text('Local',
style: TextStyle(
color: timelineDecision == 'local'
? Theme.of(context).primaryColor
: Colors.grey)),
style: TextButton.styleFrom(
textStyle: const TextStyle(fontSize: 20),
)),
],
);
}
_onEndScroll(ScrollMetrics metrics) {
//loadToTrue();
setState(() {
documentLimit = documentLimit + 10;
});
}
#override
Widget build(context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: const Text('Entango'),
actions: <Widget>[
PopupMenuButton(
icon: const Icon(Icons.add),
onSelected: choiceAction,
itemBuilder: (context) => [
PopupMenuItem(
child: Text("Timelimit post"),
value: 1,
),
PopupMenuItem(
child: Text("Normal post"),
value: 2,
)
]),
IconButton(
icon: const Icon(Icons.search),
tooltip: 'Show Snackbar',
onPressed: () => Navigator.push(
context, MaterialPageRoute(builder: (context) => Search())),
)
],
),
body: ListView(
children: <Widget>[
buildToggleTimeline(),
Divider(
height: 0.0,
),
//buildSlider(),
StreamBuilder(
initialData: cache,
stream: timelineDecision == "follow"
? FirebaseFirestore.instance
.collection("timeline")
.doc(widget.currentUser.id)
.collection('timelinePosts')
.orderBy('timestamp', descending: true)
.limit(documentLimit)
.snapshots()
: FirebaseFirestore.instance
.collection("postsLocalRef")
.doc(address)
.collection("userPosts")
.orderBy('timestamp', descending: true)
.limit(documentLimit)
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> streamSnapshot) {
items = streamSnapshot.data != null &&
streamSnapshot.data.docs != null
? streamSnapshot.data.docs
: [];
List<Post> posts =
items.map((doc) => Post.fromDocument(doc)).toList();
List<Post> postsLocal =
items.map((doc) => Post.fromDocument(doc)).toList();
cache = streamSnapshot.data;
return !streamSnapshot.hasData ||
streamSnapshot.connectionState ==
ConnectionState.waiting
? Center(
child: CircularProgressIndicator(),
)
: NotificationListener<ScrollNotification>(
onNotification: (scrollNotification) {
if (scrollNotification is ScrollEndNotification) {
_onEndScroll(scrollNotification.metrics);
}
},
child: Container(
height: MediaQuery.of(context).size.height - 200,
margin: EdgeInsets.only(bottom: 1),
child: ListView.builder(
physics: BouncingScrollPhysics(
parent: ScrollPhysics()),
shrinkWrap: true,
itemCount: items.length,
itemBuilder: (_, i) =>
timelineDecision == "follow"
? posts[i]
: postsLocal[i])));
})
],
),
);
}
void choiceAction(int value) {
if (value == 1) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Upload_limit(
currentUser: currentUser,
)));
} else {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Upload(
currentUser: currentUser,
)));
}
}
}
There are few issues I could find. One of them is using StreamBuilder to fetch paged data. While it might be great in theory but it won't work in case of Firebase as Firebase is providing Stream. So every-time, setState is called, a new steam will be created. I have wrote sample app for fetching data from firestore in paginated way.
//main.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
final String title;
const MyHomePage({Key? key, required this.title}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
static const PAGE_SIZE = 30;
bool _allFetched = false;
bool _isLoading = false;
List<ColorDetails> _data = [];
DocumentSnapshot? _lastDocument;
#override
void initState() {
super.initState();
_fetchFirebaseData();
}
Future<void> _fetchFirebaseData() async {
if (_isLoading) {
return;
}
setState(() {
_isLoading = true;
});
Query _query = FirebaseFirestore.instance
.collection("sample_data")
.orderBy('color_label');
if (_lastDocument != null) {
_query = _query.startAfterDocument(_lastDocument!).limit(PAGE_SIZE);
} else {
_query = _query.limit(PAGE_SIZE);
}
final List<ColorDetails> pagedData = await _query.get().then((value) {
if (value.docs.isNotEmpty) {
_lastDocument = value.docs.last;
} else {
_lastDocument = null;
}
return value.docs
.map((e) => ColorDetails.fromMap(e.data() as Map<String, dynamic>))
.toList();
});
setState(() {
_data.addAll(pagedData);
if (pagedData.length < PAGE_SIZE) {
_allFetched = true;
}
_isLoading = false;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: NotificationListener<ScrollEndNotification>(
child: ListView.builder(
itemBuilder: (context, index) {
if (index == _data.length) {
return Container(
key: ValueKey('Loader'),
width: double.infinity,
height: 60,
child: Center(
child: CircularProgressIndicator(),
),
);
}
final item = _data[index];
return ListTile(
key: ValueKey(
item,
),
tileColor: Color(item.code | 0xFF000000),
title: Text(
item.label,
style: TextStyle(color: Colors.white),
),
);
},
itemCount: _data.length + (_allFetched ? 0 : 1),
),
onNotification: (scrollEnd) {
if (scrollEnd.metrics.atEdge && scrollEnd.metrics.pixels > 0) {
_fetchFirebaseData();
}
return true;
},
),
);
}
}
class ColorDetails {
final String label;
final int code;
ColorDetails(this.code, this.label);
factory ColorDetails.fromMap(Map<String, dynamic> json) {
return ColorDetails(json['color_code'], json['color_label']);
}
Map toJson() {
return {
'color_code': code,
'color_label': label,
};
}
}
In case you are interested, you can checkout the article I have written as well at https://blog.litedevs.com/infinite-scroll-list-using-flutter-firebase-firestore

Lazy Loading list view builder in Flutter with Helper.Provider-Model

what I'm trying to do is use lazy loading of elements in list view builder in flutter.
I used also the Provider, helpers and model paradigma and I'm trying to load some dishes from my db in firestore.
I found different solution for implementing lazy loading such as this. But I obtain an error.
Below the code:
Helpers
class DishServices{
String collection = "dishes";
FirebaseFirestore _firestore = FirebaseFirestore.instance;
String subcollection = "ingredients";
List<DishModel> dishes = [];
QuerySnapshot collectionState;
Future<List<DishModel>> getLazyDishes() async
{
var collectionData = _firestore.collection(collection).orderBy("name").limit(2);
fetchDocuments(collectionData);
return dishes;
}
Future<List<DishModel>> getLazyDishesNext() async {
// Get the last visible document
var lastVisible = collectionState.docs[collectionState.docs.length-1];
var collectionData = FirebaseFirestore.instance
.collection(collection)
.orderBy("name").startAfterDocument(lastVisible).limit(3);
fetchDocuments(collectionData);
return dishes;
}
fetchDocuments(Query collection){
collection.get().then((value) {
collectionState = value; // store collection state to set where to start next
value.docs.forEach((element) {
dishes.add(DishModel.fromSnapshot(element));
});
});
}
}
Provider
class DishProvider with ChangeNotifier{
DishServices _dishServices = DishServices();
List<DishModel> dishes = [];
List<DishModel> dishesLazyNext = [];
DishProvider.initialize(){
_loadDishes();
// searchIngredient(ingredientName:"cime");
}
_loadDishes() async{
dishes = await _dishServices.getLazyDishes();
notifyListeners();
}
Future loadNextLazyDishes()async{
dishesLazyNext = await _dishServices.getLazyDishesNext();
dishes = dishes + dishesLazyNext;
notifyListeners();
}
}
Main.dart
oid main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MultiProvider(providers: [
ChangeNotifierProvider.value(value: DishProvider.initialize())
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'MyApp',
theme: ThemeData(
primarySwatch: Colors.red,
),
home: ScreensController()
)));
}
Finally my build is :
class _MainHomeState extends State<MainHome> {
ScrollController scrollController = ScrollController();
#override
void initState() {
super.initState();
final dishProvider = Provider.of<DishProvider>(context);
scrollController.addListener(() {
if (scrollController.position.atEdge) {
if (scrollController.position.pixels == 0)
print('ListView scroll at top');
else {
print('ListView scroll at bottom');
dishProvider.loadNextLazyDishes(); // Load next documents
}
}
});
}
#override
Widget build(BuildContext context) {
final dishProvider = Provider.of<DishProvider>(context);
//atest.loadA();
return Scaffold(
body: SafeArea(
child: ListView(
children: <Widget>[
OtherElement(),
Container(
child: ListView.builder(
controller: scrollController,
itemCount: dishProvider.dishes.length,
shrinkWrap: true,
itemBuilder: (_,index){
return DishWidget(
dishModel: dishProvider.dishes[index],
);
}),
),
],
),
),
);
}
}
this is the error that return when I try to run the app
How I can fix it?
Just move the scroll listener to your build:
class _MainHomeState extends State<MainHome> {
ScrollController scrollController = ScrollController();
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
final dishProvider = Provider.of<DishProvider>(context);
scrollController.addListener(() {
if (scrollController.position.atEdge) {
if (scrollController.position.pixels == 0)
print('ListView scroll at top');
else {
print('ListView scroll at bottom');
dishProvider.loadNextLazyDishes(); // Load next documents
}
}
});
//atest.loadA();
return Scaffold(
body: SafeArea(
child: ListView(
children: <Widget>[
OtherElement(),
Container(
child: ListView.builder(
controller: scrollController,
itemCount: dishProvider.dishes.length,
shrinkWrap: true,
itemBuilder: (_,index){
return DishWidget(
dishModel: dishProvider.dishes[index],
);
}),
),
],
),
),
);
}
}

Can't get value of a List<Text> in container Flutter Firebase

I am trying to print the documentIDs of the documents present in my 'Menu' collection. But seems like the Widget is not getting the value of documentIDs List<Text> list. When I print the list in the addDocId function it does print the content of documentIDs but in the build widget if I print the list it only outputs - ('[]'). I can't figure out why it is getting docIds empty. Kindly help me out.
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';
class Category extends StatefulWidget {
#override
_CategoryState createState() => _CategoryState();
}
class _CategoryState extends State<Category> {
bool _initialized = false;
bool _error = false;
initializeFlutterFire() async {
try {
await Firebase.initializeApp();
setState(() {
_initialized = true;
});
} catch (e) {
setState(() {
_error = true;
});
}
}
#override
void initState() {
initializeFlutterFire();
super.initState();
}
Widget somethingWentWrong() {
return AlertDialog(
title: Text('Something Went Wrong'),
content: Icon(
Icons.error,
color: Colors.red,
size: 40.0,
),
);
}
Widget loading() {
return Center(
child: CircularProgressIndicator(
backgroundColor: Colors.black,
valueColor: new AlwaysStoppedAnimation<Color>(Colors.black),
),
);
}
List<Text> docIds = [];
void addDocId(String s) {
docIds.add(Text(s));
print(docIds);
}
#override
Widget build(BuildContext context) {
if (_error) {
return somethingWentWrong();
}
if (!_initialized) {
return loading();
}
FirebaseFirestore.instance
.collection('Menu')
.get()
.then((QuerySnapshot querySnapshot) => {
querySnapshot.docs.forEach((doc) {
addDocId(doc.id);
})
});
print(docIds);
return Container(
margin: EdgeInsets.fromLTRB(
50.0,
100.0,
50.0,
100.0,
),
// color: Colors.blueAccent,
child: Center(
child: ListView(
children: docIds,
),
));
}
}
Since Text is only recognized in Flutter, it doesn't work for Firebase. Try using a String with the Text's value instead or create a map with each independent variable in Text such as the font size.

The getter 'uid' was called on null

Guys I have this login page with email and password and when the user is succesfully logged it takes him to a chatscreen. On the chatscreen I need to access users uid to check who is sending the message, but I am getting the "The getter 'uid' was called on null.". I am using bloc pattern to login with validators. How can I fix this situation?
class LoginScreen extends StatefulWidget {
FirebaseUser user;
#override
_LoginScreenState createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final _loginBloc = LoginBloc();
#override
void initState() {
super.initState();
_loginBloc.outState.listen((state) async {
switch (state) {
case LoginState.SUCCESS:
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => ComercialUserScreen()));
break;
case LoginState.FAIL:
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Erro'),
content: Text('Revise seu email e senha'),
));
break;
case LoginState.LOADING:
case LoginState.IDLE:
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder<LoginState>(
stream: _loginBloc.outState,
initialData: LoginState.LOADING,
// ignore: missing_return
builder: (context, snapshot) {
print(snapshot.data);
switch (snapshot.data) {
case LoginState.LOADING:
return Center(
child: CircularProgressIndicator(),
);
case LoginState.FAIL:
case LoginState.SUCCESS:
case LoginState.IDLE:
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
InputField(
icon: Icons.person_outline,
hint: 'Usuário',
obscure: false,
stream: _loginBloc.outEmail,
onChanged: _loginBloc.changeEmail,
),
InputField(
icon: Icons.lock_outline,
hint: 'Senha',
obscure: true,
stream: _loginBloc.outEmail,
onChanged: _loginBloc.changePassword,
),
SizedBox(
height: 32,
),
StreamBuilder<bool>(
stream: _loginBloc.outSubmitValid,
builder: (context, snapshot) {
return RaisedButton(
child: Text("Entrar"),
onPressed:
snapshot.hasData ? _loginBloc.submit : null,
);
})
],
);
}
}),
);
}
}
class LoginBloc extends BlocBase with LoginValidators{
FirebaseUser _currentUser;
final _emailController = BehaviorSubject<String>();
final _passwordController = BehaviorSubject<String>();
final _stateController = BehaviorSubject<LoginState>();
Stream<String> get outEmail => _emailController.stream.transform(validateEmail);
Stream<String> get outPassword =>_passwordController.stream.transform(validatePassword);
Stream<LoginState> get outState => _stateController.stream;
Stream<bool> get outSubmitValid => Observable.combineLatest2(
outEmail, outPassword, (a, b) => true);
Function(String) get changeEmail => _emailController.sink.add;
Function(String) get changePassword => _passwordController.sink.add;
StreamSubscription _streamSubscription;
LoginBloc(){
_streamSubscription = FirebaseAuth.instance.onAuthStateChanged.listen((user) async {
if(user != null) {
if(await verifyAdmins(user)) {
_stateController.add(LoginState.SUCCESS);
} else {
FirebaseAuth.instance.signOut();
_stateController.add(LoginState.FAIL);
}
} else {
_stateController.add(LoginState.IDLE);
}
});
}
void submit (){
final email = _emailController.value;
final password = _passwordController.value;
_stateController.add(LoginState.LOADING);
FirebaseAuth.instance.signInWithEmailAndPassword(
email: email,
password: password
).catchError((e) {
_stateController.add(LoginState.FAIL);
});
}
Future<bool> verifyAdmins (FirebaseUser user) async {
return await Firestore.instance.collection('users').document(user.uid).get().then((doc)
{
if(doc.data != null){
return true;
} else {
return false;
}
}).catchError((e) {
return false;
});
}
#override
void dispose() {
_emailController.close();
_passwordController.close();
_stateController.close();
_streamSubscription.cancel();
// TODO: implement dispose
}
}
class _AdminChatState extends State<AdminChat> {
bool _isLoading = false;
void _sendMessage({String text, File imgFile}) async {
DocumentSnapshot snapshot;
FirebaseUser user = await FirebaseAuth.instance.currentUser();
Map<String, dynamic> data = {
"uid" : user.uid,
'name' : user.displayName,
'photo' : user.photoUrl,
'time' : Timestamp.now()
};
if (imgFile != null){
StorageUploadTask task = FirebaseStorage.instance.ref().child('users').child(
DateTime.now().millisecondsSinceEpoch.toString()
).putFile(imgFile);
setState(() {
_isLoading = true;
});
StorageTaskSnapshot taskSnapshot = await task.onComplete;
String url = await taskSnapshot.ref.getDownloadURL();
data['imgUrl'] = url;
setState(() {
_isLoading = false;
});
}
if(text != null) data["text"] = text;
Firestore.instance.collection("users").document(snapshot.documentID)
.collection('messages').add(data);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('hello'),
),
body: Column(
children: <Widget>[
Expanded(
child: StreamBuilder<QuerySnapshot>(
stream:
Firestore.instance.collection('users').document(widget.snapshot.documentID)
.collection('messages').orderBy('time').snapshots(),
builder: (context, snapshot){
switch(snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
return Center(
child: CircularProgressIndicator(),
);
default:
List<DocumentSnapshot> documents =
snapshot.data.documents.reversed.toList();
return ListView.builder(
itemCount: documents.length,
reverse: true,
itemBuilder: (context, index){
// ignore: unrelated_type_equality_checks
return ChatMessage(documents[index].data, true);
});
}
},
),
),
_isLoading ? LinearProgressIndicator() : Container(),
TextComposer(_sendMessage),
],
),
);
}
}
The command await FirebaseAuth.instance.currentUser(); in the _AdminChatState doesn't return any data so when you try to insert data based on the user is returns null. This happens because you can't use await while the state hasn't initialized. If that happened the entire class would wait with nothing show until the user was returned. Furthermore, you use a storage query with the same logic. If you want a state widget to run something when it starts up you can use the initState() function. If you want do display a progress bar while waiting for the data to return a FutureBuilder or StreamBuilder are great choices.

Resources