Strange FirebaseAuth.instance.onAuthStateChanged is not updating streambuilder - flutter

I am using firebase authetication in flutter application.When user signup or login i can see that FirebaseAuth.instance.onAuthStateChanged is called
but Streambuilder is not updating the widget. I also noticed that sometime FirebaseAuth.instance.onAuthStateChanged is not even called after user login. But when i reload the screen or rerun the app i can see that user is logged in. Below is my streambuilder code.
return StreamBuilder<FirebaseUser>(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (ctx, userSnapshot) {
if (userSnapshot.hasData) {
print(userSnapshot.data);
print('data changed');
return FutureBuilder(
future: Future.delayed(Duration(seconds: 1)),
builder: (ctx, asyncdata) {
if (asyncdata.connectionState == ConnectionState.done) {
print('user has data');
return UserList();
} else {
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
},
);
} else {
print('load auth screen');
return AuthScreen();
}
},
);

Probably you do signIn with a FirebaseAuth that you don't assign in onAuthStateChanged, so you should do like this:
_auth = FirebaseAuth.instance;
///
StreamBuilder<FirebaseUser>(
stream: _auth.onAuthStateChanged,
///
signIn() async {
var result = await _auth.signInWithCredential(credential);
}

Related

LateInitializationError: Field 'database' has not been initialized

I’m building an app, so far everything works. Until I click on a button that calls this Staful Widget:
class ToDo1 extends StatefulWidget {
#override
_ToDo1State createState() => _ToDo1State();
}
class _ToDo1State extends State<ToDo1> {
var User;
late DatabaseService database;
Future<void> connectToFirebase() async{
await Firebase.initializeApp();
final FirebaseAuth auth = FirebaseAuth.instance;
UserCredential result = await FirebaseAuth.instance.signInAnonymously();
User = result.user;
database = DatabaseService(User.uid);
if (!(await database.checkIfUserExists())) {
database.setTodo('To-Do anlegen', false);
}
}
void toggleDone(String key, bool value) {
database.setTodo(key, !value);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Center(
child: Text(
'Stufe 1',
style: TextStyle(
fontStyle: FontStyle.italic,
decoration: TextDecoration.underline),
)),
backgroundColor: Color.fromRGBO(35, 112, 192, 1),
),
body: FutureBuilder(
future: connectToFirebase(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else {
return StreamBuilder<DocumentSnapshot> (
stream: database.getTodos(),
builder: (context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if(!snapshot.hasData) {
return CircularProgressIndicator();
} else {
Map<String, dynamic> items = snapshot.data!.data as Map<String, dynamic>;
return ListView.separated(
separatorBuilder: (BuildContext context, int index) {
return SizedBox(
height: 10,
);
},
padding: EdgeInsets.all(10),
itemCount: items.length,
itemBuilder: (BuildContext, i) {
String key = items.keys.elementAt(i);
return ToDoItem(
key,
items[key]!,
() => toggleDone(key, items[key]),
);
});
}
}
);
}
},
)
);
}
}
Then I am confronted with the following error:
The following LateError was thrown building FutureBuilder<void>(dirty, state: _FutureBuilderState<void>#bc115):
LateInitializationError: Field 'database' has not been initialized.
This is the class that interacts with the firebase:
class DatabaseService {
final String userID;
DatabaseService(this.userID);
final CollectionReference userTodos =
FirebaseFirestore.instance.collection('userTodos');
Future setTodo(String item, bool value) async {
return await userTodos.doc(userID).set(
{item:value}, SetOptions(merge: true));
}
Future deleteTodo(String key) async {
return await userTodos.doc(userID).update(
{key: FieldValue.delete(),}
);
}
Future checkIfUserExists() async {
if((await userTodos.doc(userID).get()).exists) {
return true;
}
else {
return false;
}
}
Stream<DocumentSnapshot> getTodos() {
return userTodos.doc(userID).snapshots();
}
}
I hope I have provided all the necessary data so that the problem can be solved. If not, just write it to me and I will try to send you the material you need.
Let try changing this code from
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else {
to
if (snapshot.connectionState != ConnectionState.done) {
return Center(child: CircularProgressIndicator());
} else {
In async.dart file you can see:
/// The state of connection to an asynchronous computation.
///
/// The usual flow of state is as follows:
///
/// 1. [none], maybe with some initial data.
/// 2. [waiting], indicating that the asynchronous operation has begun,
/// typically with the data being null.
/// 3. [active], with data being non-null, and possible changing over time.
/// 4. [done], with data being non-null.
///
/// See also:
///
/// * [AsyncSnapshot], which augments a connection state with information
/// received from the asynchronous computation.
enum ConnectionState {
/// Not currently connected to any asynchronous computation.
///
/// For example, a [FutureBuilder] whose [FutureBuilder.future] is null.
none,
/// Connected to an asynchronous computation and awaiting interaction.
waiting,
/// Connected to an active asynchronous computation.
///
/// For example, a [Stream] that has returned at least one value, but is not
/// yet done.
active,
/// Connected to a terminated asynchronous computation.
done,
}
You will miss ConnectionState.none and ConnectionState.active if you just compare with ConnectionState.waiting, so the Future isn't completed when you call .todos() in your stream and it will cause the issue.
I suggest you to check this answer in which they have the exact same issue as you do:
Future Builders are built even before getting the data. So, you should check whether it has data.
In that answer they suggest to use FutureBuilder in the following:
FutureBuilder<Position>(
future: getInitialPosition(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Map(snapshot.data);
}else{
return CircularProgressIndicator();
//Display loading, you may adapt this widget to your interface or use some state management solution
}
}
)

Why does my async method run twice in Flutter?

I want to load a static list data when entering indexScreen,but the list sometimes show twice the same content,sometimes not.
This is my list setting:List<ListClass> listItems=List<ListClass>();,ListClass is a simple class with on different attributes and a constructor.
I use home:IndexScreen() in main.dart to show Index page.
return MaterialApp(
home: IndexScreen(),
debugShowCheckedModeBanner: false,
onGenerateRoute: router.generator,
builder: EasyLoading.init(),
);
And before this page build,it will update listItems using:
Future<bool> initUserAndIndex() async{
if (curUserEmail==null) sharedGetData(USER_EMAIL).then((value) => curUserEmail=value.toString());
print(curUserEmail);
await UserTable().getUserInfo(curUserEmail).then((value){print("user ok");});
await CollectionTable().getIndexList().then((value){print("Collection ok");return true;});
return null;
}
buildPage:
#override
Widget build(BuildContext context) {
return FutureBuilder<Object>(
future: initUserAndIndex(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState==ConnectionState.waiting)
{
EasyLoading.show(status: 'loading...');
// avoid no return,this cause a whiteborad transition,I don't know how to solve it,too.
return Container();
}
else
{
EasyLoading.dismiss();
return SafeArea(
child: Scaffold(
// the listItems is used in Body()
body: Body(),
),
);
}
},
);
}
}
I run this app,and it prints twice user ok and Collection ok.But when I use ROUTER.NAVIGETE,it only prints once.
User Information is OK,but the list is such a great problem--the page shows twice content
I put my code at an order of relevance of this prblom,I think.Next I put my the two awaited funtion here:
User:
Future<bool> getUserInfo(String userEmail) async{
await userCollection.where({'userEmail':userEmail}).get().then((res) async {
//assign to the static variables
return true;
});
return null;
}
Collection:
Future<bool> getIndexList() async {
listItems.clear();
await listCollection.get().then((value){
var v = value.data;
for (var data in v) {
//get data and package them,add after the listItems list.
listItems.add(ListClass(header, content, userId, favorCount, wordCount));
}
return true;
});
}
You probably want to assign your future in your widget class, but not in the build method as the documentation show, otherwise, everytime your build method is triggered, it will call again your FutureBuilder.
final Future<String> _calculation = Future<String>.delayed(
const Duration(seconds: 2),
() => 'Data Loaded',
);
#override
Widget build(BuildContext context) {
return DefaultTextStyle(
style: Theme.of(context).textTheme.headline2!,
textAlign: TextAlign.center,
child: FutureBuilder<String>(
future: _calculation, // a previously-obtained Future<String> or null
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
// ...
}
),
),
}

Flutter MaterialApp home based on futureBuilder

When my app starts up, it detects whether the user is logged in or not (with firebase) and based on this check, it shows the homepage or the login page. Up to now everything is fine, but I would like to add one more layer.
The user can login as normal user or as admin.
So the check should be not only on the authentication state, but also on the "level" of the user, and show different pages, based on the user level.
I get the user level with a query on the firestore database, so it's a Future.
This is the code i'm using:
final usersCollection = FirebaseFirestore.instance.collection('users');
User loggedUser = FirebaseAuth.instance.currentUser;
Future<InfoUtente> userInfo;
String livelloLogin;
// here I get the user from the firestore database, based on the authenticated user id
Future<InfoUtente> fetchInfoUtente() async {
final response = await usersCollection
.where(
'uid',
isEqualTo: loggedUser.uid,
)
.get();
return InfoUtente.fromFireStore(response.docs.first);
}
// here I return the page based on the user authentication "level"
Future<Widget> widgetChoice() async {
if (!isLogged)
return LoginNewPage();
else {
userInfo.then(
(value) {
livelloLogin = value.loginLevel;
if (livelloLogin == 'struttura')
return StrutturaPage();
else
return MainPage();
},
);
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
// the homepage of the material app is a future builder
home: FutureBuilder(
future: widgetChoice(),
builder: (BuildContext context, AsyncSnapshot<Widget> widget) {
if (!widget.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
return widget.data;
},
),
);
}
something is not right because it always shows the circular progress indicator.
What am I doing wrong?
Is this the correct way of doing this or am I completely wrong?
If there is no data fetched or found, your screen will stuck on loading infinitely. Update your builder's implementation as
builder: (BuildContext context, AsyncSnapshot<Widget> widget) {
if(widget.connectionState == ConnectionState.done){
if (!widget.hasData) {
return Center(
child: Text('No Data exists')
);
}
return widget.data;
}
return Center(
child: CircularProgressIndicator(),
);
},
And update your widgetChoice as
Future<Widget> widgetChoice() async {
if (!isLogged)
return LoginNewPage();
else {
var userInfo = await fetchInfoUtente();
livelloLogin = userInfo.loginLevel;
if (livelloLogin == 'struttura')
return StrutturaPage();
else
return MainPage();
}
}
If i'm right you have to call the future function like that:
FutureBuilder(
future: widgetChoice,
Without ()

Flutter ChangeNotifier, duplicate entries to list inside initState method

I have a Provider in my iniState(){...} of my list view page. If logged in, the user is redirected to this page automatically. The issue is that the initState(){...} is being called more than once and my list view has duplicate entries from firestore. Here is the code I have tried.
It appears to be working on initial load, but if I hot restart it duplicates the items in it
MaterialApp
home: FutureBuilder(
future: Provider.of<AuthService>(context, listen: false).getUser(),
builder: (context, AsyncSnapshot<User> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.error != null) {
print("error");
return Text(snapshot.error.toString());
}
return snapshot.hasData ? PropertyListPage() : WelcomePage();
} else {
return ProgressLoader(
text: 'Loading, please wait...',
);
}
}),
List Screen initState(){...}
#override
void initState() {
super.initState();
Provider.of<PropertyListViewModel>(context, listen: false)
.fetchAllProperties();
}
list view model (Future) to get listings
Future<void> fetchAllProperties() async {
User user = FirebaseAuth.instance.currentUser;
await propertyCollection
.where('emailAddress', isEqualTo: user.email)
.get()
.then((snapshot) {
snapshot.docs.asMap().forEach(
(key, queryDocumentSnapshot) {
properties.add(Property.fromSnapshot(queryDocumentSnapshot));
},
);
});
notifyListeners();
}
This is where I set the ChangeNotifier
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(ChangeNotifierProvider<AuthService>(
create: (context) => AuthService(),
child: MyApp(),
));
}

Error: Class 'String' has no instance getter 'token'. I/flutter ( 3268): Receiver: "dc9e0de8fa2eaa917657e810db06aad2458e4f65"

I have been struggling with this problem for like two days. My social media app should save its state, when signed in so that when you leave the app and come back again it should start from the home page, not the sign in page. I have found that it is possible to do this with StreamBuilder and FutureBuilder. I have tried some things with FutureBuilder and I have some errors.
Below is how my main page looks like:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (BuildContext context) => UserData(),
child: MaterialApp(
title: 'Curtain App',
debugShowCheckedModeBanner: false,
home: FutureBuilder(
future: SharedPreferencesHelper.getPrefs(),
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
}
if (snapshot.hasData) {
Provider.of<UserData>(context).currentUserId =
snapshot.data.token;
return HomeScreen();
} else {
return LoginScreen();
}
},
),
),
);
}
}
class SharedPreferencesHelper {
static final String _tokenCode = "token";
static Future<String> getPrefs() async {
final SharedPreferences preferences = await SharedPreferences.getInstance();
return preferences.getString(_tokenCode) ?? "empty";
}
}
And this is my LoginPage submit btn code:
_submit() async {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
// logging in the user w/ Firebase
//AuthService.login(_email, _password);
var user = await DatabaseService.loginUser(_username, _password);
final data = json.decode(user);
SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
print("Hi ${data['username']}");
print("Status ${data['status']}");
print("Token ${data['token']}");
if (data['username'] != null) {
setState(() {
_message = "Hi ${data['username']}";
sharedPreferences.setString('token', data['token']);
});
Navigator.of(context).pushAndRemoveUntil(
CupertinoPageRoute(
builder: (context) => HomeScreen(),
),
(Route<dynamic> route) => false);
}
}
}
Any ideas on how to solve this ?
Just remove the .token from the line where the error occurs. snapshot.data already is the token.