I have flutter app need to load some api's data in splash screen which contain animation.
class _SplashPageState extends State<SplashPage> {
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
var auth = Provider.of<AuthProvider>(context, listen: false);
auth.chekingAuthVariables();
var loading = Provider.of<LoadingProvider>(context, listen: false);
loading.getBrands();
loading.getVideos();
var catsProv = Provider.of<CatProviders>(context, listen: false);
catsProv.getCategoryList();
});
super.initState();
}
#override
Widget build(BuildContext context) {
return AnimatedSplashScreen(
backgroundColor: ColorManager.tabBottonNonActive,
duration: 3000,
splash: 'assets/splash.png',
nextScreen: const MainTabPage(),
splashTransition: SplashTransition.slideTransition,
);
}
}
the duration of animation is 3 seconds, sometimes when the net is slow the api's data dont load which make a problem for loading some screens.
How can I make the duration of animation finish after loading all api's data?
void loadAPIData() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
var auth = Provider.of<AuthProvider>(context, listen: false);
auth.chekingAuthVariables();
var loading = Provider.of<LoadingProvider>(context, listen: false);
loading.getBrands();
loading.getVideos();
var catsProv = Provider.of<CatProviders>(context, listen: false);
catsProv.getCategoryList();
// run the animation indefinitely and use navigate after retrieving all data
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SecondRoute()),
);
});
}
I noticed you are making API calls without using await, which is quite odd considering you are loading data from an asynchronous source.
Related
I want to call a loading screen widget that has a column with an image, a text and a loading indicator, 5 seconds after the screen is built there should called a setState-method that changes a boolean value[foundOpponent] and with that the screen (the text should be changed into "found opponent" and the indicator into an Icon) after that there should be a delay of 2 seconds and then a Navigator.push to another screen.
I have already looked at https://stackoverflow.com/questions/49466556/flutter-run-method-on-widget-build-complete and tried most of the explained methods and it somewhat worked but the screen of the virtual phone was extremely lagging , the image that should get built in the build method does not even get displayed and while the image is loading the method that should get called after the build is being executed.
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
sleep(const Duration(seconds: 10));
setState(() {
foundOpponent = true;
});
sleep(const Duration(milliseconds: 2500));
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const QuestionMainPage()),
);
});
}
I've tried the following methods:
WidgetsBinding.instance
.addPostFrameCallback((_) => yourFunction(context));
SchedulerBinding.instance.addPostFrameCallback((_) => yourFunction(context));
if (SchedulerBinding.instance.schedulerPhase == SchedulerPhase.persistentCallbacks) {
SchedulerBinding.instance.addPostFrameCallback((_) => yourFunction(context));
}
Future<void> _runsAfterBuild() async {
await Future((){}); // <-- Dummy await
// This code runs after build
}
#override
Widget build(BuildContext context) {
_runsAfterBuild();
return Container();
}
Try using Future.delayed and make sure to put await
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
await Future.delayed(Duration(seconds: 10));
//await task a
await Future.delayed(Duration(seconds: 1));
//await task B
});
}
I am using StreamBuilder and ListView to show data from FireStoreDatabase in UI. Usually, it updates itself when I make any change in the database (without refresh). but when I sort the data in the stream, it no longer updates. here's the code am using for StreamBuilder.
Stream<List<Attraction>> attractionsStream = Stream.value([]);
void initState() {
final database = Provider.of<Database>(context, listen: false);
setState(() {
//this.attractions = attractions.toList();
attractionsStream = database.attractionStream();
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder<List<Attraction>>(
stream: attractionsStream,
builder: (context, snapshot) {...}
);
}
_sortData() async {
final db = Provider.of<Database>(context, listen: false);
var attractions = await db.attractionStream().first;
attractions.sort((a, b) {...}
setState(() {
attractionsStream = Stream.value(attractions);
});
}
Could you please guide me on this?
Try adding a unique Key to each widget that your StreamBuilder builds.
It is also uncommon to completely reset the stream, as opposed to pushing new data through the stream already in use.
I'm new to flutter and building an app using ChangeNotifier and provider for an MVVM design pattern. When a login page loads I want to check it's already logged in or not. if it's already logged in, then navigate to the next page. But it show's following error.
My ChangeNotifier Binding code from splash screen
_moveToNextScreen() async {
await Navigator.pushReplacement(context,
MaterialPageRoute(
builder: (context) =>
ChangeNotifierProvider(
create: (_) => LoginViewModel(),
child: LoginScreen(),
),
));
}
Login Screen code
class _LoginScreenState extends State<LoginScreen> {
LoginViewModel _loginViewModel;
//controller for login
TextEditingController _usernameController = TextEditingController();
TextEditingController _passwordController = TextEditingController();
Size size; // calculate screen size
#override
void initState() {
_loginViewModel = Provider.of<LoginViewModel>(context, listen: false);
_loginViewModel.loggedInOrNot();
checkLoginStatus();
super.initState();
}
checkLoginStatus() async {
print('vcal - ${_loginViewModel.getIsLoggedIn}');
if (_loginViewModel.getIsLoggedIn == true) {
//_moveToNextScreen();
}
}
#override
Widget build(BuildContext context) {
_loginViewModel = Provider.of<LoginViewModel>(context);
size = MediaQuery.of(context).size;
return Scaffold(
backgroundColor: green,
//IP screen
// Asset Tracker- Text
body: _loginScreen(),
);
}
my LoginViewModel.dart
class LoginViewModel extends ChangeNotifier{
LoginRepository _loginRepository = LoginRepository();
bool _isLoading = false;
bool _isLoggedIn = false;
void loggedInOrNot() async {
_isLoggedIn = !_isLoggedIn;
//await _loginRepository.checkToken();
notifyListeners();
}
bool get getIsLoggedIn => _isLoggedIn;
bool get getIsLoading => _isLoading;
}
How can i resolve this issue?
You should access a provider like this
Provider.of<LoginViewModel>(context, listen: false).getIsLoggedIn;
and Wrap with Consumer widget with top of the ui class widget tree
Consumer is rebuilds your UI every state changes.
For more info about provider & consumer I recommend you to their documentation find it from here
It is maybe because when you go to the next page you create new ChangeNotifierProvider. You have to place it above these two Widgets, e.g. above the MaterialApp/CupertinoApp
im new to flutter. I am trying to push to a new page whenever my qrcode reader detects a qrcode. however upon detecting the qrcode, infinite pages are being pushed. Can anyone provide me with some advice or sample code that pushes to a new page whenever the state changes?
class _QRViewExampleState extends State<QRViewExample> {
final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
_QRViewExampleState({this.state});
var state = false;
QRViewController controller;
#override
Widget build(BuildContext context) {
if (state == true) {
WidgetsBinding.instance.addPostFrameCallback((_) {
Navigator.of(context).push(
MaterialPageRoute(builder: (ctx) => Menu()),
);
// Navigation
});
}
return Scaffold(
...
);
}
void _onQRViewCreated(QRViewController controller) {
this.controller = controller;
controller.scannedDataStream.listen((scanData) {
setState(() {
state = true;
});
});
}
first of all, this line of code:
WidgetsBinding.instance.addPostFrameCallback((_) {});
usually used when you want run statement after build finished, and called inside initState(). in your case i think you don't need it.
i recommend you to call navigator inside your QRViewController listener:
_onQRViewCreated(QRViewController controller) {
this.controller = controller;
controller.scannedDataStream.listen((scanData) {
Navigator.of(context).push(
MaterialPageRoute(builder: (ctx) => Menu(scanData)),
);
});
}
I use SharedPreferences to keep login status. its works fine.
but after close my app when I open this. it's showing all my screen like flash screen then its stay on its right screen.
but I don't want this and this not good.
I want when apps check login status its shows a loading screen or anything then after completing its show apps right screen.
how can I do this?
Here is my login status check code
checkLoginStatus() async {
sharedPreferences = await SharedPreferences.getInstance();
if (sharedPreferences.getString("empid") == null) {
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (BuildContext context) => Home()),
(Route<dynamic> route) => false);
}
}
Snippet
void main async {
bool isUserLogin = await User.isUserLogin();
if (isUserLogin) { // wait untill user details load
await User.currentUser.loadPastUserDetails();
}
runApp(MyApp(isUserLogin));
}
and
class MyApp extends StatelessWidget {
bool isUserLogin;
MyApp(this.isUserLogin);
#override
Widget build(BuildContext context) {
var defaultRoot = isUserLogin ? HomePage() : LoginScreen();
final material = MaterialApp(
debugShowCheckedModeBanner: false,
home: defaultRoot,
);
return material;
}
}
Hope this helps!
Personnally I like to use a variable _isLoading which I set to true at the beginning of my login method using a setState. Then using this bool you just have to change what is displayed in your Scaffold.
bool _isLoading = false;
checkLoginStatus() async {
setState() => _isLoading = true;
sharedPreferences = await SharedPreferences.getInstance();
if (sharedPreferences.getString("empid") == null) {
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (BuildContext context) => Home()),
(Route<dynamic> route) => false);
} else {
setState() => _isLoading = false;
}
}
EDIT: In your case this might be more relevant:
#override
Widget build(BuildContext context) {
switch (currentUser.status) {
case AuthStatus.notSignedIn:
return LoginPage();
case AuthStatus.signedIn:
return HomePage();
default:
return LoadingPage();
}
}
My currentUser is just a class that I've created containing an enum AuthStatus which can have the value notSignedIn or signedIn. If you set the status to null while loading you can display your loading screen.