I would like my button to show only if my _isloading condition is false.
With my code the button appears but too early.
my widget:
i want my button after my timer set _isLoading = false.
Actually, my button show directly to start timer.
How do I get my button to only display when my timer set my _isLoading = false ?
First, you need to keep in mind the Flutter's tree widget structure. The tree hierachy should consists of only Widgets.
In your _validateUser(), since this method doesn't return a Widget, it cannot be put as a child of the Column widget. This method should be trigger by a button (after the user finish the form for example).
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:modal_progress_hud/modal_progress_hud.dart';
void main() {
runApp(MaterialApp(home: Home()));
}
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: TestPage(),
);
}
}
class TestPage extends StatefulWidget {
#override
_TestPageState createState() => _TestPageState();
}
class _TestPageState extends State<TestPage> {
final _scaffoldKey = GlobalKey<ScaffoldState>();
bool _isLoading = false;
bool _isValidated = false;
_validateUser() async {
setState(() {
_isLoading = true;
});
await Future.delayed(Duration(seconds: 3), () {
setState(() {
_isLoading = false;
_isValidated = true;
});
});
}
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_validateUser();
});
super.initState();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
resizeToAvoidBottomInset: false,
key: _scaffoldKey,
body: Center(
child: ModalProgressHUD(
inAsyncCall: _isLoading,
child: Container(
alignment: Alignment.center,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// FlatButton(
// color: Colors.red,
// child: Text("Validate User"),
// onPressed: _validateUser,
// ),
// SizedBox(height: 10),
if (_isValidated)
FlatButton(
color: Colors.green,
child: Text("Validate User"),
onPressed: _validateUser,
),
],
),
),
),
),
));
}
}
Related
I was trying to display a progress indicator on my app on flutter while it's loading. If I don't use it, the app works properly and the data load, but when I add the progress indicator, after it disappears, the app shows only the containers' borders or colors, without the data in them. How can I solve?
Here's the code of the main page:
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
#override
MyAppState createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
bool isLoaded = false;
#override
void initState() {
super.initState();
DataSync data = DataSync();
data.getInitialData();
globals.isLoaded.addListener(() {
setState(() {
isLoaded = true;
});
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'MainPage',
home: Scaffold(resizeToAvoidBottomInset: false, body: inizializeApp() ),
debugShowCheckedModeBanner: false,
);
}
Container inizializeApp() {
return Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topRight,
end: Alignment.bottomCenter,
colors: [
Color.fromARGB(255, 24, 26, 38),
Color.fromARGB(255, 1, 0, 5),
])),
//height: 500,
padding: const EdgeInsets.all(8),
child: Center(
child: isLoaded
? ListView(
physics: const NeverScrollableScrollPhysics(),
children: const [
SizedBox(height: 70, child: WeatherWidgets()),
SizedBox(height: 70, child: WeekDayWidget()),
SizedBox(height: 50, child: DateWidget()),
DataWidget(),
],
)
: const CircularProgressIndicator()),
);
}
}
and the class where I update the globals containing the data:
ValueNotifier<WeatherForecastResult> forecastResultNotifier =
ValueNotifier<WeatherForecastResult>(WeatherForecastResult.noParam());
ValueNotifier<int> selectedIndex = ValueNotifier<int>(0);
ValueNotifier<bool> isLoaded = ValueNotifier<bool>(false);
class DataSync {
Geolocation geolocation = Geolocation();
WeatherForecastResult forecastResult = WeatherForecastResult.noParam();
Weather weather = Weather();
getInitialData() async {
Position position = await geolocation.determinePosition();
forecastResult =
await weather.getForecast(position.latitude, position.longitude);
forecastResultNotifier.value = forecastResult;
isLoaded.value = true;
}
}
Tried various widget for the progress indicator, but the problem doesn't disappear
I tried to reproduce the problem - and failed! Could you check this simplified version (that should be complete in itself and run instantly) and try to reproduce the error? Or could you write a shortened version that is complete to run and reproduces the mistake? Then I would be happy to try to find out more!
import 'package:flutter/material.dart';
ValueNotifier<bool> globalIsLoaded = ValueNotifier<bool>(false);
class MyAppState extends State<MyApp> {
bool isLoaded = false;
Container initializeApp() {
return Container(
child: Center(
child: isLoaded
? const Text('is loaded')
: const CircularProgressIndicator(),
));
}
Container anotherInitializeApp() {
return Container(
child: Center(
child: ValueListenableBuilder(
builder: (BuildContext context, bool valueFromGlobalIsLoaded,
Widget? child) {
return valueFromGlobalIsLoaded
? const Text('is loaded')
: const CircularProgressIndicator();
},
valueListenable:
globalIsLoaded, // <-- ignores local isLoaded, so you would not need an addListener
),
));
}
#override
void initState() {
super.initState();
DataSync data = DataSync();
// data.getInitialData(); <-- moved it down after the addListener (but should not be decicive if this function is not super fast)
globalIsLoaded.addListener(() {
setState(() {
isLoaded = true;
});
});
data.getInitialData();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: initializeApp(), // try also: body: anotherInitializeApp(),
),
);
}
}
class DataSync {
getInitialData() async {
await Future.delayed(const Duration(seconds: 2));
globalIsLoaded.value = true;
}
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
#override
MyAppState createState() => MyAppState();
}
void main() => runApp(MyApp());
I have two screens where the second screen is pushed above the first with Navigator.push() and the second screen is partial transparent. I want to display a SnackBar, but it isn't really visible. It looks like the ScaffoldMessenger chooses the wrong of the two Scaffolds to attach the Snackbar. This leads to the effect that the SnackBar collides with the TextInput and it is also not fully visible. But this bad behavior is only the case as long as the soft keyboard is open. If the keyboard is closed, everything works fine. It seems like the open keyboard tells the ScaffoldMessenger to choose the Scaffold from the second screen to display the SnackBar.
How can I achieve that the SnackBar is shown normally in the sense of is attached to the Scaffold of screen 2 while the keyboard is open? The expected behavior is that the Snackbar isn't displayed transparent.
Keyboard open -> SnackBar is attached to Scaffold of screen 1 -> Bad
Keyboard closed -> SnackBar is attached to Scaffold of screen 2 -> Good
GIF showing the complete workflow
My code (fully executable)
import 'dart:io';
import 'package:keyboard_utils/keyboard_listener.dart';
import 'package:keyboard_utils/keyboard_utils.dart';
import 'package:flutter/material.dart' hide KeyboardListener;
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) => const MaterialApp(home: MyHomePage());
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Title')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[const Text('You have pushed the button this many times:'),
Text('$_counter', style: Theme.of(context).textTheme.headline4),
ElevatedButton(
onPressed: () {
Navigator.of(context).push(PageRouteBuilder(
opaque: false, // push route with transparency
pageBuilder: (context, animation, secondaryAnimation) => const Screen2(),
));
},
child: const Text('navigate'),
)
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => setState(() => _counter++),
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
class Screen2 extends StatefulWidget {
const Screen2({Key? key}) : super(key: key);
#override
State<Screen2> createState() => _Screen2State();
}
class _Screen2State extends State<Screen2> {
final _keyboardUtils = KeyboardUtils();
late int _idKeyboardListener;
final focusNode = FocusNode();
bool isEmojiKeyboardVisible = false;
bool isKeyboardVisible = false;
double _keyboardHeight = 300;
#override
void initState() {
super.initState();
_idKeyboardListener = _keyboardUtils.add(
listener: KeyboardListener(
willHideKeyboard: () {
if (isKeyboardVisible) {
isKeyboardVisible = false;
isEmojiKeyboardVisible = false;
}
setState(() {}); // show correct Icon in IconButton
},
willShowKeyboard: (double keyboardHeight) async {
if (Platform.isAndroid) {
_keyboardHeight = keyboardHeight + WidgetsBinding.instance.window.viewPadding.top / WidgetsBinding.instance.window.devicePixelRatio;
} else {
_keyboardHeight = keyboardHeight;
}
isKeyboardVisible = true;
isEmojiKeyboardVisible = true;
setState(() {});
},
)
);
}
#override
void dispose() {
_keyboardUtils.unsubscribeListener(subscribingId: _idKeyboardListener);
if (_keyboardUtils.canCallDispose()) {
_keyboardUtils.dispose();
}
focusNode.dispose();
super.dispose();
}
Future<void> onEmojiButtonPressed() async {
if(isEmojiKeyboardVisible){
if(isKeyboardVisible){
FocusManager.instance.primaryFocus?.unfocus();
isKeyboardVisible = false;
} else {
focusNode.unfocus();
await Future<void>.delayed(const Duration(milliseconds: 1));
if(!mounted) return;
FocusScope.of(context).requestFocus(focusNode);
}
} else {
assert(!isKeyboardVisible);
setState(() {
isEmojiKeyboardVisible = true;
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold( // wrapping with ScaffoldMessenger does NOT fix this bug
backgroundColor: Colors.white.withOpacity(0.5),
resizeToAvoidBottomInset: false,
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Expanded(child: SizedBox(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Column(
children: [
Expanded(
child: Container(
height: 200,
),
),
Row(
children: [
IconButton(
icon: Icon(isKeyboardVisible || !isEmojiKeyboardVisible ? Icons.emoji_emotions_outlined : Icons.keyboard_rounded),
onPressed: onEmojiButtonPressed,
),
Expanded(
child: TextField(
focusNode: focusNode,
),
),
IconButton(
icon: const Icon(Icons.send),
onPressed: () => ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('A snack!'))),
),
],
),
],
),
),
),
Offstage(
offstage: !isEmojiKeyboardVisible,
child: SizedBox(
height: _keyboardHeight,
child: Container(color: Colors.red),
),
),
],
),
),
);
}
}
Dependencies
keyboard_utils: ^1.3.4
What I've tried
I tried to wrap the Scaffold of Screen2 with a ScaffoldMessenger. This doesn't fix my problem. In that case, no SnackBar was shown at all if the keyboard was open.
Edit: I also created an GitHub issue for that but I don't expect an answer soon: https://github.com/flutter/flutter/issues/105406#issuecomment-1147194647
Edit 2: A workaround for this issue is to use SnackBarBehaviod.floating and a bottom margin, for example:
SnackBar(
content: Text('A snack!'),
margin: EdgeInsets.only(bottom: 350.0),
behavior: SnackBarBehavior.floating,
)
But this is not a satisfying solution.
I am trying to display an information dialog when starting an application.
After closing, another window appears asking for permission. I call it all in the initState function. It works, but I noticed that this first info dialog also closes on its own when 15 seconds have elapsed. As I understand, this is because the application has loaded and the context is lost.
And when I change runApp(MyApp()) to runApp(MaterialApp(home: MyApp())). It works, the popup doesn't dissapear. But the other showdialogs on other pages didn't close automatically (Navigator.of(context).pop() and Navigator.pop(context) doesnt work.
How do I properly pass context to my initial showdialog so that it doesn't disappear when the start page loads?
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatefulWidget {
static final navKey = new GlobalKey<NavigatorState>();
const MyApp({Key navKey}) : super(key: navKey);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
final context = MyApp.navKey.currentState.overlay.context;
await showDialogIfFirstLoaded(context);
await initPlatformState();
});
}
showDialogIfFirstLoaded(BuildContext context, prefs) async {
bool isFirstLoaded = prefs.getBool(keyIsFirstLoaded);
if (isFirstLoaded == null) {
return showDialog(
context: context,
builder: (BuildContext context) {
// return object of type Dialog
return new AlertDialog(
// title: new Text("title"),
content: new Text("//"),
actions: <Widget>[
new FlatButton(
child: new Text(".."),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey:MyApp.navKey,
home: new SplashScreen(),}
class SplashScreen extends StatefulWidget {
#override
_SplashScreenState createState() => new _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> with SingleTickerProviderStateMixin {
Timer _timer;
bool _visible = true;
startTime() async {
_timer = Timer(new Duration(seconds: 5), navigationPage);
}
void navigationPage() {
Navigator.of(context).pushReplacementNamed('/home');
}
#override
void initState() {
_timer = Timer(Duration(seconds: 4),
() => setState(
() {
_visible = !_visible;
},
),
);
startTime();
super.initState();
}
#override
void dispose() {
_timer.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return new Stack(
children: <Widget>[
Container(
width: double.infinity,
child: Image.asset('images/bg.jpg',
fit: BoxFit.cover,
height: 1200,
),
),
Container(
width: double.infinity,
height: 1200,
color: Color.fromRGBO(0, 0, 0, 0.8),
),
Container(
alignment: Alignment.center,
child: Row(
children: <Widget>[
Expanded(
flex: 2,
child: Container(
child: Text(''),
),
),
],
),
),
],
);
}
}
I am unable to get a basic implementation of AnimatedCrossFade working. I am able to change the state successfully but the animation does not trigger when the state is changed. Here is what I have:
class Home extends StatefulWidget {
#override
HomeState createState() => HomeState();
}
class HomeState extends State<Home> {
bool showPost;
#override
void initState() {
showPost = true;
super.initState();
}
#override
Widget build(BuildContext context) {
return CupertinoTabView(builder: (context) {
return getPage();
});
}
Widget getPage() {
return Center(
child: Row(children: [
CupertinoButton(
child: Text("press"),
onPressed: () {
setState(() {
showPost = !showPost;
});
log(showPost.toString());
},
),
AnimatedCrossFade(
duration: const Duration(seconds: 3),
firstChild: Center(child: Text("First Option")),
secondChild: Center(
child: Text("Second Option")),
crossFadeState:
showPost ? CrossFadeState.showFirst : CrossFadeState.showSecond)
]));
}
}
I'm new to flutter.I have here 3 classes which are the Login(), MainMenu() which is the screen after already logged, and this MyDrawer()which is a drawer of my App.
What I'm trying to do is I want to access the signOut() method from Login(). How would I do it or what should I do to redesign my code. I've tried below accessing it and it receives and exception The method 'call' was called on null.
This is a code snippet from my full code:
class Login extends StatefulWidget {
#override
_LoginState createState() => _LoginState();
}
enum LoginStatus { notSignIn, signIn }
class _LoginState extends State<Login> {
LoginStatus _loginStatus = LoginStatus.notSignIn;
String email, password;
final _key = new GlobalKey<FormState>();
bool _secureText = true;
signOut() async {
SharedPreferences preferences = await SharedPreferences.getInstance();
setState(() {
_loginStatus = LoginStatus.notSignIn;
});
}
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
switch (_loginStatus) {
case LoginStatus.notSignIn:
return Scaffold(
backgroundColor: Colors.cyan,
body: Center(
child: ListView(
shrinkWrap: true,
padding: EdgeInsets.all(15.0),
children: <Widget>[
Center(
child: Container(
padding: const EdgeInsets.all(8.0),
color: Colors.cyan,
child: Form(
key: _key,
break;
case LoginStatus.signIn:
return MainMenu();
break;
}
}
}
class MainMenu extends StatefulWidget {
#override
_MainMenuState createState() => _MainMenuState();
}
class _MainMenuState extends State<MainMenu> {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
)
)
}
class MyDrawer extends StatefulWidget {
final Function onTap;
final VoidCallback signOut;
MyDrawer(
{this.onTap,this.signOut
});
#override
_MyDrawerState createState() => _MyDrawerState();
}
class _MyDrawerState extends State<MyDrawer> {
signOut() {
setState(() {
widget.signOut();
});
}
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return SizedBox(
width: MediaQuery
.of(context)
.size
.width * 0.7,
child: Drawer(
child: Container(
height:100,
color: Colors.white,
child: ListView(
padding: EdgeInsets.all(0),
children: <Widget>[
ListTile(
leading: Icon(Icons.exit_to_app,color: Colors.cyan, size: 30.0),
onTap: () {
signOut();
},
title: Text("Logout",
style: TextStyle(color: Colors.black,fontWeight: FontWeight.w500, fontSize: 18),
),
),
],
),
),
),
);
}
}
I'm really stuck with this problem. Any help would be greatly appreciated. Thanks!