I want to change the text on popup with user interaction but it is not changing. I've tried navigator.pop(context) and relaunch show method. It correctly work but is it a correct way? And can I change value on the popup without Navigator.pop. Why doesn't it work?
Here is my code.
import 'package:flutter/material.dart';
import 'package:rflutter_alert/rflutter_alert.dart';
void main() => runApp(RatelApp());
class RatelApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: Text('RFlutter Alert by Ratel'),
),
body: Home(),
),
);
}
}
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
String val = "Deneme";
showAlertDialog(BuildContext context, Function onPressed) {
// set up the button
Widget okButton = FlatButton(
child: Text(val),
onPressed: onPressed,
);
// set up the AlertDialog
AlertDialog alert = AlertDialog(
title: Text("My title"),
content: Text("This is my message."),
actions: [
okButton,
],
);
// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
#override
Widget build(BuildContext context) {
return Container(
child: RaisedButton(
child: Text("Show Popup"),
onPressed: () {
showAlertDialog(context, (){
setState(() {
val = "changed";
});
});
},
),
);
}
}
Dialogs are usually stateless and are also not a part of the state of the calling Widget so calling that setState method isn't doing anything for the dialog. To make a dialog that can have changing contents use a StatefulBuilder.
The docs have an example that shows how to use it in a dialog just like in your application.
Docs example:
await showDialog<void>(
context: context,
builder: (BuildContext context) {
int selectedRadio = 0;
return AlertDialog(
content: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Column(
mainAxisSize: MainAxisSize.min,
children: List<Widget>.generate(4, (int index) {
return Radio<int>(
value: index,
groupValue: selectedRadio,
onChanged: (int value) {
setState(() => selectedRadio = value);
},
);
}),
);
},
),
);
},
);
You need to wrap your AlertDialog with StatefulBuilder widget.
Related
I'm trying to get the value from the TextField then compare it to a string = '123'
If the value = '123' then alert 'Successful!' or else alert 'Failed!'
Here is my code:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Retrieve Text Input',
home: MyCustomForm(),
);
}
}
class MyCustomForm extends StatefulWidget {
#override
_MyCustomFormState createState() => _MyCustomFormState();
}
class _MyCustomFormState extends State<MyCustomForm> {
TextEditingController myController = new TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Retrieve Text Input'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: TextField(
controller: myController,
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
if(myController == '123')
{
showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: Text('Successful!'),
);
},
);
}
else
{
showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: Text('Failed!'),
);
},
);
}
},
child: Icon(Icons.text_fields),
),
);
}
}
My code always shows failed even I entered '123'. Can someone help me to figure out what I did wrong here? Thank you
To access the text inside a TextField Controller, you need to use myController.text, the myController is just a controller itself so you can't compare it to a String like that.
So, you should change this line
if(myController == '123')
into this
if(myController.text == '123')
I'm new in Flutter and I implemented the bloc architecture with streambuilder.
I created 2 pages with just a button which change my background color. All of theses pages are listening a stream to change the background color but when I change on the first page, it doesn't on the second.
But I want all my application change if 1 page decide to change it
Do I need to initialize a singleton bloc that my 2 screens used it ? Because for the moment each screen initializes its own bloc
Here is an example of 1 page (the second one is the same)
class Test extends StatelessWidget {
final ColorBloc _bloc = ColorBloc();
#override
Widget build(BuildContext context) {
return StreamBuilder<Response<ColorResponse>>(
stream: _bloc.stream,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Scaffold(
appBar: AppBar(
title: Text('First Route clicked'),
),
backgroundColor: snapshot.data.data.color,
body: new Center(
child: new InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Act2()),
);
}, // Handle your callback
child: Ink(height: 100, width: 100, color: Colors.blue),
)),
floatingActionButton: FloatingActionButton(
onPressed: () {
_bloc.changeColor(Colors.yellow);
},
child: Icon(Icons.navigation),
backgroundColor: Colors.green,
));
}
return Scaffold(
appBar: AppBar(
title: Text('First Route'),
),
body: Center(
child: new InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Act2()),
);
}, // Handle your callback
child: Ink(height: 200, width: 200, color: Colors.red))),
floatingActionButton: FloatingActionButton(
onPressed: () {
_bloc.changeColor(Colors.yellow);
},
child: Icon(Icons.navigation),
backgroundColor: Colors.green,
));
},
);
}
}
To change the state of all screen when a bloc fires an event, you can use multiple StreamBuilder, but all of them need to listen to the bloc that fire the event. You can try these 2 ways:
Passing the bloc as parameter into the 2nd screen
class Test extends StatelessWidget {
final ColorBloc _bloc = ColorBloc();
#override
Widget build(BuildContext context) {
return StreamBuilder<Response<ColorResponse>>(
// ... other lines
body: new Center(
child: new InkWell(
onTap: () {
// Pass your bloc to the 2nd screen
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Act2(bloc: _bloc)),
);
},
// ... other lines
Use package such as provider package to pass the bloc down the tree. In your first screen, you can do this:
class Test extends StatelessWidget {
final ColorBloc _bloc = ColorBloc();
#override
Widget build(BuildContext context) {
// Use Provider to provide the bloc down the widget tree
return Provider(
create: (_) => _bloc,
child: StreamBuilder<Response<ColorResponse>>(
// ... other lines
Then in the 2nd screen (which I assume is Act2()), you get the ColorBloc from the Provider:
class Act2 extends StatefulWidget {
#override
_Act2State createState() => _Act2State();
}
class _Act2State extends State<Act2> {
ColorBloc _colorBloc;
#override
void didChangeDependencies() {
// Get the bloc in the 1st page
_colorBloc = Provider.of<ColorBloc>(context);
super.didChangeDependencies();
}
#override
Widget build(BuildContext context) {
return StreamBuilder<Response<ColorResponse>>(
// Use the bloc like in the 1st page
stream: _colorBloc.stream,
builder: (context, snapshot) {
if (snapshot.hasData) {
// ... other lines
Small note: When using StreamBuilder you could initiate the value without the need to duplicate codes. Since I don't know the structure of your Response object, I'm taking Response(ColorResponse(color: Colors.green)) as the example:
// ... other lines
#override
Widget build(BuildContext context) {
return Provider(
create: (_) => _bloc,
child: StreamBuilder<Response<ColorResponse>>(
// Initiate your data here
initialData: Response(ColorResponse(color: Colors.green)),
stream: _bloc.stream,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Scaffold(
appBar: AppBar(
title: Text('First Route clicked'),
),
backgroundColor: snapshot.data.data.color,
// ... other lines
}
// Don't need to copy the above code block for the case when the data is not streamed yet
return Container(child: Center(child: CircularProgressIndicator()));
},
),
);
}
I've got an app having file structure like this: main -> auth -> home -> secret. Key codes are as below:
For main.dart:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
StreamProvider<User>.value(value: AuthService().user),
ChangeNotifierProvider(create: (context) => SecretProvider()),
],
child: MaterialApp(
title: 'My Secrets',
home: AuthScreen(),
),
);
}
}
For home.dart:
class HomeScreen extends StatelessWidget {
final AuthService _auth = AuthService();
#override
Widget build(BuildContext context) {
var secretProvider = Provider.of<SecretProvider>(context);
return ChangeNotifierProvider(
create: (context) => SecretProvider(),
child: Scaffold(
appBar: AppBar(
// some codes...
),
body: StreamBuilder<List<Secret>>(
stream: secretProvider.secrets,
builder: (context, snapshot) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 15.0),
child: ListView.separated(
// return 0 if snapshot.data is null
itemCount: snapshot.data?.length ?? 0,
itemBuilder: (context, index) {
return ListTile(
leading: Icon(Icons.web),
title: Text(snapshot.data[index].title),
trailing: Icon(Icons.edit),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecretScreen(
secret: snapshot.data[index],
),
),
);
},
);
},
separatorBuilder: (context, index) {
return Divider();
},
),
);
},
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecretScreen()),
);
},
),
),
);
}
}
For secret.dart:
class SecretScreen extends StatefulWidget {
final Secret secret;
SecretScreen({this.secret});
#override
_SecretScreenState createState() => _SecretScreenState();
}
class _SecretScreenState extends State<SecretScreen> {
// some codes...
#override
void initState() {
final secretProvider = Provider.of<SecretProvider>(context, listen: false);
// some codes...
super.initState();
}
#override
void dispose() {
// some codes...
super.dispose();
}
#override
Widget build(BuildContext context) {
final secretProvider = Provider.of<SecretProvider>(context);
return Scaffold(
// some codes...
);
}
}
These codes worked just fine, but later on I decided to move the ChangeNotifierProvider from main.dart to home.dart due to some class instance life cycle issue. The new code is like below:
For main.dart:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
StreamProvider<User>.value(value: AuthService().user),
],
child: MaterialApp(
title: 'My Secrets',
home: AuthScreen(),
),
);
}
}
For home.dart:
class HomeScreen extends StatelessWidget {
final AuthService _auth = AuthService();
#override
Widget build(BuildContext context) {
// var secretProvider = Provider.of<SecretProvider>(context);
return ChangeNotifierProvider(
create: (context) => SecretProvider(),
child: Consumer<SecretProvider>(
builder: (context, secretProvider, child) {
return Scaffold(
appBar: AppBar(
// some codes...
),
body: StreamBuilder<List<Secret>>(
stream: secretProvider.secrets,
// stream: SecretProvider().secrets,
builder: (context, snapshot) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 15.0),
child: ListView.separated(
// return 0 if snapshot.data is null
itemCount: snapshot.data?.length ?? 0,
itemBuilder: (context, index) {
return ListTile(
leading: Icon(Icons.web),
title: Text(snapshot.data[index].title),
trailing: Icon(Icons.edit),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecretScreen(
secret: snapshot.data[index],
),
),
);
},
);
},
separatorBuilder: (context, index) {
return Divider();
},
),
);
},
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecretScreen()),
);
},
),
);
},
),
);
}
}
Basically, I just moved the ChangeNotifierProvider to home.dart and used a Consumer to pass the context, but this time, whenever I navigate to secret screen, it prompts me error like below:
Could not find the correct Provider<SecretProvider> above this SecretScreen Widget
This likely happens because you used a `BuildContext` that does not include the provider
of your choice.
This BuildContext is really bugging me. Even if I'm having ChangeNotifierProvider one level lower than before, the SecretScreen widget should still be aware of the SecretProvider that passed on from HomeScreen because it's still the child of HomeScreen and according to my knowledge, the context should contain the SecretProvider.
You get this error because your SecretProvider instance is part of HomeScreen which is not a parent of SecretScreen.
In order, when you push a new page, this new page is not a descendent of the previous one so you can't access to inherited object with the .of(context) method.
Here the a schema representing the widget tree to explain the situation :
With a Provider on top of MaterialApp (the navigator) :
Provider
MaterialApp
HomeScreen -> push SecretScreen
SecretScreen -> Here we can acces the Provider by calling Provider.of(context) because the context can access to its ancestors
With a Provider created in HomeScreen :
MaterialApp
HomeScreen -> push SecretScreen
Provider -> The provider is part of HomeScreen
SecretScreen -> The context can't access to the Provider because it's not part of its ancestors
I hope my answer is pretty clear and will help you to understand what happens ;)
I'm using provider 4.3.2 in this flutter code, this is a simple flutter app that has a text filed, flat button, and a list view builder that contain the text widget. I created a class ListData that has the list and is shown in the list view builder using provider. Here is the problem, I created a addData method in the ListData class. I used this method to add data to list using provider in the onPressed method of flat button add it is throwing error, unable to find. the solution for this problem. Also this is a short form of my main app
import 'dart:collection';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
String data;
return ChangeNotifierProvider(
create: (context) => ListData(),
child: MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("list"),
),
body: Column(
children: [
TextField(
onChanged: (value) => data = value,
),
FlatButton(
child: Text("Add"),
color: Colors.blue,
onPressed: () {
Provider.of<ListData>(context).addData(data);
},
),
Expanded(
child: MyListView(),
),
],
),
),
),
);
}
}
class MyListView extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ListView.builder(
itemBuilder: (context, index) {
return Text(Provider.of<ListData>(context).listData[index]);
},
itemCount: Provider.of<ListData>(context).listCount,
);
}
}
class ListData extends ChangeNotifier {
List _listData = [
'Hello',
"hi",
];
UnmodifiableListView get listData {
return UnmodifiableListView(_listData);
}
int get listCount {
return _listData.length;
}
void addData(String data) {
_listData.add(data);
notifyListeners();
}
}
You can copy paste run full code below
You need Builder and listen: false
code snippet
Builder(builder: (BuildContext context) {
return FlatButton(
child: Text("Add"),
color: Colors.blue,
onPressed: () {
Provider.of<ListData>(context, listen: false).addData(data);
},
);
}),
working demo
full code
import 'dart:collection';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
String data;
return ChangeNotifierProvider(
create: (context) => ListData(),
child: MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("list"),
),
body: Column(
children: [
TextField(
onChanged: (value) => data = value,
),
Builder(builder: (BuildContext context) {
return FlatButton(
child: Text("Add"),
color: Colors.blue,
onPressed: () {
Provider.of<ListData>(context, listen: false).addData(data);
},
);
}),
Expanded(
child: MyListView(),
),
],
),
),
),
);
}
}
class MyListView extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ListView.builder(
itemBuilder: (context, index) {
return Text(Provider.of<ListData>(context).listData[index]);
},
itemCount: Provider.of<ListData>(context).listCount,
);
}
}
class ListData extends ChangeNotifier {
List _listData = [
'Hello',
"hi",
];
UnmodifiableListView get listData {
return UnmodifiableListView(_listData);
}
int get listCount {
return _listData.length;
}
void addData(String data) {
_listData.add(data);
notifyListeners();
}
}
You need to wrap your FlatButton in a Consumer widget because Provider.of is called with a BuildContext that is an ancestor of the provider.
return ChangeNotifierProvider(
create: (_) => ListData(),
child: Consumer<ListData>(
builder: (_, listData, __) => FlatButton(onPressed: () => listData.addData(data)),
},
);
Check out this to learn more with simple examples to help you understand why you get the error and how to use it.
https://pub.dev/documentation/provider/latest/provider/Consumer-class.html
Minimal code:
void main() => runApp(MaterialApp(home: MainPage()));
class MainPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
drawer: MyDrawer(),
);
}
}
class MyDrawer extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Drawer(
child: RaisedButton(
onPressed: () {
// Close the Drawer, not the Dialog.
Timer(Duration(seconds: 2), () => Navigator.of(context, rootNavigator: true).pop());
// Show the Dialog and keep it in opened state.
showDialog(
context: context,
builder: (_) => AlertDialog(title: Text('FooDialog')),
);
},
child: Text('Show Dialog'),
),
);
}
}
On pressing the button, I am showing dialog and after 2s I want to close the Drawer while keeping the Dialog opened on the screen. For this I am using Timer and rootNavigator property of Navigator. However, my dialog is getting dismissed.
Is there any solution for closing the drawer besides using GlobalKey<DrawerControllerState> stuff?
class MyDrawer extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Drawer(
child: RaisedButton(
onPressed: () {
// Close the Drawer, not the Dialog.
Timer(Duration(seconds: 2), () => Scaffold.of(context).openEndDrawer());
// Show the Dialog and keep it in opened state.
showDialog(
context: context,
builder: (_) => AlertDialog(title: Text('FooDialog')),
);
},
child: Text('Show Dialog'),
),
);
}
}
You can use ScaffoldState, to close the drawer. Just keep a track of the time and you are good to go. In this answer, I have told you on how to use the ScaffoldState with your drawer.
This code will help you achieve what you want. I have used the second option from my previous answer, that is, using everything in the MainPage only
class MainPage extends StatelessWidget {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
// this will check for the drawer state and close it
// using _scaffoldKey
timer() {
return Future.delayed(Duration(seconds: 2), (){
// checking whether it is open
if(_scaffoldKey.currentState.isDrawerOpen){
// here how you close it
_scaffoldKey.currentState.openEndDrawer();
}
});
}
Future<void> _showMyDialog(BuildContext context) async {
return showDialog<void>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: Text('AlertDialog Title'),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text('This is a demo alert dialog.'),
Text('Would you like to approve of this message?'),
],
),
),
actions: <Widget>[
FlatButton(
child: Text('Approve'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
// Our drawer
Drawer _drawer(BuildContext context) => Drawer(
child: RaisedButton(
onPressed: (){
timer();
_showMyDialog(context);
},
child: Text('Show Dialog'),
)
);
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(),
drawer: _drawer(context)
);
}
}
Result
Please note: I have not clicked anywhere on the screen, to close the drawer. It goes automatically by the timer()