setState() or markNeedsBuild() called when widget tree was locked - flutter

Flutter
i am trying to display a widget into Stack IF an condition (true or false ) , and it work with no problems
but when i need to change the condition bool into SetState to hide the Widget again , it is also work but with annoying error messages whish is setState() or markNeedsBuild() called when widget tree was locked.
my code is so complicated but i am gonna show a simple similar example
bool displayWidget = false;
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Stack(
children: [
Container(
child: TextButton(
onPressed: () {
final result = await FilePicker.platform.pickFiles(allowMultiple: false );
if (result == null) return;
final path = result.files.single.path;
setState(() => displayWidget = true);
},
child: Text ("studio")
),
),
displayWidget?
GestureDetector(
onTap: ()=> setState(() => displayWidget = false), // the error happen when i click here
child: Container(
child: Image.asset("here is the picture in full secreen"),
),
):Container()
],
),
);
}
}
i know there is a photo viewer better than this way :D but i only give a simple example for other real case

First of all I would like to ask you where is #override? Did you forget to add that in your question? If yes then that error might have something to do with that. If no then try declaring the bool value above #override.

please see if this example can be helpful to you (I simplified yours just a little bit more so it can be easily run in dartpad).
in your case onPressed function should contain your file picking logic and have a return type as you expect it to be (probably, String?) and check if returned value isn't null, then show your image
class MyWidget extends StatefulWidget {
#override
State<MyWidget> createState() => MyWidgetState();
}
class MyWidgetState extends State<MyWidget> {
bool displayWidget = false;
bool onPressed(bool isShown) {
//instead of this mocked function pickFile here
return !isShown;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Row(
children: [
Container(
child: TextButton(
onPressed: () {
displayWidget = onPressed(displayWidget);
setState(() => displayWidget);
},
child: Text ("Show or hide text"),
),
),
if(displayWidget)
GestureDetector(
onTap: ()=> setState(() => displayWidget = !displayWidget),
child: Container(
child: Text('This text is to be shown'),
),
),
],
),
);
}
}

I found the solution from the official website of flutter
simply wrap set state into
in this solution would be for most cases
if(!mounted) {
setState
}
or
if(mounted) { // depends on your widget case
setState
}
source https://www.kindacode.com/article/flutter-error-setstate-called-after-dispose/
if not , try this
WidgetsBinding.instance
.addPostFrameCallback((_) => setState(() {}));
source https://www.thetopsites.net/article/50288698.shtml
and i find same error with many answers in stackoverflow
press below
Flutter setState() or markNeedsBuild() called when widget tree was locked

Related

Flutter Riverpod : The member 'state' can only be used within instance members of subclasses of 'package:state_notifier/state_notifier.dart'

I have simple example about toggle widget,my expectation after click button i show circularProgressIndicator then after 3 second i showing Text. For my example i use riverpod_hooks and flutter_hooks.
LoadingProvider
class IsLoading extends StateNotifier<bool> {
IsLoading() : super(false);
void toggleLoading(bool value) => state = value;
}
final isLoadingProvider = StateNotifierProvider((ref) => IsLoading());
Main.dart
class TestingApp extends HookWidget {
#override
Widget build(BuildContext context) {
final IsLoading isLoading = useProvider(isLoadingProvider);
return Scaffold(
body: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(
child: isLoading.state ? CircularProgressIndicator() : Text('This Is Your Data'),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: FlatButton(
onPressed: () => showLoading(isLoading), child: Text('Toggle Loading')),
),
],
),
),
);
}
void showLoading(IsLoading isLoading) async {
isLoading.toggleLoading(true);
print("Proses");
await Future.delayed(const Duration(seconds: 3));
print("Done");
isLoading.toggleLoading(false);
}
}
But when i press the button , it's not show progressIndicator and still showing the text and i got this warning :
The member 'state' can only be used within instance members of subclasses of 'package:state_notifier/state_notifier.dart'.
I missed something ?
My source code following the documentation like this :
It says right there that it won’t trigger the method to rebuild when you call increment like that.
so instead of isLoading.toggleLoading(true)
call with a read method so it can rebuild the state like that
isLoading.read(context).toggleLoading(true)
Provide two types for your StateNotifierProvider, the type of class you are returning and the state the class has.
class IsLoading extends StateNotifier<bool> {
IsLoading() : super(false);
void toggleLoading(bool value) => state = value;
}
final isLoadingProvider =
StateNotifierProvider<IsLoading, bool>((ref) => IsLoading());
in your build method (of a ConsumerStateWidget) use:
final isLoading = ref.watch(isLoadingProvider);
return Scaffold(body: Center(child:
isLoading ? CircularProgressIndicator() : Text('This Is Your Data'),))

Flutter-web TextFormField issue

usually I can disable/grey-out a button until a TextFormField meets certain parameters in flutter by something like this:
TextFormField(
controller: _controller
value: (value)
)
SubmitButton(
onPressed: _controller.text.isNotEmpty ? _submit : null;
)
But when compiled as a website the Button seems no longer aware of the controller value...
I have tried targeting in several different ways, e.g. _controller.value.text.isEmpty and _controller.text.isEmpty...
I'm guessing I'm missing something or this method just isn't possible for web ... Is there any other way to get the same result?
To be honest, your code shouldn't work in flutter mobile either, but may be works because of screen keyboard causes widget rebuild when showing or hiding.
To fix this issue we have to use stateful widget with state variable like canSubmit and update it in textField's listener onChange with setState method. Then every time the text changes, our stateful widget will update the submit button..
class Page extends StatefulWidget {
#override
_PageState createState() => _PageState();
}
class _PageState extends State<Page> {
bool canSubmit;
#override
void initState() {
canSubmit = false;
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: <Widget>[
TextField(
onChanged: (value) {
setState(() {
canSubmit = value.isNotEmpty;
});
},
),
RaisedButton(
onPressed: canSubmit ? _submit : null,
child: Text('Submit'),
)
],
),
),
);
}
void _submit() {
print('Submitted');
}
}

AlertDialog function runs on everypage in flutter

theAlertDialog in this code should run only when the user access the Account page (obviously), but while testing it, it runs on the Account page and all the next pages, and it is even duplicated, i mean when i head from Account page to another page the AlertDialog will be displayed twice
class Account extends StatefulWidget {
#override
_AccountState createState() => _AccountState();
}
class _AccountState extends State<Account> {
#override
Widget build(BuildContext context) {
Future.delayed(Duration.zero, () => FirstRun(context));
return Scaffold(
//there are alot of widgets here like drawer but all of it works fine
//i don't think its necessary to write it
);
}
FirstRun(BuildContext context) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool first = (prefs.getBool('firstUse'));
print('Pressed $first');
if (first == null) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: Color(0xaa6b6b6b),
elevation: 10,
content: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
'first run dialog',
overflow: TextOverflow.ellipsis,
maxLines: 6,
style: TextStyle(
color: Colors.white,
fontSize: 24,
),
textAlign: TextAlign.center,
),
Container(
child: MaterialButton(
onPressed: () {
prefs.setBool('firstUse', false);
Navigator.of(context).pop();
print('Pressed $first');
},
child: Text(
'ok',
),
))
],
),
);
},
);
}
}
}
maybe it happens because you start showing the alert on build method. try to show it on initState method of the Account widget.
class _AccountState extends State<Account> {
#override
initState() {
Future.delayed(Duration.zero, () => FirstRun(this.context));
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
//there are alot of widgets here like drawer but all of it works fine
//i don't think its necessary to write it
);
}
as a work around i call the next page using Navigator.Replace instead of Navigator.push , but i dont think this is a real solution

How do i change a boolean in a StatefullWidget from another one?

i'm brand new to Flutter.
I'm trying to open a panel pressing a button and than closing it by pressing a button on that panel.
I've managed to do it by writing the code in the same page.
What i can't do is splitting the code and keep everything working.
What I'm actually doing is calling a variable in the State of a widget that is initialized False and then with an if statement i'm calling: or an empty container or the panel i want.
When i press the button i call SetState(){} and the variable changes to true to let the panel appears, then in the panel there's button that do opposite thing.
Assuming that what i'm doing it is correct. How to i keep doing this with the panel refactored in a new page?
I've red something about streams and inherited widgets but i haven't completely understood
If I understand correctly, you want to notify a StatefullWidget from another StatefullWidget. There are several approaches on this one but since you've mentioned Streams, I'll try to post an example and explain a bit this scenario.
So basically, you can consider the streams like a pipe linked to a faucet in one end and the other end it's added into a cup (the end can be split in multiple ends and put in multiple cups, "broadcast streams").
Now, the cup is the listener (subscriber) and waits for water to drop trough the pipe.
The faucet is the emitter, and it will emit water droplets when the faucet is opened.
The faucet can be opened when the other end is put into a cup, this is a smart faucet with some cool sensors, (the emitter will start emitting events when a subscriber is "detected).
The droplets are actual events that are happening in the the app.
Also you must remember to close the faucet in order to avoid a massive leak from your cup into the kitchen floor. (you must cancel the subscribers when you've done handling events to avoid a leak).
Now for your particular case here's the code snippet that kind of illustrate the above metaphor:
class ThePannel extends StatefulWidget { // this is the cup
final Stream<bool> closeMeStream; // this is the pipe
const ThePannel({Key key, this.closeMeStream}) : super(key: key);
#override
_ThePannelState createState() => _ThePannelState(closeMeStream);
}
class _ThePannelState extends State<ThePannel> {
bool _closeMe = false;
final Stream<bool> closeMeStream;
StreamSubscription _streamSubscription;
_ThePannelState(this.closeMeStream);
#override
void initState() {
super.initState();
_streamSubscription = closeMeStream.listen((shouldClose) { // here we listen for new events coming down the pipe
setState(() {
_closeMe = shouldClose; // we got a new "droplet"
});
});
}
#override
void dispose() {
_streamSubscription.cancel(); // THIS IS QUITE IMPORTANT, we have to close the faucet
super.dispose();
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
SomeWidgetHere(shouldClose: _closeMe),
RaisedButton(
onPressed: () {
setState(() {
_closeMe = true;
});
},
)
],
);
}
}
class SomeWidgetThatUseThePreviousOne extends StatefulWidget { // this one is the faucet, it will emit droplets
#override
_SomeWidgetThatUseThePreviousOneState createState() =>
_SomeWidgetThatUseThePreviousOneState();
}
class _SomeWidgetThatUseThePreviousOneState
extends State<SomeWidgetThatUseThePreviousOne> {
final StreamController<bool> thisStreamWillEmitEvents = StreamController(); // this is the end of the pipe linked to the faucet
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
ThePannel(closeMeStream: thisStreamWillEmitEvents.stream), // we send the other end of the pipe to the cup
RaisedButton(
child: Text("THIS SHOULD CLOSE THE PANNEL"),
onPressed: () {
thisStreamWillEmitEvents.add(true); // we will emit one droplet here
},
),
RaisedButton(
child: Text("THIS SHOULD OPEN THE PANNEL"),
onPressed: () {
thisStreamWillEmitEvents.add(false); // we will emit another droplet here
},
)
],
);
}
#override
void dispose() {
thisStreamWillEmitEvents.close(); // close the faucet from this end.
super.dispose();
}
}
I hope that my analogy will help you understand a bit the streams concept.
If you want to open an dialog (instead of what you call a "panel") you can simply give the selected data back when you close the dialog again.
You can find a good tutorial here: https://medium.com/#nils.backe/flutter-alert-dialogs-9b0bb9b01d28
you can navigate and return data from another screen like that :
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'Returning Data',
home: HomeScreen(),
));
}
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Returning Data Demo'),
),
body: Center(child: SelectionButton()),
);
}
}
class SelectionButton extends StatelessWidget {
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
_navigateAndDisplaySelection(context);
},
child: Text('Pick an option, any option!'),
);
}
// A method that launches the SelectionScreen and awaits the result from
// Navigator.pop!
_navigateAndDisplaySelection(BuildContext context) async {
// Navigator.push returns a Future that will complete after we call
// Navigator.pop on the Selection Screen!
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => SelectionScreen()),
);
// After the Selection Screen returns a result, hide any previous snackbars
// and show the new result!
Scaffold.of(context)
..removeCurrentSnackBar()
..showSnackBar(SnackBar(content: Text("$result")));
}
}
class SelectionScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Pick an option'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
onPressed: () {
// Close the screen and return "Yep!" as the result
Navigator.pop(context, 'Yep!');
},
child: Text('Yep!'),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
onPressed: () {
// Close the screen and return "Nope!" as the result
Navigator.pop(context, 'Nope.');
},
child: Text('Nope.'),
),
)
],
),
),
);
}
}
for more details about navigation:
https://flutter.dev/docs/cookbook/navigation/returning-data

How to implement a Slider within an AlertDialog in Flutter?

I am learning app development on Flutter and can't get my Slider to work within the AlertDialog. It won't change it's value.
I did search the problem and came across this post on StackOverFlow:
Flutter - Why slider doesn't update in AlertDialog?
I read it and have kind of understood it. The accepted answer says that:
The problem is, dialogs are not built inside build method. They are on a different widget tree. So when the dialog creator updates, the dialog won't.
However I am not able to understand how exactly does it have to be implemented as not enough background code is provided.
This is what my current implementation looks like:
double _fontSize = 1.0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(qt.title),
actions: <Widget>[
IconButton(
icon: Icon(Icons.format_size),
onPressed: () {
getFontSize(context);
},
),
],
),
body: ListView.builder(
padding: EdgeInsets.symmetric(vertical: 15.0),
itemCount: 3,
itemBuilder: (context, index) {
if (index == 0) {
return _getListTile(qt.scripture, qt.reading);
} else if (index == 1) {
return _getListTile('Reflection:', qt.reflection);
} else {
return _getListTile('Prayer:', qt.prayer);
}
})
);
}
void getFontSize(BuildContext context) {
showDialog(context: context,builder: (context){
return AlertDialog(
title: Text("Font Size"),
content: Slider(
value: _fontSize,
min: 0,
max: 100,
divisions: 5,
onChanged: (value){
setState(() {
_fontSize = value;
});
},
),
actions: <Widget>[
RaisedButton(
child: Text("Done"),
onPressed: (){},
)
],
);
});
}
Widget parseLargeText(String text) {...}
Widget _getListTile(String title, String subtitle) {...}
I understand that I will need to make use of async and await and Future. But I am not able to understand how exactly. I've spent more than an hour on this problem and can't any more. Please forgive me if this question is stupid and noobish. But trust me, I tried my best.
Here is a minimal runnable example. Key points:
The dialog is a stateful widget that stores the current value in its State. This is important because dialogs are technically separate "pages" on your app, inserted higher up in the hierarchy
Navigator.pop(...) to close the dialog and return the result
Usage of async/await
import 'package:flutter/material.dart';
void main() => runApp(App());
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
double _fontSize = 20.0;
void _showFontSizePickerDialog() async {
// <-- note the async keyword here
// this will contain the result from Navigator.pop(context, result)
final selectedFontSize = await showDialog<double>(
context: context,
builder: (context) => FontSizePickerDialog(initialFontSize: _fontSize),
);
// execution of this code continues when the dialog was closed (popped)
// note that the result can also be null, so check it
// (back button or pressed outside of the dialog)
if (selectedFontSize != null) {
setState(() {
_fontSize = selectedFontSize;
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text('Font Size: ${_fontSize}'),
RaisedButton(
onPressed: _showFontSizePickerDialog,
child: Text('Select Font Size'),
)
],
),
),
);
}
}
// move the dialog into it's own stateful widget.
// It's completely independent from your page
// this is good practice
class FontSizePickerDialog extends StatefulWidget {
/// initial selection for the slider
final double initialFontSize;
const FontSizePickerDialog({Key key, this.initialFontSize}) : super(key: key);
#override
_FontSizePickerDialogState createState() => _FontSizePickerDialogState();
}
class _FontSizePickerDialogState extends State<FontSizePickerDialog> {
/// current selection of the slider
double _fontSize;
#override
void initState() {
super.initState();
_fontSize = widget.initialFontSize;
}
#override
Widget build(BuildContext context) {
return AlertDialog(
title: Text('Font Size'),
content: Container(
child: Slider(
value: _fontSize,
min: 10,
max: 100,
divisions: 9,
onChanged: (value) {
setState(() {
_fontSize = value;
});
},
),
),
actions: <Widget>[
FlatButton(
onPressed: () {
// Use the second argument of Navigator.pop(...) to pass
// back a result to the page that opened the dialog
Navigator.pop(context, _fontSize);
},
child: Text('DONE'),
)
],
);
}
}
You just need to warp the AlertDialog() with a StatefulBuilder()