I made a list of documents in firestore in firebase with image , title , price and number(index of doc) property when I write values in property in firestore they didint update all in application some of them are miss as NULL in consell run .
to fix it :
to fix the issue is to make update in value again so will fix but who will update it so many time.
The full code is :
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
void main() => runApp(MaterialApp(
home: Home(),
));
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
void initState() {
// TODO: implement initState
super.initState();
GetDatafromFirebase();
}
GetDatafromFirebase() {
Firestore.instance
.collection('DesginStore')
.document('Logo')
.collection('sub')
.orderBy('number',descending: false)
.snapshots()
.listen((data) =>
data.documents.forEach((doc) => print("Title is :${doc["title"]} And for order number is ${doc["number"]}")));
}
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Text('Fortesting'),
],
);
}
}
the result in app is(video) :
https://drive.google.com/file/d/1FCKI3W5XTmVgTbtXKpYLHMrzAFya7AgQ/view?usp=sharing
I would suggest familiarizing with some of the architectures used by other developers.
https://codewithandrea.com/videos/2020-02-10-starter-architecture-flutter-firebase/
or taking a look at this
Flutter - Get array from Firestore with FutureBuilder (using query)
Only difference would be that you need to call setState on listening to changed data.
Related
So, I'm building an app with MultiProviders that are on the root of the application above MaterialApp() (So that the value is available in all of the sub-widgets and subsequent pages that I navigate to also).
For my main function:
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(const MyApp());
}
Where as the MyApp class is:
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
DarkThemeProvider themeChangeProvider = DarkThemeProvider();
final Future<FirebaseApp> _firebaseApp = Firebase.initializeApp();
void getCurrentAppTheme() async {
themeChangeProvider.darkTheme =
await themeChangeProvider.darkThemePreference.getTheme();
}
#override
void initState() {
super.initState();
getCurrentAppTheme();
}
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (_) => themeChangeProvider,
),
ChangeNotifierProvider(
create: (_) => VideoPlayService.instance(),
),
ChangeNotifierProvider(
create: (_) => FirestoreAPI.instance(),
),
ChangeNotifierProvider(
create: (_) => AuthUtil.instance(),
),
ChangeNotifierProvider(
create: (_) => PageSwitcher.instance(),
),
],
child: Consumer5(
builder: (
ctx,
DarkThemeProvider darkThemeProvider,
VideoPlayService videoPlayService,
FirestoreAPI firestoreAPI,
AuthUtil user,
PageSwitcher switcher,
child,
) {
return MaterialApp(
debugShowCheckedModeBanner: false,
themeMode: Provider.of<DarkThemeProvider>(ctx).getCurrentTheme,
theme: DarkThemeProvider.customLightTheme,
darkTheme: DarkThemeProvider.customDarkTheme,
home: FutureBuilder(
future: _firebaseApp,
builder: (cx, snapshot) {
if (snapshot.hasError) {
return const Center(
child: Text('Could not connect to firebase'),
);
} else if (snapshot.connectionState == ConnectionState.done) {
return const Wrapper();
} else {
return const SplashScreen();
}
},
),
);
},
),
);
}
}
I've used future builder to initialize the app with Firebase but the issue is the provider AuthUtil and FirestoreAPI are both dependent on firebase, now if I were to remove these both and add them as a child to the Future builder(Above Wrapper) I would lose the access to the provider in my whole app (Since the provider will not be at the root anymore, It will only be available for the first child only and whenever I'll navigate to another page it will not be available).
And in the given code as I've written the firebase is not initialized but the Provider's instance is created first hence throwing an exception that firebase was not initialized, I also need to be able to show the SplashScreen while Firebase is initializing, I tried to wrap my head around future providers but I'm not sure how they work or even, if they are relevant to my case.
In summary I want to initialize the firebase first, then create the providers of AuthUtil and FirestoreAPI class while maintaining the scheme of showing splash screen or Error screen if firebase initialization fails and keeping the providers at the root (for the record I did try making a FutureBuilder at root but the It didn't make sense and I had to have multiple MaterialApp callings for the snapshot's cases which is just nonsense).
If anybody can help me solve this as soon as possible...? It would be a great save.
I typically call initializeApp in my main method:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(const MyApp());
}
While the initializeApp call is asynchronous, it is really just a call to the underlying native SDK where it is synchronous. So we're talking about
waiting for nano-seconds here, not tens to hundreds of milliseconds where a FutureBuilder would be meaningful.
In my Firestore DB, inside 'location' collection i have 2 docs,(named as Europe,Australia) having a field 'name' with their string values (same as their document names).
I have worked with StreamBuilder and Streams before, but this time i dont want real-time calls, but just once.
I wanna print that 'name' field data of all the docs inside location collection.
This is what my UI code looks like:
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
double spaceInBetween = 25;
#override
Widget build(BuildContext context) {
DatabaseService().getData();
return Scaffold(
body: Container(
child: Text("data here")
);
}
I wanna print all that documents data, with all their names using ListView.builder() on the HomePage.
This is my DatabaseService class (using the official FlutterFire Docs https://firebase.flutter.dev/docs/firestore/usage/ but didnt find what i was looking for)
class DatabaseService {
final locationCollection = FirebaseFirestore.instance.collection("location");
getData() async {
await locationCollection.get().then((QuerySnapshot querySnapshot) {
querySnapshot.docs.forEach((doc) {
print(doc['name']);
});
});
}
}
Also wanted to know if there's any other way i could do this, using FutureBuilder or anything else, just wanna get field data from all docs in a collection from Firestore and print it (im still learning).
Thank you :)
I think the answer is FutureBuilder. You can create a Future method which is going to get datas from Firebase servers and return it. After that you just create a FutureBuilder which is going to help you to show datas and if something wrong with the server or the internet connection you will not get any error messages because FutureBuilder will show an CircularProgressIndicator.
I made a demo code for you to demostrate FutureBuilder.
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final locationCollection = FirebaseFirestore.instance.collection("location");
#override
void initState() {
super.initState();
}
Future<List<String>> getData() async {
List<String> name = [];
await locationCollection.get().then((QuerySnapshot querySnapshot) {
querySnapshot.docs.forEach((doc) {
name = doc['name'];
});
});
return name;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Center(
child: FutureBuilder<List<String>>(
future: getData(), // call getData method
builder: (context, snapshot) {
List<String> nameList = snapshot.data ?? []; // create a local variable which is storing data from the getData method
if (snapshot.hasError) print(snapshot.error);
return snapshot.hasData
? ListView.builder( // if getData method give datas listviewbuilder is going to show datas
itemCount: nameList.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(nameList[index]),
);
},
)
: Center(child: CircularProgressIndicator()); // if something wrong with the server or with the internet you will see a CircularProgressIndicator
}),
),
),
);
}
}
In order to ensure you only get the data once, you can use a FutureBuilder and ensure you define the future outside the build method (for example in the initState) so that it doesn't get called again whenever the build method is called.
FutureBuilder
...
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.
https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html
Update the getData method of your DatabaseService class to this below:
Future<List<String>> getData() async {
final QuerySnapshot locationDataSnapshot = await locationCollection.get();
final List<String> listOfNames = locationDataSnapshot.docs
.map((QueryDocumentSnapshot documentSnapshot) =>
documentSnapshot.data()['name'] as String)
.toList();
return listOfNames;
}
This code above fetches the list of documents from the location collection and maps them to a list of names, which is then returned.
You can then get define the future object to get this data in your initState and use it in your FutureBuilder like shown below:
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Future<List<String>> _nameListFuture;
#override
void initState() {
super.initState();
_nameListFuture = DatabaseService().getData();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<List<String>>(
future: _nameListFuture,
builder: (context, snapshot) {
if (snapshot.data == null) {
return Center(child: CircularProgressIndicator());
}
final List<String> nameList = snapshot.data;
return ListView.builder(
itemCount: nameList.length,
itemBuilder: (context, index) => Text(nameList[index]),
);
},
),
);
}
}
So I wanted to add firebase_auth to my app and I ended up here https://firebase.flutter.dev/docs/overview/#initializing-flutterfire
So when I copy the code to set it up (I followed both of the approaches that they give but in this case, I am using the Stateful Widget one) the next error pops up.
══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
The following assertion was thrown building FirebaseLoading:
MediaQuery.of() called with a context that does not contain a MediaQuery.
No MediaQuery ancestor could be found starting from the context that was passed to MediaQuery.of().
This can happen because you do not have a WidgetsApp or MaterialApp widget (those widgets introduce
a MediaQuery), or it can happen if the context you use comes from a widget above those widgets.
The context used was:
Scaffold
The relevant error-causing widget was:
FirebaseLoading
lib\main.dart:63
When the exception was thrown, this was the stack:
Apparently, somehow, the context that this widget creates doesn't have a MediaQuery so when the children (FirebaseLoading()) tries to access it to display a loading message it can't:
Here is the code of FirebaseLoading() btw:
import 'package:flutter/material.dart';
class FirebaseLoading extends StatelessWidget {
const FirebaseLoading({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text("Loading"),
),
);
}
}
And here is the main class where FirebaseLoading is called:
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:qrcode_test/Models/Cart.dart';
import 'package:qrcode_test/Models/User.dart';
import 'Views/Controller.dart';
import 'Views/FirebaseError.dart';
import 'Views/FirebaseLoading.dart';
void main() {
// WidgetsFlutterBinding.ensureInitialized();
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(builder: (context) => Cart(bundles: [])),
ChangeNotifierProvider(builder: (context) => User()),
],
child: MyApp(),
),
);
}
class MyApp extends StatefulWidget {
_AppState createState() => _AppState();
}
class _AppState extends State<MyApp> {
// Set default `_initialized` and `_error` state to false
bool _initialized = false;
bool _error = false;
// Define an async function to initialize FlutterFire
void initializeFlutterFire() async {
try {
// Wait for Firebase to initialize and set `_initialized` state to true
await Firebase.initializeApp();
setState(() {
_initialized = true;
});
} catch (e) {
// Set `_error` state to true if Firebase initialization fails
setState(() {
_error = true;
});
}
}
#override
void initState() {
initializeFlutterFire();
super.initState();
}
#override
Widget build(BuildContext context) {
// Show error message if initialization failed
if (_error) {
return FirebaseError();
}
// Show a loader until FlutterFire is initialized
if (!_initialized) {
return FirebaseLoading();
}
return Controller();
}
}
I don't know if the multiproviders that I was using in the app can be causing any kind of problem but I don't think so.
Okay, it's not enought to call the widget over there, it has to be inside of a MaterialWidget like so:
#override
Widget build(BuildContext context) {
// Show error message if initialization failed
if (_error) {
return new MaterialApp(
title: "My Cashier",
theme: defaultTheme,
home: new FirebaseError(),
);
}
// Show a loader until FlutterFire is initialized
if (!_initialized) {
return new MaterialApp(
title: "My Cashier",
theme: defaultTheme,
home: new FirebaseLoading(),
);
}
return new MaterialApp(
title: "My Cashier",
theme: defaultTheme,
home: new Controller(),
);
}
There is for sure a way not to repeat the MaterialApp, the title and the theme but I am tired...
I have created a Flutter project that has a home page with a bottom navigation bar. I used an IndexedStack as the body.
I'm trying to make my CustomList() a feed which shows the most recent documents.
I intend to use pagination too.
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
final widgetOptions = [
CustomList(),
Page2(),
Page3(),
Page4(),
];
int _selectedItemPosition = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: BottomNavigationBar(
//
currentIndex: _selectedItemPosition,
onPositionChanged: (index) => setState(() {
_selectedItemPosition = index;
}),
items: [
BottomNavigationBarItem(),
BottomNavigationBarItem(),
BottomNavigationBarItem(),
BottomNavigationBarItem()
],
),
body: IndexedStack(
index: _selectedItemPosition,
children: widgetOptions,
),
);
}
}
This is the code of my CustomList():
class CustomList extends StatefulWidget {
#override
_CustomListState createState() => _CustomListState();
}
class _CustomListState extends State<CustomList> {
#override
Widget build(BuildContext context) {
Future<Object> getData()
{
//get Data from server
}
return FutureBuilder<Object>(
future: getData(),
builder: (context, snapshot) {
if(snapshot.data != null)
{
if(snapshot.hasData)
{
//get Documents
}
return ListView.builder(
//
itemBuilder: (context , index) {
//return a widget that uses the data received from the snapshot
},
);
}
}
);
}
}
The issue is that every time I change the page using the bottom navigation bar, whenever I come back to my default page with the CustomList(), the FutureBuilder is fired again resulting in my list having duplicates. This is due to the CustomList() being initialized again.
How do I structure my code so that the FutureBuilder is executed only once and isn't fired repeatedly when I use the BottomNavigationBar to change the page?
This is because you get a new future every time build is called, because you pass a function call to the FutureBuilder and not a reference that stays the same.
There are several easy options to solve this.
You can store a reference to the future and pass this reference to the FutureBuilder
You can use an AsyncMemoizer from the async package to only run the future once https://api.flutter.dev/flutter/package-async_async/AsyncMemoizer-class.html
You can use the FutureProvider from the provider package https://pub.dev/documentation/provider/latest/provider/FutureProvider-class.html
I am developing an app with firestore. In my app main screen i am calling firestore listener in initState(). when i am going next screen (Screen B) then the previous screen (main screen) initState() function
executing and interrupting listeners in screen B. I am not calling main screen initState() in Screen B.
Why it showing ?
Below shown as the code.
class MainScreen extends StatelessWidget {
MainScreen({Key key}): super(key: key);
//The title want to My App
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'My App',
home: MyMainPage(title: 'MyApp',));
}
}
class MyMainPage extends StatefulWidget {
MyMainPage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyMainPageState createState() => new _MyMainPageState();
}
class _MyMainPageState extends State<MyMainPage> with TickerProviderStateMixin {
_MyMainPageState();
#override
void initState(){
super.initState();
loadData();
}
loadData(){
widget.authentication.getFirestore().collection("xxx").document("xxx").collection("xxx")
.snapshots().listen((data){
debugPrint("Entered in _loadData firebase fn in MainScreen");
});
}
#override
Widget build(BuildContext context) {
return new new Scaffold(
backgroundColor: Colors.white,
appBar: new AppBar(
iconTheme: IconThemeData(
color: Colors.black, //change font color here
),
),
body:new RaisedButton(child:new Text("click),onPressed: () {redirectToScreenB();})
}
redirectToScreenB(
Navigator.push(context,MaterialPageRoute(builder: (context) => ScreenB()));
//I have used no change found
// Navigator.pushAndRemoveUntil(context,MaterialPageRoute(builder: (context) => ScreenB()),ModalRoute.withName('/'));
)
}
code for ScreenB
class ScreenB extends StatefulWidget {
ScreenBage({Key key, this.title}) : super(key: key);
final String title;
#override
ScreenBPageState createState() => new ScreenBPageState();
}
class ScreenBPageState extends State<ScreenB> with TickerProviderStateMixin {
ScreenBPageState();
#override
void initState(){
super.initState();
debugPrint("Screen B initState");
loadSecondData();
}
loadSecondData(){
debugPrint("loadSecondData started");
widget.authentication.getFirestore().collection("xxx").document("uid")
.collection("data").where("name", isEqualTo:"xxxx")
.snapshots().listen((data){
debugPrint("Entered in loadSecondData firebase fn in ScreenB");
});
}
The out put is coming when going to ScreenB
Screen B initState
loadSecondData started
Entered in _loadData firebase fn in MainScreen
And the loadSecondData() is stoping . Why the previous page listener is loading in new page.
I have tried StreamBuilder but not loadSecondData() without interrupt.
It seems that you are loading the same data in screenB from screenA.
Your code is the same for both loadData() and loadSecondData(). You should change one of them.
loadData()
{
widget.authentication.getFirestore().collection("xxx").document("xxx").collection("xxx").snapshots().listen((data) {
debugPrint("Entered in _loadData firebase fn in MainScreen");
});}
loadSecondData()
{
debugPrint("loadSecondData started");
widget.authentication.getFirestore().collection("xxx").document("xxx").collection("xxx").snapshots().listen((data){
debugPrint("Entered in _loadData firebase fn in MainScreen");
});