Bad state error when fetching data from firebase - flutter

Iā€™m fetching data from firebase but i have this error
Unhandled Exception: Bad state: field does not exist within the DocumentSnapshotPlatform
the fields are correct and there is no difference i checked them
this is my method
static final List<ProductModels> _products = [];
Future<void> fetchData() async {
await FirebaseFirestore.instance
.collection('products')
.get()
.then((QuerySnapshot productsSnapshot) {
for (var element in productsSnapshot.docs) {
_products.insert(
0,
ProductModels(
id: element.get('id'),
title: element.get('title'),
imageUrl: element.get('imageUrl'),
productcat: element.get('productCategoryName'),
price: element.get('price'),
salePrice: element.get('salePrice'),
isOnSale: element.get('isOnSale'),
size: element.get('Size'),
color: element.get('Color'),
));
}
});
notifyListeners();
}
and i call it here
class FetchScreen extends StatefulWidget {
const FetchScreen({Key? key}) : super(key: key);
#override
State<FetchScreen> createState() => _FetchScreenState();
}
class _FetchScreenState extends State<FetchScreen> {
#override
void initState() {
Future.delayed(Duration(microseconds: 5),() async{
final productsProvider = Provider.of<ProductProvider>(context,listen: false);
await productsProvider.fetchData();
Navigator.pushReplacement(context, MaterialPageRoute(builder: (context)=> BtmNavBarScreen()));
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Container();
}
}
there is no difference between the firebase and the code i checked each one

Related

Why Provider is not giving the initialized value instead of expected one?

I'm trying to display a document field value from firestore and want to display it on other pages using the provider.
This is my code inside the provider file:
class UserState extends ChangeNotifier {
String userName = 'default error';
Future<void> getName() async {
await FirebaseFirestore.instance
.collection("Users")
.doc(FirebaseAuth.instance.currentUser!.uid)
.get()
.then((value) {
userName = (value.data()?[' uname'] ?? "Default userName");
print(userName);
});
notifyListeners();
}
}
Here the correct userName value is getting printed using print statement, but when I try to pass it through the provider it is showing the initialized string value default error which I provided for null safety.
This is the screen where I want to display the variable userName :
class testscreen extends StatefulWidget {
const testscreen({Key? key}) : super(key: key);
_testscreenState createState() => _testscreenState();
}
class _testscreenState extends State<testscreen> {
#override
Widget build(BuildContext context) {
Provider.of<UserState>(context, listen: false).getName();
final String name = Provider.of<UserState>(context).userName;
return Scaffold(body: Text(name));
}
}
How can I show the right value instead of initialized value for userName?What's wrong with my code?
There are some fixes in the code.
notifyListeners() are not waiting for the fetch to complete
await FirebaseFirestore.instance
.collection("Users")
.doc(FirebaseAuth.instance.currentUser!.uid)
.get()
.then((value) {
userName = (value.data()?[' uname'] ?? "Default userName");
print(userName);
notifyListeners(); šŸ‘ˆ Try adding the notifyListeners() here
});
}
Remove the provider() call from within the bluid method
class testscreen extends StatefulWidget {
const testscreen({Key? key}) : super(key: key);
_testscreenState createState() => _testscreenState();
}
class _testscreenState extends State<testscreen> {
#override
void initState() {
super.initState();
Provider.of<UserState>(context, listen: false).getName();šŸ‘ˆ Call getName here
}
#override
Widget build(BuildContext context) {
return Scaffold(body: Text( Provider.of<UserState>(context).userName)); šŸ‘ˆ Call it this way
}
}
notifyListeners doesn't wait to finish the fetch. You can try
void getName() {
FirebaseFirestore.instance
.collection("Users")
.doc(FirebaseAuth.instance.currentUser!.uid)
.get()
.then((value) {
userName = (value.data()?[' uname'] ?? "Default userName");
print(userName);
notifyListeners();
});
}
Or
Future<void> getName() async {
final value = await FirebaseFirestore.instance
.collection("Users")
.doc(FirebaseAuth.instance.currentUser!.uid)
.get();
userName = (value.data()?[' uname'] ?? "Default userName");
notifyListeners();
}
I will prefer using Consumer widget for this case.
You can also use initState to call the method.
class testscreen extends StatefulWidget {
const testscreen({Key? key}) : super(key: key);
_testscreenState createState() => _testscreenState();
}
class _testscreenState extends State<testscreen> {
#override
void initState() {
super.initState();
Provider.of<UserState>(context, listen: false).getName();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Consumer<UserState>(// using consumer
builder: (context, value, child) {
return Text(value.userName);
},
),
);
}
}
Also you can call the method on constructor,
class UserState extends ChangeNotifier {
String userName = 'default error';
UserState() {
getName();
}
Future<void> getName() async {
await Future.delayed(Duration(seconds: 1));
userName = "Default userName";
notifyListeners();
}
}

How to use Future<bool> return value from function without async function (Flutter)

I wrote short flutter app that have a async function that get value from cloud firestore and return Future bool type according to the information from the database.
now in my main code I want to return a Widget according to the value that got return form that mention function, but I didn't succeed using the returned value right.
I tried to write an external async function that will call the first function and will put the returned value inside a shared variable. but it didn't work for me and got sometimes error about that variable isn't been initialized.
it's looked like the code is not stopping line by line and wait for my function to return the value when been called and continue on the if statement.
Someone know how can I return a widget according to the value that the function returning and fix that issue?.
I tried using block of code using then but I never use it before so it doesn't worked as well.
my fist function:
Future<bool> firstFunc(String uid) async {
DocumentSnapshot userData =
await FirebaseFirestore.instance.collection('Users').doc(uid).get();
bool value = userData["param"];
return value;
}
and this is my main code:
// imports
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: AuthenticationWrapper(),
);
}
}
class AuthenticationWrapper extends StatefulWidget {
const AuthenticationWrapper({Key? key}) : super(key: key);
#override
_AuthenticationWrapperState createState() => _AuthenticationWrapperState();
}
class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
final FirebaseAuth _auth = FirebaseAuth.instance;
late bool sharedVar;
void getValue(String uid) async {
bool value = await firstFunc(uid);
sharedVar = value;
}
#override
Widget build(BuildContext context) {
if (_auth.currentUser != null) {
getValue(_auth.currentUser!.uid);
if (sharedVar == true)
{
return BossHomePage();
}
else {
return ClientHomePage();
}
} else {
return const EnterPhonePage();
}
}
}
The reason it doesn't wait for getValue is that getValue is of type void.
Try this;
class AuthenticationWrapper extends StatefulWidget {
const AuthenticationWrapper({Key? key}) : super(key: key);
#override
_AuthenticationWrapperState createState() => _AuthenticationWrapperState();
}
class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
final FirebaseAuth _auth = FirebaseAuth.instance;
#override
Widget build(BuildContext context) {
if (_auth.currentUser != null) {
return FutureBuilder<bool>(
future: firstFunc(uid),
builder: (BuildContext context, snapshot){
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return const CircularProgressIndicator();
case ConnectionState.none:
return const Text('No data');
case ConnectionState.active:
case ConnectionState.done:
if (snapshot.data == true) {
return BossHomePage();
} else {
return ClientHomePage();
}
default:
return const Text('No data');
}
},
)
} else {
return const EnterPhonePage();
}
}
}
getValue is a future method, You need to use async at some point. You can convert this to
Future<bool> getValue(String uid) async {
bool value = ....;
return value;
}
And use FutureBuilder (or initState is better SatefullWidget) for this case
late bool sharedVar;
Future<void> getValue(String uid) async {
bool value =...;
sharedVar = value;
}
bool? currentUser;
_initData()async{
getValue("");
///all others methods
}
#override
void initState() {
super.initState();
_initData();
}
More about async-await

how to access SharedPrefrence value throughout the app? its value become null after i close the app?

My home page is Displayed Based on the UserType value that I set in SharedPreffrence, for the first time when I run The app it works fine and the home page is displayed Successfully because it knows the values of Usertype from SharedPreference, but when I close the app and run again The value of UserType become Null, due to this my home page is not Displayed because Usertype value is null, I set SharedPreference when the user login for The first time,
As you See in my code First I pass the value of UserType from SharedPreference, and also I Call Get() method in initState(), to Call SharedPrefrence.
Here is how I set SharedPreference when the user Login for the first time.
Future loginWithEmailAndPasswords(String email, String password,BuildContext context) async {
try {
UserCredential register = await _firebaseAuth.signInWithEmailAndPassword(
email: email, password: password);
User registredUser = register.user;
final customSnapshots= await customSnapshot.doc(registredUser.uid).get();
if(customSnapshots.exists){
SharedPreferences preferences= await SharedPreferences.getInstance() ;
preferences.setString("UserType", customSnapshots.data()['Account type'].toString());
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context)=>homecontroller(
controllUserType:preferences.getString('UserType'),
userid: customSnapshots.data()['userId'].toString(),))); }
}
Here is My HomeController Page.
class homecontroller extends StatelessWidget {
final String controllUserType;
final String userid;
const homecontroller({Key key,#required this.controllUserType,#required this.userid}):super(key: key);
#override
Widget build(BuildContext context) {
Authservice auth
final Authservice auth=Provider.of(context).auth;
return StreamBuilder(
stream:auth.authStateChanges,
builder: (context,AsyncSnapshot<String>snapshot){
if(snapshot.connectionState==ConnectionState.active){
final bool SignedIn=snapshot.hasData;
return SignedIn?HomePage(UserType:controllUserType,userID: userid,):firstview();
}else{
return CircularProgressIndicator();
}
},
);
}
}
Here is My home page
class HomePage extends StatefulWidget {
final String UserType;
final String userID;
const HomePage({Key key,#required this.UserType,#required this.userID}):super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
bool isSignedIn= false;
String owneruerID;
dynamic uploadusertypes;
List<Widget>_children;
void initState(){
super.initState();
uploadusertypes= widget.UserType; //this value is passed from SharedPreference when the user login for the first time
owneruerID = widget.userID;//this value is passed from SharedPreference when the user login for the first time
GetData(); // here is Where i Call SharedPreference Method
_children=[
TimeLinePage(),
SearchPage(), //search(),
UploadPage(UserSID:owneruerID,uploadusertypes:uploadusertypes),
NotificationsPage(),
ProfilePage(userProfileID:widget.userID),
];
if(FirebaseAuth.instance.currentUser!=null){
setState(() {
isSignedIn= true;
});
}else{
setState(() {
isSignedIn= false;
});
}
}
#override
Widget build(BuildContext context) {
if(isSignedIn){
if(widget.UserType== 'Customer')
{
return Scaffold(
body: WillPopScope(
onWillPop: onwillpops,
child: buildHomeScreen()));
}
}
void GetData()async {
SharedPreferences preferences= await SharedPreferences.getInstance();
setState(() {
widget.UserType=preferences.getString('UserType');
});
}
}
I think first you should call GetData() before the pass shared preference values to variables ( EX - UserType). Then you could get the UserType value and then could pass that value to uploadusertypes.
Try the following code.
class HomePage extends StatefulWidget {
final String UserType;
final String userID;
const HomePage({Key key,#required this.UserType,#required this.userID}):super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
bool isSignedIn= false;
String owneruerID;
dynamic uploadusertypes;
List<Widget>_children;
void initState(){
super.initState();
GetData(); // you should call this one first.
uploadusertypes= widget.UserType; //this value is passed from SharedPreference when the user login for the first time
owneruerID = widget.userID;//this value is passed from SharedPreference when the user login for the first time
_children=[
TimeLinePage(),
SearchPage(), //search(),
UploadPage(UserSID:owneruerID,uploadusertypes:uploadusertypes),
NotificationsPage(),
ProfilePage(userProfileID:widget.userID),
];
if(FirebaseAuth.instance.currentUser!=null){
setState(() {
isSignedIn= true;
});
}else{
setState(() {
isSignedIn= false;
});
}
}
#override
Widget build(BuildContext context) {
if(isSignedIn){
if(widget.UserType== 'Customer')
{
return Scaffold(
body: WillPopScope(
onWillPop: onwillpops,
child: buildHomeScreen()));
}
}
void GetData()async {
SharedPreferences preferences= await SharedPreferences.getInstance();
setState(() {
widget.UserType=preferences.getString('UserType');
});
}
}
Hope this will solve your problem.
what I am Doing is Just I Remove both uploadusertypes and owneruerID from InitState() and I Declared Them Inside Getdata(), it works but i don't know weather it have effect on my code or not,
uploadusertypes= widget.UserType;
owneruerID = widget.userID;
Here is what I am doing
void initState(){
GetData();
super.initState();
_children=[
TimeLinePage(),
SearchPage(), //search(),
UploadPage(UserSID:owneruerID,uploadusertypes:uploadusertypes),
NotificationsPage(),
ProfilePage(userProfileID:widget.userID),
];
_agentchildren=[
TimeLinePage(),
SearchPage(), //search(),
NotificationsPage(),
ProfilePage(userProfileID:owneruerID),
];
if(FirebaseAuth.instance.currentUser!=null){
setState(() {
isSignedIn= true;
});
}else{
setState(() {
isSignedIn= false;
});
}
uploadusertypes= widget.UserType;
owneruerID = widget.userID;
}
void GetData()async {
SharedPreferences preferences= await SharedPreferences.getInstance();
setState(() {
uploadusertypes=preferences.get('UserType');
widget.UserType=preferences.get('UserType');
});
}

Return async value to initState()

I've the below code that is working fine, reading the csv data from url and printing the output:
import 'package:flutter/material.dart';
import 'dart:convert';
import 'dart:io';
import 'dart:async';
import 'package:csv/csv.dart';
void fetchUserData() async {
final request = await HttpClient().getUrl(Uri.parse(
'https://docs.google.com/spreadsheets/d/e/2PACX-1vQvf9tp4-fETDJbC-HRmRKvVFAXEAGO4lrYPpVeiYkB6nqqXdSs3CjX0eBMvjIoEeX9_qU6K2RWmzVk/pub?gid=0&single=true&output=csv'));
final response = await request.close();
List<List<dynamic>> rowsAsListOfValues;
await for (final csvString in response.transform(const Utf8Decoder())) {
rowsAsListOfValues =
const CsvToListConverter().convert(csvString);
}
print(rowsAsListOfValues);
}
class _MyHomePageState extends State<MyHomePage> {
#override
void initState() {
super.initState();
fetchUserData();
}
#override
Widget build(BuildContext context) { // ... // }
}
Instead of getting the output printed, I need it to be returned into a variable, which I can display in y widget, I tried to do it as below:
Future<List<List<dynamic>>> fetchUserData() async { /// change
final request = await HttpClient().getUrl(Uri.parse(
'https://docs.google.com/spreadsheets/d/e/2PACX-1vQvf9tp4-fETDJbC-HRmRKvVFAXEAGO4lrYPpVeiYkB6nqqXdSs3CjX0eBMvjIoEeX9_qU6K2RWmzVk/pub?gid=0&single=true&output=csv'));
final response = await request.close();
List<List<dynamic>> rowsAsListOfValues;
await for (final csvString in response.transform(const Utf8Decoder())) {
rowsAsListOfValues =
const CsvToListConverter().convert(csvString);
}
return rowsAsListOfValues; /// change
}
class _MyHomePageState extends State<MyHomePage> {
var rowsAsListOfValues; /// new
#override
void initState() {
super.initState();
rowsAsListOfValues = fetchUserData(); /// new
print(rowsAsListOfValues); /// new
}
#override
Widget build(BuildContext context) { // ... // }
}
But I got the output as I/flutter ( 7505): Instance of 'Future<List<List<dynamic>>>'
How can I fix it?
You need to switch from initState to didChangeDependency in this case. Because you need to await some process and you cant wait in initState. However you can wait like this
#override
void didChangeDependencies() async {
super.didChangeDependencies();
rowsAsListOfValues = await fetchUserData();
super.setState(() {}); // to update widget data
/// new
print(rowsAsListOfValues);
}
And this is the result
I/flutter (24313): [[vranches], [Dammam, 2], [Khobar, 3]]
You can wrap your code with Future.delayed() as given below.
#override
void initState() {
super.initState();
Future.delayed(Duration.zero,()async{
rowsAsListOfValues =await fetchUserData();
setState(() {});
print(rowsAsListOfValues); // this return correct value
});
print(rowsAsListOfValues); // this return null
}
Full Code
import 'package:flutter/material.dart';
import 'dart:convert';
import 'dart:io';
import 'dart:async';
import 'package:csv/csv.dart';
void main() {
runApp(App());
}
class App 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,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: HomePage(title: 'Flutter Demo Home Page'),
);
}
}
class HomePage extends StatefulWidget {
HomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_AppState createState() => _AppState();
}
Future<List<List<dynamic>>> fetchUserData() async {
final request = await HttpClient().getUrl(Uri.parse(
'https://docs.google.com/spreadsheets/d/e/2PACX-1vQvf9tp4-fETDJbC-HRmRKvVFAXEAGO4lrYPpVeiYkB6nqqXdSs3CjX0eBMvjIoEeX9_qU6K2RWmzVk/pub?gid=0&single=true&output=csv'));
final response = await request.close();
List<List<dynamic>> rowsAsListOfValues;
await for (final csvString in response.transform(const Utf8Decoder())) {
rowsAsListOfValues =
const CsvToListConverter().convert(csvString);
}
return rowsAsListOfValues;
}
class _AppState extends State<HomePage> {
var rowsAsListOfValues;
#override
void initState() {
super.initState();
Future.delayed(Duration.zero, () async {
rowsAsListOfValues = await fetchUserData();
setState(() {});
print(rowsAsListOfValues);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'$rowsAsListOfValues',
),
],
),
),
);
}
}
The initState method is synchronous, and does not support async. I recommend the use of FutureBuilder, but you can also move the code to an async function.
FutureBuilder
import 'package:flutter/material.dart' show
Widget, FutureBuilder, AsyncSnapshot
;
class _MyHomePageState extends State<MyHomePage> {
static Future<void> fetchUserData() {
return Future().delayed(
Duration(seconds: 10),
() => 'loaded'
);
}
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: Future.wait([
fetchUserData()
]),
builder: (
BuildContext context,
AsyncSnapshot snapshot
) {
if (snapshot.hasData) {
return Text(snapshot.data);
}
return Text('loading...');
}
);
}
}
Async function
#override
void initState () {
super.initState();
(() async {
rowsAsListOfValues = await fetchUserData();
print(rowsAsListOfValues);
})();
}
OR
#override
void initState() {
super.initState();
initLoad();
}
void initLoad() async {
rowsAsListOfValues = await fetchUserData();
print(rowsAsListOfValues);
}
I feel more relaxed when using then() with async functions. You can try this:
fetchUserData().then((value) {
setState(() {
rowsAsListOfValues = value;
});
});
Or you can use await like this.
#override
void initState() async {
super.initState();
rowsAsListOfValues = await fetchUserData();
print(rowsAsListOfValues);
}

Unhandled Exception: A class was used after being disposed. - Flutter

I'm getting this error
E/flutter ( 9610): [ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: A CustRegViewModel was used after being disposed.
E/flutter ( 9610): Once you have called dispose() on a CustRegViewModel, it can no longer be used.
I have a View named CustRegView where I take a phone number form the user and send it to ViewModel named CustRegViewModel to authenticate which returns bool based on its authentication status.
class CustRegView extends StatefulWidget {
#override
_CustRegViewState createState() => _CustRegViewState();
}
class CustRegView extends StatelessWidget{
final TextEditingController _controller = TextEditingController();
#override
Widget build(BuildContext context) {
final deviceSize = MediaQuery.of(context).size;
return BaseView<CustRegViewModel>(
builder: (context, model, child) => Scaffold(
...<some code>
FlatButton (
onPressed: () async {
var registerSuccess = await model.register( _controller.text, context);
// ^^^^^ HERE, IN ABOVE LINE, I'M GETTING AN ERROR ^^^^^^
if (registerSuccess) {
Navigator.pushNamed(context, 'newScreen');
} else {
UIHelper().showErrorButtomSheet(context, model.errorMessage);
}
)
}
CustRegViewModel looks like this
class CustRegViewModel extends BaseViewModel {
final AuthService _authService = locator<AuthService>();
final DialogService _dialogService = locator<DialogService>();
dynamic newUserResult;
dynamic verifyResult;
Future<bool> register(String phoneNo, BuildContext context) async {
await verifyPhone;
return verifyResult ? true : false; // From here it returns true
}
Future<void> verifyPhone(phoneNo) async {
await FirebaseAuth.instance.verifyPhoneNumber(
phoneNumber: updatedPhoneNo,
timeout: Duration(seconds: 50),
verificationCompleted: (AuthCredential authCred) async {...... <some code>
verificationFailed: (AuthException authException) {...... <some code>
codeSent: (String verID, [int forceCodeResend]) async {...... <some code>
codeAutoRetrievalTimeout: (String verID) {...
).catchError((error) {...... <some code>
}
}
BaseView looks like this
class BaseView<T extends BaseViewModel> extends StatefulWidget {
final Widget Function(BuildContext context, T model, Widget child) builder;
final Function(T) onModelReady;
BaseView({this.builder, this.onModelReady});
#override
_BaseViewState<T> createState() => _BaseViewState<T>();
}
class _BaseViewState<T extends BaseViewModel> extends State<BaseView<T>> {
T model = locator<T>();
#override
void initState() {
if (widget.onModelReady != null) {
widget.onModelReady(model);
}
super.initState();
}
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<T>(
create: (context) => model,
child: Consumer<T>(builder: widget.builder),
);
}
}
BaseViewModel looks like this
class BaseViewModel extends ChangeNotifier {
ViewState _state = ViewState.Idle;
ViewState get state => _state;
void setState(ViewState viewState) {
_state = viewState;
notifyListeners();
}
}
You may have used RegisterLazySingleton instead of Factory in your depencie injection.