I have created a Stateful widget to show my Alert Dialog which contains a searchbar , however the search bar text doesn't update on typing and stays blank. I have set the controller of the textfield as TextEditingController() however its still not working.
code
import 'package:flutter/material.dart';
import 'package:flutter_convertor/Data Models/Society.dart';
class MyDialogContent extends StatefulWidget {
#override
_MyDialogContentState createState() => new _MyDialogContentState();
}
class _MyDialogContentState extends State<MyDialogContent> {
#override
void initState(){
super.initState();
}
#override
Widget build(BuildContext context) {
//Search bar
Container searchBar = Container(
child: Padding(
padding: const EdgeInsets.only(left: 0.0, right: 0.0),
child: TextField(
onChanged: (value) {
filterSearchResults(value);
},
controller: TextEditingController(),
decoration: InputDecoration(
labelText: "Search",
prefixIcon: Icon(Icons.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(15.0)))),
),
),
);
AlertDialog dialog = AlertDialog(
title: searchBar,
content: Container(
....
),
);
return dialog;
}
}
The controller works in my main dart file. but not when i put in my Alert Dialog stateful Widget
this one is the same way that you use, this will not work
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
class Sample extends StatefulWidget {
#override
_SampleState createState() => new _SampleState();
}
class _SampleState extends State<Sample> {
List<String> countries = <String>['Belgium','France','Italy','Germany','Spain','Portugal'];
int _selectedCountryIndex = 0;
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_){_showDialog();});
}
_buildList(){
if (countries.length == 0){
return new Container();
}
return new Column(
children: new List<RadioListTile<int>>.generate(
countries.length,
(int index){
return new RadioListTile<int>(
value: index,
groupValue: _selectedCountryIndex,
title: new Text(countries[index]),
onChanged: (int value) {
setState((){
_selectedCountryIndex = value;
});
},
);
}
)
);
}
_showDialog() async{
await showDialog<String>(
context: context,
builder: (BuildContext context){
return new CupertinoAlertDialog(
title: new Text('Please select'),
actions: <Widget>[
new CupertinoDialogAction(
isDestructiveAction: true,
onPressed: (){Navigator.of(context).pop('Cancel');},
child: new Text('Cancel'),
),
new CupertinoDialogAction(
isDestructiveAction: true,
onPressed: (){Navigator.of(context).pop('Accept');},
child: new Text('Accept'),
),
],
content: new SingleChildScrollView(
child: new Material(
child: _buildList(),
),
),
);
},
barrierDismissible: false,
);
}
#override
Widget build(BuildContext context) {
return new Container();
}
}
you should do it like below
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
class Sample extends StatefulWidget {
#override
_SampleState createState() => new _SampleState();
}
class _SampleState extends State<Sample> {
List<String> countries = <String>['Belgium','France','Italy','Germany','Spain','Portugal'];
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_){_showDialog();});
}
_showDialog() async{
await showDialog<String>(
context: context,
builder: (BuildContext context){
return new CupertinoAlertDialog(
title: new Text('Please select'),
actions: <Widget>[
new CupertinoDialogAction(
isDestructiveAction: true,
onPressed: (){Navigator.of(context).pop('Cancel');},
child: new Text('Cancel'),
),
new CupertinoDialogAction(
isDestructiveAction: true,
onPressed: (){Navigator.of(context).pop('Accept');},
child: new Text('Accept'),
),
],
content: new SingleChildScrollView(
child: new Material(
child: new MyDialogContent(countries: countries),
),
),
);
},
barrierDismissible: false,
);
}
#override
Widget build(BuildContext context) {
return new Container();
}
}
class MyDialogContent extends StatefulWidget {
MyDialogContent({
Key key,
this.countries,
}): super(key: key);
final List<String> countries;
#override
_MyDialogContentState createState() => new _MyDialogContentState();
}
class _MyDialogContentState extends State<MyDialogContent> {
int _selectedIndex = 0;
#override
void initState(){
super.initState();
}
_getContent(){
if (widget.countries.length == 0){
return new Container();
}
return new Column(
children: new List<RadioListTile<int>>.generate(
widget.countries.length,
(int index){
return new RadioListTile<int>(
value: index,
groupValue: _selectedIndex,
title: new Text(widget.countries[index]),
onChanged: (int value) {
setState((){
_selectedIndex = value;
});
},
);
}
)
);
}
#override
Widget build(BuildContext context) {
return _getContent();
}
}
Related
I need to display checkboxes selected by the user on the previous page using pop()
I have a function that displays the user's message on the previous page and I need to pass the selected checkboxes in the same way. How to pass them as arguments to pop()?
Screen with checkboxes:
const TextScreen({Key? key}) : super(key: key);
#override
State<TextScreen> createState() => _TextScreenState();
}
class _TextScreenState extends State<TextScreen> {
// initial values for checkboxes
bool _privacy = false;
bool _termsOfUse = false;
// text controller for message input
TextEditingController textController = TextEditingController();
#override
void dispose() {
textController.dispose();
super.dispose();
}
// go to result screen
void getResult(BuildContext context) {
String valueResult = textController.text;
Navigator.pop(context, valueResult);
}
#override
Widget build(BuildContext context) {
//change state for privacy checkbox
_onPrivacyChange(value) {
setState(() {
_privacy = value!;
});
}
//change state for terms of use checkbox
_onTermsOfUSeChange(value) {
setState(() {
_termsOfUse = value!;
});
}
return Scaffold(
appBar: AppBar(
title: const Text('Enter data'),
),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextField(
controller: textController,
decoration: const InputDecoration(labelText: 'Message')),
const SizedBox(height: 20),
CheckboxListTile(
title: const Text('Privacy'),
controlAffinity: ListTileControlAffinity.leading,
value: _privacy,
onChanged: _onPrivacyChange,
contentPadding: EdgeInsets.zero,
),
CheckboxListTile(
title: const Text('Terms of use'),
controlAffinity: ListTileControlAffinity.leading,
value: _termsOfUse,
onChanged: _onTermsOfUSeChange,
contentPadding: EdgeInsets.zero,
),
ElevatedButton(
onPressed: () {
getResult(context);
},
child: const Text('Display result'))
],
)),
);
}
}
Screen with results display:
class ResultScreen extends StatefulWidget {
const ResultScreen({Key? key}) : super(key: key);
#override
State<ResultScreen> createState() => _ResultScreenState();
}
class _ResultScreenState extends State<ResultScreen> {
String? _valueText = '';
#override
Widget build(BuildContext context) {
// navigation to next screen
void _navToNextScreen(BuildContext context) async {
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => const TextScreen()),
);
// update widget after result comes back
setState(() {
_valueText = result;
});
}
return Scaffold(
appBar: AppBar(
title: const Text('Results'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
_navToNextScreen(context);
},
child: const Text('Enter data'),
),
const SizedBox(height: 50),
Text('Message: $_valueText'),
const SizedBox(height: 20),
Text('Checkboxes: '),
],
)),
);
}
}
I think this should be the job of a simple state management strategy; for communication between separate widgets (in this case, two page widgets), that's the cleanest approach. You should create a common service to which both page widgets are subscribed: one to trigger the changes, the other to capture them and display them, using a ChangeNotifier service along with Consumer widgets, as shown below:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => SelectedData(),
child: MyApp()
)
);
}
class SelectedData extends ChangeNotifier {
bool _privacy = false;
bool _termsOfUse = false;
String _valueResult = '';
bool get privacy => _privacy;
bool get termsOfUse => _termsOfUse;
String get valueResult => _valueResult;
set privacy(bool value) {
_privacy = value;
notifyListeners();
}
set termsOfUse(bool value) {
_termsOfUse = value;
notifyListeners();
}
set valueResult(String value) {
_valueResult = value;
notifyListeners();
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: ResultScreen(),
),
),
);
}
}
class TextScreen extends StatefulWidget {
const TextScreen({Key? key}) : super(key: key);
#override
State<TextScreen> createState() => _TextScreenState();
}
class _TextScreenState extends State<TextScreen> {
// text controller for message input
TextEditingController textController = TextEditingController();
#override
void initState() {
super.initState();
}
#override
void dispose() {
textController.dispose();
super.dispose();
}
// go to result screen
void getResult(BuildContext context) {
Navigator.pop(context);
}
#override
Widget build(BuildContext context) {
SelectedData data = Provider.of<SelectedData>(context, listen: false);
textController.text = data.valueResult;
//change state for privacy checkbox
_onPrivacyChange(value) {
data.privacy = value;
}
//change state for terms of use checkbox
_onTermsOfUSeChange(value) {
data.termsOfUse = value;
}
return Scaffold(
appBar: AppBar(
title: const Text('Enter data'),
),
body: Consumer<SelectedData>(
builder: (context, selectedData, child) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextField(
controller: textController,
onChanged: (value) {
data.valueResult = value;
},
decoration: const InputDecoration(labelText: 'Message')),
const SizedBox(height: 20),
CheckboxListTile(
title: const Text('Privacy'),
controlAffinity: ListTileControlAffinity.leading,
value: selectedData.privacy,
onChanged: _onPrivacyChange,
contentPadding: EdgeInsets.zero,
),
CheckboxListTile(
title: const Text('Terms of use'),
controlAffinity: ListTileControlAffinity.leading,
value: selectedData.termsOfUse,
onChanged: _onTermsOfUSeChange,
contentPadding: EdgeInsets.zero,
),
ElevatedButton(
onPressed: () {
getResult(context);
},
child: const Text('Display result'))
],
));
}
),
);
}
}
class ResultScreen extends StatefulWidget {
const ResultScreen({Key? key}) : super(key: key);
#override
State<ResultScreen> createState() => _ResultScreenState();
}
class _ResultScreenState extends State<ResultScreen> {
#override
Widget build(BuildContext context) {
// navigation to next screen
void _navToNextScreen(BuildContext context) async {
await Navigator.push(
context,
MaterialPageRoute(builder: (context) => const TextScreen()),
);
}
return Scaffold(
appBar: AppBar(
title: const Text('Results'),
),
body: Consumer<SelectedData>(
builder: (context, selectedData, child) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
_navToNextScreen(context);
},
child: const Text('Enter data'),
),
const SizedBox(height: 50),
Text('Message: ${selectedData.valueResult}'),
const SizedBox(height: 20),
const Text('Checkboxes: '),
Text('Privacy: ${selectedData.privacy}'),
Text('Terms of Use: ${selectedData.termsOfUse}')
],
));
}
),
);
}
}
Here's the output when you implement it this way:
So from what i see is you are only passing one value that is message and you what many values to pass at a time so here the map can be used and as pop() function takes dynamic returns you can pass any thing.
From your example i have created a sample example that will be a working proof which will demostrate the using map for passing data to previous screen.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: ResultScreen(),
);
}
}
class TextScreen extends StatefulWidget {
const TextScreen({Key? key}) : super(key: key);
#override
_TextScreenState createState() => _TextScreenState();
}
class _TextScreenState extends State<TextScreen> {
// initial values for checkboxes
bool _privacy = false;
bool _termsOfUse = false;
// text controller for message input
TextEditingController textController = TextEditingController();
#override
void dispose() {
textController.dispose();
super.dispose();
}
// go to result screen
void getResult(BuildContext context) {
String valueResult = textController.text;
final data = {
"message":valueResult,
"privacy": _privacy,
'terms':_termsOfUse,
};
Navigator.pop(context, data);
}
#override
Widget build(BuildContext context) {
//change state for privacy checkbox
_onPrivacyChange(value) {
setState(() {
_privacy = value!;
});
}
//change state for terms of use checkbox
_onTermsOfUSeChange(value) {
setState(() {
_termsOfUse = value!;
});
}
return Scaffold(
appBar: AppBar(
title: const Text('Enter data'),
),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextField(
controller: textController,
decoration: const InputDecoration(labelText: 'Message')),
const SizedBox(height: 20),
CheckboxListTile(
title: const Text('Privacy'),
controlAffinity: ListTileControlAffinity.leading,
value: _privacy,
onChanged: _onPrivacyChange,
contentPadding: EdgeInsets.zero,
),
CheckboxListTile(
title: const Text('Terms of use'),
controlAffinity: ListTileControlAffinity.leading,
value: _termsOfUse,
onChanged: _onTermsOfUSeChange,
contentPadding: EdgeInsets.zero,
),
ElevatedButton(
onPressed: () {
getResult(context);
},
child: const Text('Display result'))
],
)),
);
}
}
class ResultScreen extends StatefulWidget {
const ResultScreen({Key? key}) : super(key: key);
#override
State<ResultScreen> createState() => _ResultScreenState();
}
class _ResultScreenState extends State<ResultScreen> {
String? _valueText = '';
bool _privacyValue =false;
bool _termsOfUse = false;
#override
Widget build(BuildContext context) {
// navigation to next screen
void _navToNextScreen(BuildContext context) async {
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => const TextScreen()),
);
if(result !=null)
{
setState(() {
if(result['message']!=null )_valueText = result['message'];
if(result['privacy']!=null) _privacyValue = result['privacy'];
if(result['terms']!=null) _termsOfUse = result['terms'];
});
}
}
return Scaffold(
appBar: AppBar(
title: const Text('Results'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
_navToNextScreen(context);
},
child: const Text('Enter data'),
),
const SizedBox(height: 50),
Text('Message: $_valueText'),
const SizedBox(height: 20),
Text('Privacy Value: $_privacyValue '),
const SizedBox(height: 20),
Text('Terms Value: $_termsOfUse '),
],
)),
);
}
}
You can make changes as per your needs, So let me know if it works.
I am relatively new to Flutter and while I really like it I'm struggling to find a way to have state values in the parent be updated in showModalBottomSheet. I think I understand the issue to be that the values aren't reflecting in showModalBottomSheet when they change in the parent because showModalBottomSheet doesn't get rebuilt when the state updates.
I am storing title and content in the parent because I was also hoping to use it for editing as well as creating todos. I figured the showModalBottomSheet could be shared for both. I am attaching a picture on the simulator. What I am expecting is that when title changes (i.e. is no longer an empty string) then the Add To Do button should become enabled but it currently stays disabled unless I close the modal and re-open it.
Any help or insight would be greatly appreciated. Below is the code in my main.dart file which has showModalBottomSheet and has the state values that need to be passed down. NewToDo contains the text fields in the modal that capture the values and updates the state in main accordingly.
** EDIT **
I have seen this link but it doesn't really explain how to pass state from a parent widget down to a showBottomModalSheet widget, it just shows how to manage state within a showBottomModalSheet widget. My goal is to have the state change from within main to be able to be picked within showBottomModalSheet.
main.dart
import 'package:flutter/material.dart';
import './todoitem.dart';
import './todolist.dart';
import 'classes/todo.dart';
import './newtodo.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'To Do Homie',
theme: ThemeData(
primarySwatch: Colors.deepPurple,
),
home: const MyHomePage(title: "It's To Do's My Guy"),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({
Key? key,
required this.title,
}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String content = '';
String title = '';
int maxId = 0;
ToDo? _todo;
final titleController = TextEditingController();
final contentController = TextEditingController();
List<ToDo> _todos = [];
void _addTodo(){
final todo = ToDo (
title: title,
id: maxId,
isDone: false,
content: content
);
if (_todo != null){
setState(() {
_todos[_todos.indexOf(_todo!)] = todo;
});
} else {
setState(() {
_todos.add(todo);
});
}
setState(() {
content = '';
maxId = maxId++;
title = '';
_todo = null;
});
contentController.text = '';
titleController.text = '';
}
#override
void initState() {
super.initState();
titleController.addListener(_handleTitleChange);
contentController.addListener(_handleContentChange);
futureAlbum = fetchAlbum();
}
void _handleTitleChange() {
setState(() {
title = titleController.text;
});
}
void _handleContentChange() {
setState(() {
content = contentController.text;
});
}
void _editTodo(ToDo todoitem){
setState(() {
_todo = todoitem;
content = todoitem.content;
title = todoitem.title;
});
contentController.text = todoitem.content;
titleController.text = todoitem.title;
}
void _deleteToDo(ToDo todoitem){
setState(() {
_todos = List.from(_todos)..removeAt(_todos.indexOf(todoitem));
});
}
void _clear(){
contentController.text = '';
titleController.text = '';
setState(() {
content = '';
title = '';
_todo = null;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: SingleChildScrollView(
child: Center(
child: Container(
alignment: Alignment.topCenter,
child: ToDoList(_todos, _editTodo, _deleteToDo)
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
print(context);
return Container(child:NewToDo(titleController, contentController, _addTodo, _clear, _todo),);
});
},
child: const Icon(Icons.add),
backgroundColor: Colors.deepPurple,
),
);
}
}
NewToDo.dart
import 'package:flutter/material.dart';
import './classes/todo.dart';
class NewToDo extends StatelessWidget {
final Function _addTodo;
final Function _clear;
final ToDo? _todo;
final TextEditingController titleController;
final TextEditingController contentController;
const NewToDo(this.titleController, this.contentController, this._addTodo, this._clear, this._todo, {Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return
Column(children: [
TextField(
decoration: const InputDecoration(
labelText: 'Title',
),
controller: titleController,
autofocus: true,
),
TextField(
decoration: const InputDecoration(
labelText: 'Details',
),
controller: contentController,
autofocus: true,
),
ButtonBar(
alignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: titleController.text.isNotEmpty ? () => _addTodo() : null,
child: Text(_todo != null ? 'Edit To Do' : 'Add To Do'),
style: ButtonStyle(
backgroundColor: titleController.text.isNotEmpty ? MaterialStateProperty.all<Color>(Colors.deepPurple) : null,
overlayColor: MaterialStateProperty.all<Color>(Colors.purple),
),
),
Visibility (
visible: titleController.text.isNotEmpty || contentController.text.isNotEmpty,
child: ElevatedButton(
onPressed: () => _clear(),
child: const Text('Clear'),
)),
])
],
);
}
}
TextControllers are listenable. You can just wrap your Column in two ValueListenables (one for each controller) and that will tell that widget to update whenever their values are updated.
#override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: contentController,
builder: (context, _content, child) {
return ValueListenableBuilder(
valueListenable: titleController,
builder: (context, _title, child) {
return Column(
children: [
TextField(
decoration: const InputDecoration(
labelText: 'Title',
),
controller: titleController,
autofocus: true,
),
TextField(
decoration: const InputDecoration(
labelText: 'Details',
),
controller: contentController,
autofocus: true,
),
ButtonBar(
alignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed:
titleController.text.isNotEmpty ? () => _addTodo() : null,
child: Text(_todo != null ? 'Edit To Do' : 'Add To Do'),
style: ButtonStyle(
backgroundColor: titleController.text.isNotEmpty
? MaterialStateProperty.all<Color>(Colors.deepPurple)
: null,
overlayColor: MaterialStateProperty.all<Color>(Colors.purple),
),
),
Visibility(
visible: titleController.text.isNotEmpty ||
contentController.text.isNotEmpty,
child: ElevatedButton(
onPressed: () => _clear(),
child: const Text('Clear'),
),
),
],
)
],
);
},
);
},
);
Another more general alternative I can think of is to use Provider (or, if you're familiar enough, regular InheritedWidgets) and the pattern suggested in its readme:
class Example extends StatefulWidget {
const Example({Key key, this.child}) : super(key: key);
final Widget child;
#override
ExampleState createState() => ExampleState();
}
class ExampleState extends State<Example> {
int _count;
void increment() {
setState(() {
_count++;
});
}
#override
Widget build(BuildContext context) {
return Provider.value(
value: _count,
child: Provider.value(
value: this,
child: widget.child,
),
);
}
}
where it suggests reading the count like this:
return Text(context.watch<int>().toString());
Except I'm guessing you can just provide the whole state of the widget to descenents by replacing _count with this to refer to the whole stateful widget. Don't know if this is recommended though.
ValueListenables would be my first choice and then maybe hooks to simplify their use though.
Im trying to learn flutter, but i have stumbled upon a problem i can't solve. I have a class MyApp/MyAppState that has a list of widgets (ovelser), that is used in a listVeiw.builder.
import './barbutton.dart';
import './ovelser.dart';
void main() {
runApp(MaterialApp(home: MyApp()));
}
class MyApp extends StatefulWidget {
// This widget is the root of your application.
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return MyAppState();
}
}
class MyAppState extends State<MyApp> {
List<Widget> ovelser = [];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("progresjon"),
backgroundColor: Colors.blue,
actions: <Widget>[AddButton(nameOvelse)],
),
body: ListView.builder(
itemCount: ovelser.length,
itemBuilder: (context, index) {
final Widget ovelse = ovelser[index]; // lagrer bare ovelse objektet
return Dismissible(
// dismissible gjør det mulig å slette ting i listView
key: UniqueKey(),
onDismissed: (direction) {
//hva som skjer når man skal slette
setState(() {
ovelser.removeAt(index);
});
},
background: Container(
color: Colors.red,
),
//child er hva som skal være objektet som kan slettes
child: ovelse,
);
},
),
);
}
void addOvelse(String name) {
setState(() {
ovelser.add(Ovelser(name));
});
print(ovelser.length);
}
nameOvelse(BuildContext context) {
TextEditingController custumcontroller = TextEditingController();
return showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("new activity"),
content: TextField(
controller: custumcontroller,
),
actions: <Widget>[
FlatButton(
child: Text("create"),
onPressed: () {
String activityName = " " + custumcontroller.text;
addOvelse(activityName);
Navigator.of(context).pop();
},
)
],
);
},
);
}
}
the list ovelser takes in Ovelser objects. these objects have a class that has a list that takes in integers (progresjonsList) that i can add to via an AlertDialog.
Code for the class with progresjonList in int:
import './ovleseraddbutton.dart';
class Ovelser extends StatefulWidget {
final String name;
Ovelser(this.name);
#override
OvelserState createState() => OvelserState();
}
class OvelserState extends State<Ovelser> {
List<int> progresjonList = [];
#override
Widget build(BuildContext context) {
return Container(
height: 80,
width: double.infinity,
alignment: Alignment.centerLeft,
decoration: BoxDecoration(
border: Border(
top: BorderSide(width: 0.5, color: Colors.grey),
bottom: BorderSide(width: 0.5, color: Colors.grey),
)),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Flexible(
child: Container(
child: Text(widget.name,
overflow: TextOverflow.fade,
softWrap: false,
maxLines: 1,
style: TextStyle(
fontStyle: FontStyle.italic,
fontSize: 20,
fontWeight: FontWeight.bold)),
)),
OvelserAddbutton(addvalue)
]),
);
}
void insertValue(int value) {
setState(() {
this.progresjonList.add(value);
});
}
addvalue(BuildContext context) {
TextEditingController custumcontroller = TextEditingController();
return showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("add new value"),
content: TextField(
controller: custumcontroller,
keyboardType: TextInputType.number,
),
actions: <Widget>[
FlatButton(
child: Text("add"),
onPressed: () {
String stringnumber = custumcontroller.text;
int number = int.parse(stringnumber);
insertValue(number);
print(number);
print(progresjonList.length);
print(this.progresjonList);
Navigator.of(context).pop();
},
)
],
);
},
);
}
}
the problem is every time i create a new widget in ovelser (the list that is used in ListView) the lists with integers (progresjonList) clears out so they are empty and dont retain the values previously added by the AlertDialog. I dont understand how i can keep that from happening, so that i keep the integers added. Can anyone help me? thank you in advance:)
there are tow other small files that only have icon widgets in them that i dont think are the problem, but if you need them here they are:)
class AddButton extends StatelessWidget {
final Function setInFunction;
AddButton(this.setInFunction);
#override
Widget build(BuildContext context) {
return IconButton(
icon: Icon(Icons.add),
onPressed: () => setInFunction(context),
);
}
}
import 'package:flutter/material.dart';
class OvelserAddbutton extends StatelessWidget {
final Function setInFunction;
OvelserAddbutton(this.setInFunction);
#override
Widget build(BuildContext context) {
return IconButton(
icon: Icon(Icons.add),
onPressed: () => setInFunction(context),
);
}
}
```
progessjonList is local to Ovelser class. You need to pass overserList to Ovelser class.
class Ovelser extends StatefulWidget {
final String name;
final List<int> list;
Ovelser(this.name, this.list);
#override
OvelserState createState() => OvelserState();
}
Then when you want to add to the list in OvelserState just use
widget.list.add(/*add int here*/);
Which I see is in your insertValue function
void insertValue(int value) {
setState(() {
widget.list.add(value);
});
}
The list you pass in will be a reference to the ovelser list from the original class.
I want to Create an TextField at the bottom of the page like message app.
There is also a IconButton, which adds the entered text into ListView if TextField is not empty. If it is empty then it will show error in SnackBar.
The Problem is the SnackBar stacks on top of TextField. But I want it to be either top or bottom of TextField.
import 'package:flutter/material.dart';
Future<void> main() async {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MainPage(),
);
}
}
class MainPage extends StatefulWidget {
#override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
final _textList = <String>[];
TextEditingController _textController;
bool _addText(context, String text) {
print(text);
if (text?.isNotEmpty == true) {
setState(() {
_textList.add(text);
});
return true;
} else {
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text("Invalid Text Entered"),
behavior: SnackBarBehavior.fixed,
),
);
return false;
}
}
#override
void initState() {
_textController = TextEditingController();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Demo"),
),
body: SafeArea(
child: Column(
children: <Widget>[
Expanded(
child: ListView.separated(
itemCount: _textList.length,
separatorBuilder: (_, __) => Divider(height: 1.0),
itemBuilder: (context, index) => ListTile(
title: Text("${_textList[index]}"),
),
),
),
_buildBottom(),
],
),
),
);
}
Widget _buildBottom() {
return Material(
elevation: 5.0,
color: Colors.blue[100],
child: Row(
children: <Widget>[
Expanded(
child: TextField(
controller: _textController,
decoration: InputDecoration(
hintText: "Enter Text",
contentPadding: EdgeInsets.symmetric(horizontal: 10.0),
border: InputBorder.none,
),
),
),
Builder(
builder: (context) => IconButton(
icon: Icon(Icons.add),
onPressed: () {
final success = _addText(context, _textController.text);
if (success) _textController.clear();
},
),
),
],
),
);
}
}
This is my code in DartPad
try this,
import 'package:flutter/material.dart';
Future<void> main() async {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MainPage(),
);
}
}
class MainPage extends StatefulWidget {
#override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
final _textList = <String>[];
TextEditingController _textController;
bool isVisible = false;
bool _addText(context, String text) {
print(text);
if (text?.isNotEmpty == true) {
setState(() {
_textList.add(text);
});
return true;
} else {
Scaffold.of(context).showSnackBar(
new SnackBar(
content: Text("Invalid Text Entered"),
behavior: SnackBarBehavior.fixed,
duration: Duration(seconds: 3),
onVisible: (() {
setState(() {
isVisible = true;
});
Future.delayed(Duration(seconds: 3)).then((_) => setState(() {
isVisible = false;
}));
}),
),
);
return false;
}
}
#override
void initState() {
_textController = TextEditingController();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Demo"),
),
body: SafeArea(
child: Column(
children: <Widget>[
Expanded(
child: ListView.separated(
itemCount: _textList.length,
separatorBuilder: (_, __) => Divider(height: 1.0),
itemBuilder: (context, index) => ListTile(
title: Text("${_textList[index]}"),
),
),
),
AnimatedContainer(
margin: EdgeInsets.only(bottom: isVisible ? 50 : 0),
child: _buildBottom(),
duration: Duration(milliseconds: 100),
),
],
),
),
);
}
Widget _buildBottom() {
return Material(
elevation: 5.0,
color: Colors.blue[100],
child: Row(
children: <Widget>[
Expanded(
child: TextField(
controller: _textController,
decoration: InputDecoration(
hintText: "Enter Text",
contentPadding: EdgeInsets.symmetric(horizontal: 10.0),
border: InputBorder.none,
),
),
),
Builder(
builder: (context) => IconButton(
icon: Icon(Icons.add),
onPressed: () {
final success = _addText(context, _textController.text);
if (success) _textController.clear();
},
),
),
],
),
);
}
}
Perhaps using Flushbar might help with your problem. There are many properties that you can change, such as flushbarPosition.
It might not solve your problem exactly how you would expect it to but it can make the Flushbar appear from the top instead of the bottom and that's one way around your problem
Flushbar: https://pub.dev/packages/flushbar
I think the best thing you can do here is to change the behaviour to floating
SnackBar(
behavior: SnackBarBehavior.floating,
...
I have a form widget, a list widget, and a "wrapper" widget or in other words, a parent/container widget. So to give an idea of the widget tree, it is as such.
Parent/Container Widget
Form Widget
Button Widget
List Widget
Notice that the form, buttons and list widget are all siblings, inside of the parent/container widget. What I want to happen, is tap on a list item in the list widget, and populate the form widget with the data that gets passed from the list widget.
Here is my parent widget.
import 'package:andplus_flutter_7_gui/model/user.dart';
import 'package:andplus_flutter_7_gui/services/user_service.dart';
import 'package:flutter/material.dart';
import 'package:rxdart/rxdart.dart';
import 'crud_form.dart';
import 'crud_list.dart';
class Crud extends StatefulWidget {
Crud({Key key, this.title}) : super(key: key);
final String title;
_CrudContainerState createState() => _CrudContainerState();
}
class _CrudContainerState extends State<Crud> {
List<User> users;
User user = User();
UserService userService;
#override
void initState() {
super.initState();
if (userService == null) {
userService = UserService(user);
}
}
#override
void dispose() {
// TODO: implement dispose
super.dispose();
userService.dispose();
}
#override
Widget build(BuildContext context) {
return Material(
child: Scaffold(
resizeToAvoidBottomPadding: false,
appBar: AppBar(
title: Text(widget.title),
),
body: Builder(
builder: (context) => Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
flex: 2,
child: StreamBuilder(
builder: (context, AsyncSnapshot<User> snapshot) {
return CrudForm(
user: snapshot.data,
onUserAdded: (user) {
userService.addUser(user);
},
);
},
stream: userService.userObservable,
),
),
Expanded(
child: Text("Future button widget"),
),
Expanded(
flex: 3,
child: StreamBuilder(
builder: (ctx, AsyncSnapshot<List<User>> snap) {
return CrudList(
onUserSelected: userService.userSelected,
users: snap.data,
);
},
stream: userService.usersObservable,
),
),
],
),
),
),
);
}
void onEditUser(User user) {
setState(() {
user = user;
});
}
}
The above widget wraps the three widgets I mentioned.
Here are the children widget:
Form:
import 'package:andplus_flutter_7_gui/model/user.dart';
import 'package:flutter/material.dart';
class CrudForm extends StatefulWidget {
CrudForm({Key key, this.onUserAdded, this.user}) : super(key: key);
final User user;
final void Function(User user) onUserAdded;
_CrudFormState createState() => _CrudFormState(user: user);
}
class _CrudFormState extends State<CrudForm> {
_CrudFormState({this.user});
User user = User();
var _key = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Container(
child: Builder(
builder: (context) => Container(
color: Colors.blueAccent[100],
child: Form(
key: _key,
child: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Row(
children: <Widget>[
Text(
"First Name",
style: TextStyle(fontSize: 20),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
initialValue: widget.user?.firstName == null ||
widget.user.firstName.isEmpty
? user.firstName
: widget.user.firstName,
validator: (value) {
if (value.isEmpty) {
return "First name is required";
}
return null;
},
onSaved: (value) {
setState(() {
user.firstName = value;
});
},
),
),
)
],
),
Row(
children: <Widget>[
Text(
"Last Name",
style: TextStyle(fontSize: 20),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
validator: (value) {
if (value.isEmpty) {
return "Last name is required";
}
return null;
},
onSaved: (value) {
setState(() {
user.lastName = value;
});
},
),
),
),
],
),
RaisedButton(
child: Text(
"Save",
),
splashColor: Colors.blueGrey,
onPressed: () {
if (!_key.currentState.validate()) {
return;
}
_key.currentState.save();
widget.onUserAdded(
new User(
firstName: user.firstName,
lastName: user.lastName,
),
);
},
)
],
),
),
),
),
),
);
}
}
Here is my list widget.
import 'package:andplus_flutter_7_gui/model/user.dart';
import 'package:flutter/material.dart';
class CrudList extends StatefulWidget {
CrudList({Key key, this.users, this.onUserSelected}) : super(key: key);
final List<User> users;
final SelectUser onUserSelected;
_CrudListState createState() => _CrudListState();
}
class _CrudListState extends State<CrudList> {
#override
Widget build(BuildContext context) {
return Container(
color: Colors.green,
child: ListView.builder(
itemCount: widget.users?.length ?? 0,
itemBuilder: (BuildContext context, int index) {
var user = widget.users[index];
return ListTile(
key: Key(index.toString()),
title: Center(
child: Text(
"${user.firstName} ${user.lastName}",
style: TextStyle(color: Colors.white),
),
),
onTap: () {
print("${widget.users[index]} $index");
widget.onUserSelected(widget.users[index]);
},
);
},
),
);
}
}
typedef void SelectUser(User user);
And just for further context, here is my user service, responsible for adding the objects to the stream, and using the stream builder within rxdart to notify of state changes.
import 'package:andplus_flutter_7_gui/model/user.dart';
import 'package:rxdart/rxdart.dart';
class UserService {
User _editedUser = User();
List<User> _users = <User>[];
BehaviorSubject<User> _userSubject;
BehaviorSubject<List<User>> _usersSubject;
UserService(this._editedUser) {
_userSubject = BehaviorSubject<User>.seeded(_editedUser);
_usersSubject = BehaviorSubject<List<User>>.seeded(_users);
}
Observable<List<User>> get usersObservable => _usersSubject.stream;
Observable<User> get userObservable => _userSubject.stream;
addUser(User user) {
_users.add(user);
_usersSubject.add(_users);
}
dispose() {
_userSubject.close();
_usersSubject.close();
}
void userSelected(User user) {
_editedUser = user;
_userSubject.add(_editedUser);
}
}
What am I missing? It looks like my widget rebuilds, and tries to set the initial value in the form when I tap the user in the list widget. But the actual field doesn't get updated and I'm not sure why.
I'd appreciate any documentation or articles on how to better approach data and state management between sibling widgets within the flutter framework.
Here's a similar use case that I tried to implement locally. What I'm doing here is I generate TextFormFields dynamically and assign TextEditingController.
Column textField(int n) {
List<Widget> listForm = [];
while (n > 0) {
var textEditingController = TextEditingController();
listForm.add(
TextFormField(
controller: textEditingController,
onTap: () {
_selectedField = textEditingController;
},
),
);
n--;
}
return Column(children: listForm);
}
Clicking a ListView item updates the text of the currently selected TextFormField.
InkWell(
onTap: () {
debugPrint('Selected $index!');
if (_selectedField != null) {
_selectedField!.value = TextEditingValue(text: 'Item $index');
}
},
child: ListTile(
title: Text('Item $index'),
),
);
Complete sample
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
TextEditingController? _selectedField;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(flex: 1, child: textField(3)),
Expanded(flex: 1, child: listItems()),
],
),
),
),
);
}
Column textField(int n) {
List<Widget> listForm = [];
while (n > 0) {
var textEditingController = TextEditingController();
listForm.add(
TextFormField(
controller: textEditingController,
onTap: () {
debugPrint('Current Controller: $textEditingController');
_selectedField = textEditingController;
},
),
);
n--;
}
return Column(children: listForm);
}
ListView listItems() {
return ListView.builder(
itemCount: 5,
itemBuilder: (BuildContext context, int index) {
return InkWell(
onTap: () {
if (_selectedField != null) {
_selectedField!.value = TextEditingValue(text: 'Item $index');
}
},
child: ListTile(
title: Text('Item $index'),
),
);
},
);
}
}
Demo