Flutter Streambuilder not working as expected with Firebase - firebase

Not sure I am thinking about this right. According to my knowledge the Streambuilder is supposed to log you out if the user has been deleted from the backend which is Firebase.
The steps of what I am doing as of now -
loading the app
Signing in to the app
Loading firebase and deleting the signed in user from the backend.
I believe doing this would log me out from the app as well. Is that right?
Here is my code -
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MaterialApp(
theme: ThemeData(
accentColor: Colors.orange,
primarySwatch: Colors.blue,
),
home: StreamBuilder(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
print(FirebaseAuth.instance.authStateChanges());
if (snapshot.connectionState == ConnectionState.active) {
var user = snapshot.data;
if (user == null) {
return Welcome();
}
return Conversations("Logged in");
}
)
));
}

Firebase Authentication uses a combination of long-lived and short-lived tokens to manage login sessions, and it may take up to an hour before the short-lived token needs to be refresh and the client detects that the client is signed out.
If you waited for less time than an hour, that is probably the reason your authStateChanges() didn't fire with a new value: the token is still valid, so the client's auth state hasn't changed yet.
If you want to learn how to revoke the user's tokens, I recommend reading the documentation on that. Fair warning though: it is quite a bit more involved than simply signing in and out on the client.
If your goal is to be able to lock out users instantly, there are probably other ways to do that. For example, when using Firebase's Realtime Database or Firestore, it is quite common to keep a list of "blocked UIDs" in the database, and check against that in the security rules of your database.

When logging out by using signOut(), the state got updated right away, but it might not be the case when you delete the user.
The change might take a while to be notified to the stream at the front end. You can read more on that here: Firebase user deleted but still logged in on device
Firebase Authentication tokens are valid for one hour and cached on the user's device. It is automatically refreshed by the client SDK. Deleting the account doesn't proactively reach out to all the user's devices to delete the token.
You can try on this mini sign-in app with the provided signOut() method:
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.indigo,
),
home: MyApp(),
);
}
}
class LandingPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamBuilder<User>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
User user = snapshot.data;
if (user == null) {
return Welcome();
}
return Conversations();
} else {
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
},
);
}
}
class Welcome extends StatelessWidget {
Future<void> _signInAnonymously() async {
try {
await FirebaseAuth.instance.signInAnonymously();
} catch (e) {
print(e);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Sign in')),
body: Center(
child: RaisedButton(
child: Text('Sign in anonymously'),
onPressed: _signInAnonymously,
),
),
);
}
}
class Conversations extends StatelessWidget {
Future<void> _signOut() async {
try {
await FirebaseAuth.instance.signOut();
} catch (e) {
print(e); // TODO: show dialog with error
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home Page'),
actions: <Widget>[
FlatButton(
child: Text(
'Logout',
style: TextStyle(
fontSize: 18.0,
color: Colors.white,
),
),
onPressed: _signOut,
),
],
),
);
}
}

Related

What is the best way to create a Flutter/Firebase wrapper that auto-redirects based on auth status?

I am building a flutter app with a Firebase backend. The issue is redirecting the user to the Home Screen if they are signed in and to the Auth Screens if they are signed out. When a user signs out while at the home screen, they are automatically redirected to the auth pages, however, when they sign in, the app stays on that page and I have to restart for it to go to the home screen.
This is my app structure
main - Splash - Wrapper - Home/Auth
My wrapper code is
final auth = Provider.of<AuthService>(context);
return StreamBuilder<User?>(
stream: auth.user,
builder: (_, AsyncSnapshot<User?> snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
final User? user = snapshot.data;
return user == null ? AndroidAuth() : AndroidHome();
} else {
return Scaffold(
body: CircularProgressIndicator(),
);
}
},
);
}
my main where I initialize Firebase
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(const MyApp());
}
Material App is wrapped in multiprovider.
return MultiProvider(
providers: [
Provider<AuthService>(
create: (_) => AuthService(),
)
],
child: MaterialApp(
title: 'Flightbag',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const AndroidSplash(),
),
);
I get auth state changes like so:
Stream<User?>? get user {
return _auth.authStateChanges().map(_userFromFirebase);
}
And create User object like :
User? _userFromFirebase(auth.User? user) {
if (user == null) {
return null;
}
return User(user.uid, user.email);
}
Please help. I have been stuck on this for days.

How to keep login status with Flutter

Assumptions/what you want to achieve
Currently, I am learning about application development using Flutter+Firebase.
I want to implement the following functions as a part of the login function.
Transition to the login page and login only when logging in for the first time
Unless you log out, opening the app after exiting the app does not bring you to the login page
I did some research on my own and tried them, but they didn't work.
As a concrete method, how can the above functions be implemented?
Also, I am currently implementing a function that uses Firebase's Email and Password.
Is it a common method to implement the above functions even for Google login and Facebook login?
Since I am a new student of Flutter, I have many things that I do not understand, but I hope you can teach me.
Currently implemented
Login function using Firebase Auth
Database using Firebase Cloud Store
What I tried
Check the login status using Current User of Firebase Auth
Additional information (FW/tool ​​version, etc.)
Used
FirebaseAuth
FirebaseCloudStore
class MyApp extends StatelessWidget {
// This widget is the root of your application.
final UserState user = UserState();
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<UserState>.value(
value: user,
child: MaterialApp(
// Hide debug label
debugShowCheckedModeBanner: false,
title:'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: LoginCheck(),
initialRoute: "/",
routes:<String, WidgetBuilder>{
// "/": (BuildContext context) => LoginPage(),
AddRecord.routeName: (BuildContext context) => AddRecord(),
"/login":(BuildContext context) => LoginPage(),
"/home":(BuildContext context) => PageManager()
},
)
);
}
}
class LoginCheck extends StatefulWidget{
LoginCheck({Key key}): super(key: key);
#override
_LoginCheckState createState() => _LoginCheckState();
}
class _LoginCheckState extends State<LoginCheck>{
#override
void initState(){
super.initState();
checkUser();
// TODO: implement initState
}
void checkUser() async{
final UserState userState = Provider.of<UserState>(context);
final currentUser = await FirebaseAuth.instance.currentUser();
print(currentUser);
if(currentUser == null){
Navigator.pushReplacementNamed(context,"/login");
}else{
userState.setUser(currentUser);
Navigator.pushReplacementNamed(context, "/home");
}
}
#override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
body: Center(
child: Container(
child: Text("Loading..."),
),
),
);
}
}

How can I update a Network Image in Flutter

I'm currently learning Flutter and I wanted to try out Network Requests and working with Futures.
I want to show a random image from unsplash.com using their API and I want to change the image every time I press a certain button.
I tried implementing a function to change the image, but it doesn't work.
My code looks like this:
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:async';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: RandomImage(),
);
}
}
class RandomImage extends StatefulWidget {
#override
_RandomImageState createState() => _RandomImageState();
}
class _RandomImageState extends State<RandomImage> {
static String imageUrl = 'https://source.unsplash.com/random/300x200';
Future _imgFuture = http.get(imageUrl);
void _changeImage() async {
_imgFuture = http.put(imageUrl);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: (Text('Hello')),
),
body: Center(
child: Column(
children: [
Spacer(),
FutureBuilder(
future: _imgFuture,
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text('Oops, there was an error');
} else if (snapshot.hasData) {
return Image.network(imageUrl);
} else {
return Text('No value yet');
}
},
),
RaisedButton(
child: Text('Change Image!'),
onPressed: () => setState(() {
_changeImage();
}),
),
Spacer(),
],
),
),
);
}
}
Actually, Image.network is keeping your image, for more detail to see It here. The solution for this issue is make a simple useless query to api, so the image will be identical differently in flutter.
RaisedButton(
child: Text('Change Image!'),
onPressed: () => setState(() {
// _changeImage();
imageUrl="https://source.unsplash.com/random/300x200?v=${DateTime.now().millisecondsSinceEpoch}";
}),
),
The image won't change If you call the api too frequently, you might want to add a timer to prevent user from clicking too often.
I think the problem is in the _changeImage() method, try replace http.put with http.get.

Firebase Streambuilder is not changing login state after navigating

Here, i am trying to implement Firebase login and signup system. i am trying to change screen base on user login or not.
Basically, i want to show feed screen when user is login and when user is not login i want to show login scree. if i do login in login screen it is working fine, so i did not added that code here. but issue come when i navigate from login screen to sign up scree and even if i successfully sign up it is not showing me feed screen. When i hot reload it show me feed screen.
Moreover, i also make sure that it is reaching where i am changing screen by print in console.
Note: i know i can using function to change between login screen and signup screen, so i don't need Navigator, which will again work for me. but i want to know why after navigating using navigator it is not working.
class DeleteWidget extends StatefulWidget {
#override
_DeleteWidgetState createState() => _DeleteWidgetState();
}
class _DeleteWidgetState extends State<DeleteWidget> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: StreamBuilder<FirebaseUser>(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (BuildContext context, snapshot) {
print(snapshot.hasData);
print(snapshot.connectionState);
if (ConnectionState.active == snapshot.connectionState) {
print("object 1");
if (snapshot.hasData) {
print("object 2");
return Feed();
} else {
print("object 3");
return LoginScreen();
}
} else {
return LoginScreen();
}
}),
);
}
}
class LoginScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
child: RaisedButton(
child: Text("login"),
onPressed: () async {
Navigator.push(
context, MaterialPageRoute(builder: (context) => SignUp()));
},
),
),
),
);
}
}
class SignUp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
child: RaisedButton(
child: Text("Sign up"),
onPressed: () async {
await FirebaseAuth.instance.signInAnonymously();
},
),
),
),
);
}
}
class Feed extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
child: RaisedButton(
child: Text("feed"),
onPressed: () async {
await FirebaseAuth.instance.signOut();
},
),
),
),
);
}
}
You can use the Provider Package to Listen if user is logged in and use a Wrapper to direct the user to the correct screen. If the user logs out at any stage, they will be automatically redirected to the Login Screen.
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
User _userFromFirebaseUser(FirebaseUser user) {
return user != null ? User(uid: user.uid) : null;
}
#override
Widget build(BuildContext context) {
return StreamProvider<User>.value(
value: FirebaseAuth.instance.onAuthStateChanged.map(_userFromFirebaseUser),
child: MaterialApp(
home: Wrapper(),
),
);
}
}
Wrapper
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
if(user == null) {
return LoginScreen();
} else {
return Feed();
}
}
}

My data from firestore not retrieve using dart flutter

I am using cloud fire store in flutter I did:
connected the app with the firebase
put the google-services.json in app buil.gradle
add the dependency and plugin
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
MyApp();
#override
Widget build(BuildContext context) {
// TODO: implement build
return MaterialApp(
title: 'کوردی پۆلی یەک',
theme: ThemeData(
primarySwatch: Colors.blueGrey,
),
home: MainScreen(),
);
}
}
class MainScreen extends StatelessWidget{
List<Widget> makeListWidget(AsyncSnapshot snapshot){
return snapshot.data.documents.map<Widget>((document){
return ListTile(
title: Text(document["name"]),
);
}).toList();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("کوردی پۆلی یەک",style: TextStyle(color: Colors.white),),
backgroundColor: Colors.deepOrange,
),
body: Container(
child: StreamBuilder(
stream: Firestore.instance.collection('lesson').snapshots(),
builder: (context,snapshot){
switch(snapshot.connectionState){
case ConnectionState.none:
return Center(child: Text('No data'));
case ConnectionState.waiting:
return Center(
child:CircularProgressIndicator());
default:
return ListView(
children:makeListWidget(snapshot),
);
}
},
),
),
);
}
}
It is just loading and when ever I delete the connectionState.waiting gives me error:
NoSuchMethodError: The getter 'documents' was called on null. Receiver: null
Tried calling
Change the firestore security rules to the following:
// Allow read/write access to all users under any conditions
// Warning: **NEVER** use this rule set in production; it allows
// anyone to overwrite your entire database.
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if true;
}
}
}
change the allow read, write: if true; to allow read, write;

Resources