Navigate to another page in StreamBiulder on Flutter - flutter

I'm Trying to know if the user is logged in or not using firebase google Auth and send him to different pages depending the case.
If the user is Logged in, Streambuilder returns profile() and if not, signUp() is returned. So far, so good. But what I need is to Navigate to another page using Navigator instead of returning widgets.
I need to do this:
Navigator.push( context, MaterialPageRoute(builder: (context) => profile()),
Instead of:
return profile();
The code I'm working on is:
body: StreamBuilder(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else if (snapshot.hasData) {
return profile();
} else if (snapshot.hasError) {
return Center(child: Text("Something went wrong"));
} else {
return signUp();
}
},
),
Any idea on how to do this? Should I use other approach instead of a Streambuilder? Thanks in advance!

the FirebaseAuth.instance.authStateChanges() returns a Stream, so you can listen to it inside your app and make acts based on it, instead of using it in a StreamBuilder, you can listen to it like this:
FirebaseAuth.instance.authStateChanges().listen((user) {
if(user != null) {
Navigator.push( context, MaterialPageRoute(builder: (context) => profile()),
} else {
Navigator.push( context, MaterialPageRoute(builder: (context) => Login()),
}
});
you need just to find a place where you're going to call it, once it listens to the authStateChanges(), by authenticating a new user or logging him out, this stream will trigger the change and execute the code inside of it.

Related

Please comment on 3 flutter_bloc writing styles: BlocBuilder, BlocListener, BlocConsumer

I am practicing with flick_bloc and I wonder when to use BlocBuilder, when to use BlocListener and when to use BlocConsumer. I asked a few people, they said that BlocBuilder is used the most and I also started and just practiced with it, but it seems that Blocbuilder only changed for the first time, I don't know if it's true. Can you guys give me some comments on these spellings
Bloc Builder
Used for building widgets, For Example: If you want to show a list of employee names on a page, you can return a ListView widget based on the bloc state. Also, if the employee list comes from an API, then you will need different states such as Loading, Success and Failure states. Based on these different states you can return different widgets from BlocBuilder. A CircularProgressIndicator for showing loading state, ListView for showing employee list in the success state and Error text widget for showing error message if the API fails.
BlocBuilder<BlocA, BlocAState>(
builder: (context, state) {
if (state is Loading) {
return CircularProgressIndicator();
}
}
)
Bloc Listener
BlocBuilder can only return widgets. If you want to show a snackbar or want to Navigate from one page to another, then you need to use BlocListener for that.
BlocListener<BlocA, BlocAState>(
listener: (context, state) {
if (state is Success) {
Navigator.push(context,
MaterialPageRoute(builder: (context) => const SecondRoute()),
);
}
}
)
Bloc Consumer
If you have the use of both BlocListener and BlocBuilder, then it is better to use BlocConsumer. It reduces the boilerplate of using BlocListener and BlocBuilder together.
Code Without Bloc Consumer:
BlocListener<BlocA, BlocAState>(
listener: (context, state) {
if (state is Success) {
Navigator.push(context,
MaterialPageRoute(builder: (context) => const SecondRoute()),
);
}
},
child: BlocBuilder<BlocA, BlocAState>(
builder: (context, state) {
if (state is Loading) {
return CircularProgressIndicator();
}
}
),
)
Code using Bloc Consumer:
BlocConsumer<BlocA, BlocAState>(
listener: (context, state) {
if (state is Success) {
Navigator.push(context,
MaterialPageRoute(builder: (context) => const SecondRoute()),
);
}
},
builder: (context, state) {
if (state is Loading) {
return CircularProgressIndicator();
}
}
)
BlocBuilder: You can use it to just build out your widgets, but the draw back is that you can't build in Snackbars or Dialogs into the flow, because you must return a widget in blocbuilder and you don't want to return a snackbar or dialog.
BlocListener: This would permit you to use your dialogs and snackbars, but the issue is that it can't let you do anything a blocbuilder would let you do. Which is as you might have guessed, is to return a widget, it's more suited for dismissible UI components like the dialogs and snackbars.
BlocConsumer: This widget helps you combine both a BlocListener and a BlocBuilder, so you can return static components and dismissible UI components.
So if you won't need Snackbars or Dialogs, use a BlocBuilder, If you need Snackbars or Dialogs, use a BlocListener. If you want both of them to work in synergy use a BlocConsumer.
BlocBuilder
This is used when we want to draw a Widget based on what is the current State. In the following example a new “text” gets drawn every time the state changes.
Sample Example
BlocBuilder<OrdersBloc, OrdersState>(
buildWhen: (context, state) {
return state is OrdersState.OrderCompleted
},
builder: (context, state) {
if (state is OrdersState.OrderCompleted) {
return Container(child: Text('Order Completed!'));
} else if (OrdersState.OrderInProgress) {
return Container(child: Text('In Progress'));
} else if (OrdersState.OrderRequested) {
return Container(child: Text('A customer placed an order!'));
} else {
return Container(child: Text('Waiting for an order'));
}
},
);
BlocListener
This is just a listener not a builder (like the above), that means that its job is keep listening for new changes in the state and not to return a widget. You can use listener when you want to show any dialog or any toast, or navigation from one page to another(these are few examples).
Sample Example
BlocListener<OrdersBloc, OrdersState>(
listenWhen: (context, state) {
return state is OrdersState.OrderCompleted;
},
listener: (context, state) {
// Navigate to next screen
Navigator.of(context).pushNamed('OrderCompletedScreen');
},
child: Container(child: Text('Always draw this text!')),
);
BlocConsumer
This is used when we want to draw something based on the current state and execute some actions depending on the new arriving states. This is a mix between “BlocListener” and “BlocBuilder”.
Sample Example
BlocConsumer<OrdersBloc, OrdersState>(
listenWhen: (context, state) {
return state is OrdersState.OrderCompleted ||
state is OrdersState.OrderRefunded;
},
listener: (context, state) {
if (state is OrdersState.OrdersCompleted) {
// Navigate to next screen
Navigator.of(context).pushNamed('OrderCompletedScreen');
} else if (state is OrdersState.OrderRefunded) {
// Report to analytics
Analytics.reportRefunded(state.orderId);
}
},
buildWhen: (context, state) {
return state is OrdersState.OrderCompleted ||
state is OrdersState.OrderInProgress ||
state is OrdersState.OrderRequested;
},
builder: (context, state) {
if (state is OrdersState.OrderCompleted) {
return Container(child: Text('Order Served!'));
} else if (OrdersState.OrderInProgress) {
return Container(child: Text('In Progress'));
} else {
return Container(child: Text('No State'));
}
},
);

Flutter: show dialogue with generate route

I have a page in my app where I give a file as a parameter but if the file is too large then I would like to stay at the same screen and with a warning message displayed using showDialogue. Since there are many entry points to this screen I am using generatedRoutes to check if the file will be rejected.
This check (the if statement) works at the moment , however, I have no idea how to stay on the screen and show the error. This is what I have so far:
Route<dynamic>? generateRoute(RouteSettings settings, BuildContext context) {
switch (settings.name) {
case homePage:
return MaterialPageRoute(builder: (context) => const HomePage());
case previewFilePage:
var file = settings.arguments as FileModel;
if (file.size > kMaxFileBytes) {
// What I want to do but doesnt work
showDialog(
context: context,
builder: (context) {
return PopUpDialogue(
message: Text("File is too large"),
);
}
);
break;
} else {
return MaterialPageRoute(
builder: (context) => PreviewFilePage(
file: file,
),
);
}
default:
return MaterialPageRoute(builder: (context) => const HomePage());
}
return null;
}
Is there a way to do this or will I have to add these error messages manually on each entry page? Also I know I could create an error page and direct to it if the check fails but I would much better prefer to have a dialogue show up instead.
Thanks!

How to popUntil to a named route in Flutter?

I am trying to implement a signin_screen.dart which after taking the user's email and password, checks for a stream of authStateChanges. If the login is successful it takes automatically takes the user to the account screen (which a named route: "/my-account"). I'm trying to display a CircularProgressIndiciator in a showDialog for the time between the user submitting his details by clicking on the sign in button till the time the login process is completed.
My Code:
signin_screen.dart
return SafeArea(
child: Scaffold(
body: StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return const AccountScreen(); //named-route of "/my-account"
} else {
return CustomLoginWidget();
}
The following function is called when the sign in button (implemented in the CustomLoginWidget) is clicked :
void signInUser() async {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) {
return const Center(
child: CircularProgressIndicator(),
);
});
context.read<FirebaseAuthMethods>().loginWithEmail(
email: _emailController.text.trim(),
password: _passwordController.text.trim(),
context: context);
navigatorKey.currentState!.popUntil((route) => route.isFirst);
//Navigator.popUntil(context, ModalRoute.withName("/my-account"));
}
Note:
Navigator.popUntil(context, ModalRoute.withName("/my-account")); does not work.
navigatorKey: navigatorKey,
has been added as a property in MaterialApp(), where navigatorKey has been declared as final navigatorKey = GlobalKey<NavigatorState>();
Issue Faced:
If I do navigatorKey.currentState!.popUntil((route) => route.isFirst it takes the app to the splash screen (as named by the "/" route), but I want the app to load the account_screen under the route name: "/my-account".
Is there a way to implement this? I have tried route.isCurrent and route.isActive, but to no avail.
Please help.
How about try to declare a route name for your MyAccount class
static const routeName = "/my-account"
and then try this
navigatorKey.currentState!.popUntil((route) => route.settings.name == MyAccount.routeName);

What is the correct way to wait for the API response, in the sense of interface in flutter?

When a screen where the main content comes from an API, what would be the option to have a good UI? Should this really be done with FutureBuilder?
Yes, you can use FutureBuilder and use different states to inform your users.
For example:
return FutureBuilder(
future: _getMark(context),
builder: (ctx, snapshot) {
if (snapshot.hasData) {
return snapshot.data as Widget;
}
if (snapshot.hasError) {
//show error
}
return markUnRead;
},
);

Flutter how to redirect to login if not authorized

I'm trying to redirect to login in case the token has been expired in Flutter
Trying to get the posts:
body: new Container(
padding: new EdgeInsets.only(bottom: 8.0),
color: Color(0xff2c3e4e),
child: FutureBuilder<List<Post>>(
future: api.getposts(),
builder: (context, snapshot) {
// doing stuff with response
}
)
)
getposts and catching the error:
Future<List<Post>> getposts() async {
url = 'url';
var response = await http.get(url,
headers: {HttpHeaders.authorizationHeader: 'bearer ' + token},
);
//part I can't understand how to get to work, how do I push? This gives error about context not being defined.
if (response.body.toString().contains('Token is Expired')) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
LoginScreen()),
);
}
}
So the question is, how do I use the Navigator correctly in such cases and I can redirect back to loginScreen in case the token has been expired and needs to be renewed? Like mentioned in the comment in the code example, the context gives me "undefined".
Is this even possible the way I am doing it or am I simply handling the whole check completely wrong?
Code should have single resonpsibility. Your getPost method are doing 2 things at the same time. You should break this function such that it either successfully get the the post, or throw exception, and its caller will handle the exception. Its caller btw must be within build method, because only build method has BuildContext context, something like this:
if (response.body.toString().contains('Token is Expired')) {
throw new Exception("Token is Expired") // you may want to implement different exception class
}
body: new Container(
padding: new EdgeInsets.only(bottom: 8.0),
color: Color(0xff2c3e4e),
child: FutureBuilder<List<Post>>(
future: api.getposts(),
builder: (context, snapshot) {
if (snapshot.hasError) {
// you might want to handle different exception, such as token expires, no network, server down, etc.
Navigator.push(
context,
MaterialPageRoute(builder: (context) => LoginScreen()),
);
return Container(); // return empty widget
}
if (snapshot.hasData) {
// do somthing with data
return Text('Some data here');
}
// has no data
return Text("Loading...");
}),
),
UPDATE
Thanks to #TruongSinh I got it figured out.
Followed his example and figured out the build navigator method which works:
if (snapshot.hasError) {
#override
void run() {
scheduleMicrotask(() {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => LoginScreen()),
);
});
}
run();
}
Update: added a package containing several guards like this
I did it with a StreamBuilder to react on change and be able to display a LoadingScreen when we don't know yet if the user is connected.
StreamBuilder authGuard = StreamBuilder(
stream: Auth.authState$,
builder: (context, snapshot) {
switch (snapshot.data) {
case AuthState.PENDING:
return LoadingScreen();
case AuthState.UNAUTHENTICATED:
return SignInScreen();
case AuthState.AUTHENTICATED:
return HomeScreen();
default:
return LoadingScreen();
}
},
);
So it will change screen depending on the AuthState:
return MaterialApp(
// ...
home: authGuard,
);
And my auth class
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:rxdart/rxdart.dart';
enum AuthState { PENDING, AUTHENTICATED, UNAUTHENTICATED }
class Auth {
static final FirebaseAuth _auth = FirebaseAuth.instance;
static final GoogleSignIn _googleSignIn = GoogleSignIn();
static Stream<AuthState> authState$ = FirebaseAuth.instance.onAuthStateChanged
.map((state) =>
state != null ? AuthState.AUTHENTICATED : AuthState.UNAUTHENTICATED)
.startWith(AuthState.PENDING);
static Future<FirebaseUser> signInWithGoogle() async {
// ...
}
}