How can i show some loading screen or splash screen while flutter application loads up - flutter

I have been working on an app recently. I want to check if the user is logged in and is verified when my app loads up. So I created a Wrapper class to check if the user is logged in and is verified. Then accordingly I would show them either login screen or home screen.
I have assigned home : Wrapper(), in Main.dart .
After that I have wrapper class as
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
// checking if there is user and the user is verified
bool _isAuth() {
if (user != null && user.isVerified) {
return true;
}
return false;
}
return _isAuth() ? MainScreen() : Authenticate();
}
}
This works fine but the problem is it first flashes the login page and then takes me to the homepage if the user is logged in and is verified but it just works fine if the user is not logged in see gif image here

It probably shows the login page because of the way your logic is being handled. you should do this in initState instead of the build method. There are two ways to do this you can either use your wrapper as redirection class or use the build method like you're already doing to toggle the view.
First Method (uses redirection)
class Wrapper extends StatefulWidget {
#override
_WrapperState createState() => _WrapperState();
}
class _WrapperState extends State<Wrapper> {
#override
void initState() {
super.initState();
final user = Provider.of<User>(context, listen: false);
var _isAuth = user != null && user.isVerified;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => _isAuth ? MainScreen() : Authenticate()),
);
}
#override
Widget build(BuildContext context) {
return CircularProgressIndicator();
}
}
Second Method (uses build method):
class Wrapper extends StatefulWidget {
#override
_WrapperState createState() => _WrapperState();
}
class _WrapperState extends State<Wrapper> {
bool _isAuth = false;
bool _isLoading = true;
#override
void initState() {
super.initState();
final user = Provider.of<User>(context, listen: false);
setState(() {
_isAuth = user != null && user.isVerified;
_isLoading = false;
});
}
#override
Widget build(BuildContext context) {
return _isLoading
? CircularProgressIndicator()
: _isAuth
? MainScreen()
: Authenticate();
}
}

Related

how to await for network connectivity status in flutter

I have used connectivity_plus and internet_connection_checker packages to check the internet connectivity.
The problem occured is , the app works perfectly fine as expected when the app start's with internet on state. But when the app is opened with internet off, the dialog isn't shown !!
I assume this is happening because the build method is called before the stream of internet is listened.
Code :
class _HomePageState extends State<HomePage> {
late StreamSubscription subscription;
bool isDeviceConnected = false;
bool isAlertSet = false;
#override
void initState() {
getConnectivity();
super.initState();
}
getConnectivity() {
subscription = Connectivity().onConnectivityChanged.listen(
(ConnectivityResult result) async {
isDeviceConnected = await InternetConnectionChecker().hasConnection;
if (!isDeviceConnected && isAlertSet == false) {
showDialogBox();
setState(() {
isAlertSet = true;
});
}
},
);
}
#override
void dispose() {
subscription.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
...
);
}
showDialogBox() => showDialog(/* no internet dialog */)
Extending the question: Is it assured that this works for all the pages ?
if yes, how ?
if not , how to overcome this?
First of all you need to listen for internet connectivity in your app first screen which is probably app.dart
GlobalKey<NavigatorState> navigatorKey = GlobalKey();
final noInternet = NoInternetDialog();
class TestApp extends StatefulWidget {
#override
State<TestApp> createState() => _TestAppState();
}
class _TestAppState extends State<TestApp> {
#override
void initState() {
super.initState();
checkInternetConnectivity();
}
#override
Widget build(BuildContext context) {
return MaterialApp(...);
}
Future<void> checkInternetConnectivity() async {
Connectivity().onConnectivityChanged.getInternetStatus().listen((event)
{
if (event == InternetConnectionStatus.disconnected) {
if (!noInternet.isShowing) {
noInternet.showNoInternet();
}
}
});
}
}
Make the screen stateful in which you are calling MaterialApp and in initState of that class check for your internet connection, like above
You are saying how can I show dialog when internet connection changes for that you have to create a Generic class or extension which you can on connectivity change. You have to pass context to that dialogue using NavigatorKey
class NoInternetDialog {
bool _isShowing = false;
NoInternetDialog();
void dismiss() {
navigatorKey.currentState?.pop();
}
bool get isShowing => _isShowing;
set setIsShowing(bool value) {
_isShowing = value;
}
Future showNoInternet() {
return showDialog(
context: navigatorKey.currentState!.overlay!.context,
barrierDismissible: true,
barrierColor: Colors.white.withOpacity(0),
builder: (ctx) {
setIsShowing = true;
return AlertDialog(
elevation: 0,
backgroundColor: Colors.transparent,
insetPadding: EdgeInsets.all(3.0.h),
content: Container(...),
);
},
);
}
}
Use checkConnectivity to check current status. Only changes are exposed to the stream.
final connectivityResult = await Connectivity().checkConnectivity();

"await" in Widget build FLUTTER

I've been stuck for several hours with a problem on flutter. If you can help me that would be really nice.
I need to put "await" in my Widget build(BuildContext context){} but it's impossible to put "async".
How to do ?
When i test void _myAsyncMethod()async{} :
To Fix your issue you can put async in the body of method like this
Before=> Widget build(BuildContext context) {
After=> Widget build(BuildContext context) async{
Although this will not solve your problem as flutter wiill warn you as this is not the proper way to do it.
It's not a good practice to call await inside flutter's build method Because
Generally an apps need to run a 60 frames per second on an average hence flutter's build method we'll be called over and over to re-render the ui.
Another reason is that, doing calling await function() in build method will block your UI.
Solution
use FutureBuilder
call await auth.currentUser() in initState method
Another way to solve this is to use FutureBuilder
sample Code for 1
FutureBuilder(
builder: (BuildContext ctx, AsyncSnapshot<userModel> snapshot) {
if(ConnectionState.done == snapshot.connectionState) {
return Text(snapshot.data.userId);
} else {
return CircularProgressIndicator();
}
},
future: auth.currentUser(),
);
sample Code for 2(stateful widget)
late UserModel;
void initState() {
UserModel user = await auth.currentUser();
}
this is very basic code but it's enough for you to get started.
Note: I've assumed userModel mentioned above is response type of auth.currentUser() you can change it accordingly.
What you want to do is not optimal but you can create a method and put your await variable in there:
late final FirebaseUser _user;
void _myAsyncMethod()async{
_user = await auth.currentUser;
}
#override
Widget build(BuildContext context) {
_myAsyncMethod();
return Scaffold(appBar: AppBar(), body: Container());
}
If your are using stateful widget you can instantiate firebase auth in initstate() method.
class testFirless extends StatefulWidget {
var currentuseid = "";
testFirless({Key? key}) : super(key: key);
#override
_testFirlessState createState() => _testFirlessState();
}
class _testFirlessState extends State<testFirless> {
#override
Widget build(BuildContext context) {
return Container();
}
// ------------------------------------>heree
#override
Future<void> initState() async {
FirebaseAuth auth = FirebaseAuth.instance;
var user = await auth.currentUser;
if (user == null) {
widget.currentuseid = user!.uid;
} else {
print('User is signed in!');
}
}
}
FutureBuilder
class fbuilder extends StatelessWidget {
const fbauth({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
FirebaseAuth auth = FirebaseAuth.instance;
// --------------->
return Container(child: FutureBuilder(
builder: (BuildContext ctx, AsyncSnapshot<User> snapshot) {
if (ConnectionState.done == snapshot.connectionState) {
return Text(snapshot.data.userId.toString());
} else {
return CircularProgressIndicator();
}
},
future: auth.currentUser(),
));
}
}
in stateless or stateful widget
String currentuseid="";
class fbauth extends StatelessWidget {
const fbauth({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
FirebaseAuth auth = FirebaseAuth.instance;
// ------------------------>
auth.currentUser().then((user) {
if (user == null) {
currentuseid = user!.uid;
} else {
print('User is signed in!');
}
// other logic after the user retrieval
});
return Container();
}
}
Nb: Instead of instantiating firebase auth in every widget .you must instantiate in `void main` method

How to Reload Flutter StatefulWidget with AutomaticKeepAliveClientMixin?

How to Reload Flutter StatefulWidget with AutomaticKeepAliveClientMixin?
The below code is Not reloading the Usermovies list StreamBuilder on user logout through firebase, instead showing old user movies data only.
This HomeScreen is called in Bottom Navigation Bar with PageView. The other Page is AccountScreen with Login and Logout buttons.
My question is how to reload the UserMovies on user logout through firebase. How to reload the HomeScreen on logout from AccountScreen such that the User Movies Stream is refreshed to null.
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen>
with AutomaticKeepAliveClientMixin {
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
// need to call super method for AutomaticKeepAliveClientMixin
super.build(context);
print('Rebuild in Home Screen.....');
return StreamBuilder<app.User>(
stream: Provider.of<AuthProvider>(context, listen: true).user,
builder: (context, snapshot) {
if (snapshot.data != null) {
isUserLoggedIn = true;
rebuild = false;
} else if (snapshot.data == null && isUserLoggedIn) {
isUserLoggedIn = false;
rebuild = true;
} else {
isUserLoggedIn = false;
rebuild = false;
}
if (rebuild) {
// Not reloading the Usermovies on user logout, instead showing old user movies data only in the below stream builder
Future.delayed(Duration.zero, () => setState(() {}));
}
return StreamBuilder<List<UserMovies>>(
stream: Provider.of<UserDetailsProvider>(context,
listen: false)
.getUserFavouriteMovies(),
builder: (context, snapshot) {
snapshot.data != null && snapshot.data.length > 0
? print('data there: ')
: print('data zero');
snapshot.data != null && snapshot.data.length > 0
? Scaffold.of(context).showCurrentSnackBar() // to show last favourite movie
: Scaffold.of(context).hideCurrentSnackBar();
return SizedBox(height: 2.0);
},
},
),
}
}
return a check on whether the user exists or not
#override
bool get wantKeepAlive => isUserLoggedIn;;
in the same class listen for your user stream, and keep track of whether the user present or not and set isUserLoggedIn based on that, now state will be maintained if the user exists otherwise not.
initState(){
Provider.of<AuthProvider>(context, listen: true).user.listen((user){
isUserLoggedIn = user!=null;
});
}
here wantKeepAlive is a framework getter method, which is used by flutter framework (the mixin) to decide whether the state must be maintained or not, you can return a boolean which can be dynamic depending on your needs.

Flutter-How do I switch from login screen to home and back? Back-end works but can't show screen without manually refresh

1.this is the main entry
void main() {
WidgetsFlutterBinding.ensureInitialized();
StorageUtil.getInstance();
runApp(MaterialApp(home: MyApp()));
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Wrapper(),
);
}
}
This is the Wrapper. The log-in form or the home page do not show unless I manually hot-reload the app.
I've tried everything but i am stuck. Please help.
class Wrapper extends StatefulWidget {
#override
_WrapperState createState() => _WrapperState();
}
class _WrapperState extends State<Wrapper> {
User _user = User();
#override
Widget build(BuildContext context) {
_user.uId = StorageUtil.getString('access_token');
if(_user.uId != null && _user.uId != ""){
print('filled ${_user.uId}');
return Home();
}else{
print('empty ${_user.uId}');
return Authenticate();
}
}
}
I think your StorageUtil is giving you promise for get data back to you but you are not waiting for it when app loads at first time.You can try await StorageUtil.getInstance(); in main block.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await StorageUtil.getInstance();
runApp(MaterialApp(home: MyApp()));
}
You need to watch the instance. Right now you are grabbing the instance to get the value but you are not subscribing to the value itself, which means that when the value changes nothing will happen until you refresh the page. I recommend subscribing to the value (access_token) that is determining the login screen vs the home screen.
Flutter has some built in features that makes this a bit easier such as streams and or quicker widgets like the ValueListenerBuilder. Let's see if we can do that with StorageUtil.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await StorageUtil.getInstance();
runApp(MaterialApp(home: MyApp()));
}
class Wrapper extends StatefulWidget {
#override
_WrapperState createState() => _WrapperState();
}
class _WrapperState extends State<Wrapper> {
User _user = User();
#override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: StorageUtil.getString('access_token');,
builder: (BuildContext context, String value, _) {
if(value != null && value != ""){
print('filled ${_user.uId}');
return Home();
} else {
print('empty ${_user.uId}');
return Authenticate();
}
},
),
}
}
It is rough but it should get the job done! I recommend probably finding a more streamlined way to store your state than just the StorageUtil that'll better scale as your application grows.

Check constraint and return the body accordingly

I want to show onboarding screen only for the first time user opens the application, so at the final page of Onboarding screen I put OnBoardingStatus value to be "Done" and move to the main screen. But when user opens the application for the next time this code flash the Onboarding screen for few milliseconds and then opens the mainScreen.
Here is my code
class App2 extends StatefulWidget {
App2({Key key}) : super(key: key);
#override
_App2State createState() => _App2State();
}
class _App2State extends State<App2> {
String onBoardingStatus;
#override
void initState() {
// TODO: implement initState
getOnBoardingStatus();
super.initState();
}
Future<void> getOnBoardingStatus() async {
WidgetsFlutterBinding.ensureInitialized();
SharedPreferences prefs = await SharedPreferences.getInstance();
var onboardingstatus = prefs.getString('OnBoardingStatus');
setState(() {
onBoardingStatus = onboardingstatus;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: onBoardingStatus != null
? MainScreen()
: OnboardingScreen());
}
}
Currently you have no way to know if onBoardingStatus is null because the SharedPreferences instance hasn't been retrieved yet, or because the OnBoardingStatus really is empty. You can work around this with a FutureBuilder:
class App2 extends StatelessWidget {
App2({Key key}) : super(key: key);
Future<String> getOnBoardingStatus() async =>
(await SharedPreferences.getInstance()).getString('OnBoardingStatus');
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: getOnBoardingStatus(),
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
//TODO: Return a widget that indicates loading
}
return Scaffold(
body: snapshot.data != null
? MainScreen()
: OnboardingScreen());
},
);
}
}
However I don't think it's the best solution. For starters, App2 should get the status from an outer source - this way if you ever decide to change your storage solution you wouldn't need to touch App2.