Related
I have a buttom sheet which appers when a button is click, and it was working fine when everything was on a single dart file, but because i have to use some of the widgets on different screens i decided to split the widgets into different dart files, now i have done that but when the buttons are clicked nothing happens same thing with the other widgets, so i guess it has something to do with how i set it
Bellow is the Bottomsheet code
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import '../widgets/calendarPicker.dart';
class NewTodo extends StatefulWidget {
final Function addTx;
NewTodo({this.addTx});
#override
_NewTodoState createState() => _NewTodoState();
}
class _NewTodoState extends State<NewTodo> {
#override
Widget build(BuildContextcontext) {
showModalBottomSheet(
backgroundColor: Colors.white,
isScrollControlled: true,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(25.0))),
context: context,
builder: (_) {
return GestureDetector(
onTap: () {},
// Where i started the code pasting from
child: Padding(
padding: MediaQuery.of(context).viewInsets,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Card(
elevation: 0.000,
child: Container(
padding: EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextField(
decoration: InputDecoration(labelText: 'Title'),
controller: _titleController,
autofocus: true,
onSubmitted: null,
// onChanged: (val) {
// titleInput = val;
// },
),
TextField(
decoration: InputDecoration(labelText: 'Description'),
controller: _discriptionController,
onSubmitted: null,
// onChanged: (val) => amountInput = val,
),
Container(
height: 70,
child: Row(
children: [
Text(selectedDateAndTime == null
? 'No Date Choosen'
: DateFormat('MM/dd/yyyy HH:mm')
.format(selectedDateAndTime)
// : DateFormat.yMd()
// .format(_selectedDate),
),
FlatButton(
textColor: Theme.of(context).primaryColor,
child: Icon(Icons.calendar_today),
// onPressed: () async {
// var value = await _selectedTime();
// },
onPressed: () {
SelectDayAndTimeL();
},
),
],
),
),
RaisedButton(
child: Text('Save Todo'),
color: Theme.of(context).primaryColor,
textColor: Theme.of(context).textTheme.button.color,
onPressed: _submitData,
),
],
),
),
),
),
),
);
},
);
}
void _submitData() {
// if (_amountController.text.isEmpty) {
// return;
// }
final enteredTitle = _titleController.text;
final enteredDescription = _discriptionController.text;
if (enteredTitle.isEmpty) {
return;
}
widget.addTx(
enteredTitle,
enteredDescription,
selectedDateAndTime,
);
Navigator.of(context).pop();
}
final _titleController = TextEditingController();
final _discriptionController = TextEditingController();
var favorite;
// DateTime _selectedDate;
DateTime selectedDateAndTime;
#override
void dispose() {
super.dispose();
_discriptionController.dispose();
_titleController.dispose();
}
}
Then below is how i tried to call it on the main.dart and i also used same method on the other screen but it's not displaying the widget when the button is clicked
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:schedule_todu/widgets/new_todu.dart';
import './widgets/todu_list.dart';
void main() {
runApp(MaterialApp(
home: ItemList(),
));
}
class ItemList extends StatefulWidget {
final Function addTx;
ItemList({this.addTx});
#override
_ItemListState createState() => _ItemListState();
}
class _ItemListState extends State<ItemList> {
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: Drawer(
child: IconButton(icon: Icon(Icons.add), onPressed: null),
),
appBar: AppBar(
actions: <Widget>[
IconButton(
icon: Icon(Icons.add),
onPressed: () {NewTodo();},
),
],
title: Text('Todu Scheduled Tasks'),
centerTitle: true,
backgroundColor: Colors.redAccent,
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {NewTodo();},
backgroundColor: Colors.redAccent,
),
body: SingleChildScrollView(child: Lists()),
);
}
}
In NewToDo there is no return widget in build check below,
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import '../widgets/calendarPicker.dart';
class NewTodo extends StatefulWidget {
final Function addTx;
NewTodo({this.addTx});
#override
_NewTodoState createState() => _NewTodoState();
}
class _NewTodoState extends State<NewTodo> {
#override
Widget build(BuildContextcontext) {
//remove bottom sheet here
return GestureDetector(
onTap: () {},
// Where i started the code pasting from
child: Padding(
padding: MediaQuery.of(context).viewInsets,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Card(
elevation: 0.000,
child: Container(
padding: EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextField(
decoration: InputDecoration(labelText: 'Title'),
controller: _titleController,
autofocus: true,
onSubmitted: null,
// onChanged: (val) {
// titleInput = val;
// },
),
TextField(
decoration: InputDecoration(labelText: 'Description'),
controller: _discriptionController,
onSubmitted: null,
// onChanged: (val) => amountInput = val,
),
Container(
height: 70,
child: Row(
children: [
Text(selectedDateAndTime == null
? 'No Date Choosen'
: DateFormat('MM/dd/yyyy HH:mm')
.format(selectedDateAndTime)
// : DateFormat.yMd()
// .format(_selectedDate),
),
FlatButton(
textColor: Theme.of(context).primaryColor,
child: Icon(Icons.calendar_today),
// onPressed: () async {
// var value = await _selectedTime();
// },
onPressed: () {
SelectDayAndTimeL();
},
),
],
),
),
RaisedButton(
child: Text('Save Todo'),
color: Theme.of(context).primaryColor,
textColor: Theme.of(context).textTheme.button.color,
onPressed: _submitData,
),
],
),
),
),
),
),
);
}
void _submitData() {
// if (_amountController.text.isEmpty) {
// return;
// }
final enteredTitle = _titleController.text;
final enteredDescription = _discriptionController.text;
if (enteredTitle.isEmpty) {
return;
}
widget.addTx(
enteredTitle,
enteredDescription,
selectedDateAndTime,
);
Navigator.of(context).pop();
}
final _titleController = TextEditingController();
final _discriptionController = TextEditingController();
var favorite;
// DateTime _selectedDate;
DateTime selectedDateAndTime;
#override
void dispose() {
super.dispose();
_discriptionController.dispose();
_titleController.dispose();
}
}
Now in ItemList Screen the both onPress method should return bottomsheet like below,
showModalBottomSheet(
backgroundColor: Colors.white,
isScrollControlled: true,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(25.0))),
context: context,
builder: (_) {
return NewToDo();
});
I'm creating a raw auto complete widget.
The issue is if the widget is at the center or around the bottom of the screen, when I start typing the auto suggestions shown gets hidden under the soft keyboard. How to build the optionsViewBuilder to overcome the hiding of the options under the keyboard?
Sample source code:
class AutoCompleteWidget extends StatefulWidget {
const AutoCompleteWidget(
Key key,
) : super(key: key);
#override
_AutoCompleteWidgetState createState() => _AutoCompleteWidgetState();
}
class _AutoCompleteWidgetState extends State<AutoCompleteWidget> {
late TextEditingController _textEditingController;
String? _errorText;
final FocusNode _focusNode = FocusNode();
final GlobalKey _autocompleteKey = GlobalKey();
List<String> _autoSuggestions = ['abc', 'def', 'hij', 'aub', 'bted' 'donfr', 'xyz'];
#override
void initState() {
super.initState();
_textEditingController = TextEditingController();
}
#override
void dispose() {
_textEditingController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return RawAutocomplete<String>(
key: _autocompleteKey,
focusNode: _focusNode,
textEditingController: _textEditingController,
optionsBuilder: (TextEditingValue textEditingValue) {
if (textEditingValue.text == '') {
return _autoSuggestions;
}
return _autoSuggestions.where((dynamic option) {
return option
.toString()
.toLowerCase()
.startsWith(textEditingValue.text.toLowerCase());
});
},
optionsViewBuilder: (BuildContext context,
AutocompleteOnSelected<String> onSelected, Iterable<String> options) {
return Material(
elevation: 4.0,
child: ListView(
children: options
.map((String option) => GestureDetector(
onTap: () {
onSelected(option);
},
child: ListTile(
title: Text(option),
),
))
.toList(),
),
);
},
fieldViewBuilder: (
BuildContext context,
TextEditingController textEditingController,
FocusNode focusNode,
VoidCallback onSubmitted,
) {
return Card(
elevation: (null == _errorText ? 8 : 0),
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)),
child: TextField(
controller: textEditingController,
focusNode: focusNode,
),
);
},
);
}
}
A solution I came up with was using was building my own version of a simple autocomplete widget using a TextFormField and setting scrollPadding on it. I'm showing the results in a container with a set height that works with that padding.
#override
Widget build(BuildContext context) {
return ListView(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
children: [
// THE AUTOCOMPLETE INPUT FIELD
TextFormField(
focusNode: _focusNode,
scrollPadding: const EdgeInsets.only(bottom: 300),
maxLines: null,
key: const ValueKey('company_address'),
autocorrect: false,
enableSuggestions: false,
controller: widget.textEditingController,
validator: (value) {
if (value!.isEmpty) {
return _i10n.enterAName;
}
return null;
},
decoration: InputDecoration(
labelText: widget.labelText,
),
textInputAction: TextInputAction.next,
onChanged: (_) {
_handleChange();
widget.onChange();
},
onTap: () {
setState(() {
_showAutocompleteSuggestions = true;
});
},
),
const SizedBox(
height: 5.0,
),
// THE AUTOCOMPLETE RESULTS
if (_showAutocompleteSuggestions)
Container(
// height: _autocompleteHeight,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
boxShadow: const [
BoxShadow(blurRadius: 10.0, color: Colors.black12)
],
color: Colors.white,
),
constraints: const BoxConstraints(maxHeight: 200.0),
child: Scrollbar(
child: SingleChildScrollView(
child: Column(children: [
if (_autocompleteSuggestions.isEmpty)
const ListTile(
title: Text('No results'),
)
else
..._autocompleteSuggestions.map((_autocompleteSuggestion) =>
Material(
child: InkWell(
onTap: () {
_handleSelectSuggestion(_autocompleteSuggestion);
},
child: ListTile(
leading: const Icon(Icons.location_on_outlined),
title: Text(_autocompleteSuggestion.description),
),
),
))
]),
),
),
),
],
);
}
Forgive the quick code dump. 😁
You should use SingleChildScrollView on your screen where RawAutocomplete places with reverse: true property.
Just like beneath:
child: Center(
child: SingleChildScrollView(
reverse: true,
child: Column()
You could use some constraints to achieve the behavior you want.
First of all, place the root widget as a child of LayoutBuilder to get the layout constraints (I also used a Align top place the options view better).
After that, you can use a ConstrainedBox as the parent of your options view.
You can customize these constraints as you want. The example below is set to have half screen height as the max height of the options view minus the bottom view inset (dynamic as the state of the soft keyboard).
The code you gave on your example would be something like this:
#override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.topLeft,
child: LayoutBuilder(
builder: (context, constraints) => Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
),
child: RawAutocomplete<String>(
key: _autocompleteKey,
focusNode: _focusNode,
textEditingController: _textEditingController,
optionsBuilder: (TextEditingValue textEditingValue) {
if (textEditingValue.text == '') {
return _autoSuggestions;
}
return _autoSuggestions.where((dynamic option) {
return option
.toString()
.toLowerCase()
.startsWith(textEditingValue.text.toLowerCase());
});
},
optionsViewBuilder: (BuildContext context,
AutocompleteOnSelected<String> onSelected,
Iterable<String> options) {
return Container(
margin: EdgeInsets.symmetric(horizontal: 16),
child: Material(
elevation: 4.0,
child: ConstrainedBox(
constraints: BoxConstraints(
maxWidth: constraints.biggest.width,
maxHeight: (MediaQuery.of(context).size.height / 2) -
(MediaQuery.of(context).viewInsets.bottom / 4),
),
child: ListView(
children: options
.map((String option) => GestureDetector(
onTap: () {
onSelected(option);
},
child: ListTile(
title: Text(option),
),
))
.toList(),
),
),
),
);
},
fieldViewBuilder: (
BuildContext context,
TextEditingController textEditingController,
FocusNode focusNode,
VoidCallback onSubmitted,
) {
return Card(
elevation: (null == _errorText ? 8 : 0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0)),
child: TextField(
controller: textEditingController,
focusNode: focusNode,
),
);
},
),
),
),
);
}
I have simple form , inside it have CircularAvatar when this is pressed show ModalBottomSheet to choose between take picture from gallery or camera. To make my widget more compact , i separated it to some file.
FormDosenScreen (It's main screen)
DosenImagePicker (It's only CircularAvatar)
ModalBottomSheetPickImage (It's to show ModalBottomSheet)
The problem is , i don't know how to passing value from ModalBottomSheetPickImage to FormDosenScreen. Because value from ModalBottomSheetPickImage i will use to insert operation.
I only success passing from third Widget to second Widget , but when i passing again from second Widget to first widget the value is null, and i think the problem is passing from Second widget to first widget.
How can i passing from third Widget to first Widget ?
First Widget
class FormDosenScreen extends StatefulWidget {
static const routeNamed = '/formdosen-screen';
#override
_FormDosenScreenState createState() => _FormDosenScreenState();
}
class _FormDosenScreenState extends State<FormDosenScreen> {
String selectedFile;
#override
Widget build(BuildContext context) {
final detectKeyboardOpen = MediaQuery.of(context).viewInsets.bottom;
print('trigger');
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('Tambah Dosen'),
actions: <Widget>[
PopupMenuButton(
itemBuilder: (_) => [
PopupMenuItem(
child: Text('Tambah Pelajaran'),
value: 'add_pelajaran',
),
],
onSelected: (String value) {
switch (value) {
case 'add_pelajaran':
Navigator.of(context).pushNamed(FormPelajaranScreen.routeNamed);
break;
default:
}
},
)
],
),
body: Stack(
fit: StackFit.expand,
children: <Widget>[
SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
SizedBox(height: 20),
DosenImagePicker(onPickedImage: (file) => selectedFile = file),
SizedBox(height: 20),
Card(
margin: const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
TextFormFieldCustom(
onSaved: (value) {},
labelText: 'Nama Dosen',
),
SizedBox(height: 20),
TextFormFieldCustom(
onSaved: (value) {},
prefixIcon: Icon(Icons.email),
labelText: 'Email Dosen',
keyboardType: TextInputType.emailAddress,
),
SizedBox(height: 20),
TextFormFieldCustom(
onSaved: (value) {},
keyboardType: TextInputType.number,
inputFormatter: [
// InputNumberFormat(),
WhitelistingTextInputFormatter.digitsOnly
],
prefixIcon: Icon(Icons.local_phone),
labelText: 'Telepon Dosen',
),
],
),
),
),
SizedBox(height: kToolbarHeight),
],
),
),
Positioned(
child: Visibility(
visible: detectKeyboardOpen > 0 ? false : true,
child: RaisedButton(
onPressed: () {
print(selectedFile);
},
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
color: colorPallete.primaryColor,
child: Text(
'SIMPAN',
style: TextStyle(fontWeight: FontWeight.bold, fontFamily: AppConfig.headerFont),
),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
textTheme: ButtonTextTheme.primary,
),
),
bottom: kToolbarHeight / 2,
left: sizes.width(context) / 15,
right: sizes.width(context) / 15,
)
],
),
);
}
}
Second Widget
class DosenImagePicker extends StatefulWidget {
final Function(String file) onPickedImage;
DosenImagePicker({#required this.onPickedImage});
#override
DosenImagePickerState createState() => DosenImagePickerState();
}
class DosenImagePickerState extends State<DosenImagePicker> {
String selectedImage;
#override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.center,
child: InkWell(
onTap: () async {
await showModalBottomSheet(
context: context,
builder: (context) => ModalBottomSheetPickImage(
onPickedImage: (file) {
setState(() {
selectedImage = file;
widget.onPickedImage(selectedImage);
print('Hellooo dosen image picker $selectedImage');
});
},
),
);
},
child: CircleAvatar(
foregroundColor: colorPallete.black,
backgroundImage: selectedImage == null ? null : MemoryImage(base64.decode(selectedImage)),
radius: sizes.width(context) / 6,
backgroundColor: colorPallete.accentColor,
child: selectedImage == null ? Text('Pilih Gambar') : SizedBox(),
),
),
);
}
}
Third Widget
class ModalBottomSheetPickImage extends StatelessWidget {
final Function(String file) onPickedImage;
ModalBottomSheetPickImage({#required this.onPickedImage});
#override
Widget build(BuildContext context) {
return SizedBox(
child: Padding(
padding: const EdgeInsets.all(15.0),
child: Wrap(
alignment: WrapAlignment.spaceEvenly,
children: <Widget>[
InkWell(
onTap: () async {
final String resultBase64 =
await commonFunction.pickImage(quality: 80, returnFile: ReturnFile.BASE64);
onPickedImage(resultBase64);
},
child: CircleAvatar(
foregroundColor: colorPallete.white,
backgroundColor: colorPallete.green,
child: Icon(Icons.camera_alt),
),
),
InkWell(
onTap: () async {
final String resultBase64 =
await commonFunction.pickImage(returnFile: ReturnFile.BASE64, isCamera: false);
onPickedImage(resultBase64);
},
child: CircleAvatar(
foregroundColor: colorPallete.white,
backgroundColor: colorPallete.blue,
child: Icon(Icons.photo_library),
),
),
],
),
),
);
}
}
The cleanest and easiest way to do this is through Provider. It is one of the state management solutions you can use to pass values around the app as well as rebuild only the widgets that changed. (Ex: When the value of the Text widget changes). Here is how you can use Provider in your scenario:
This is how your model should look like:
class ImageModel extends ChangeNotifier {
String _base64Image;
get base64Image => _base64Image;
set base64Image(String base64Image) {
_base64Image = base64Image;
notifyListeners();
}
}
Don't forget to add getters and setters so that you can use notifyListeners() if you have any ui that depends on it.
Here is how you can access the values of ImageModel in your UI:
final model=Provider.of<ImageModel>(context,listen:false);
String image=model.base64Image; //get data
model.base64Image=resultBase64; //set your image data after you used ImagePicker
Here is how you can display your data in a Text Widget (Ideally, you should use Selector instead of Consumer so that the widget only rebuilds if the value its listening to changes):
#override
Widget build(BuildContext context) {
//other widgets
Selector<ImageModel, String>(
selector: (_, model) => model.base64Image,
builder: (_, image, __) {
return Text(image);
},
);
}
)
}
You could achieve this easily. If you are using Blocs.
Here is my requirement, when I click the Add button, dynamically new cards with three TextFields should be generated, and how to assign each TextField with dynamically created TextEditingControllers> or is there any other way to take value from TextFields?
final name1 = new TextField(
controller: name1Controller,
decoration: InputDecoration(
labelText: 'Full Name', border: OutlineInputBorder()));
final age1 = new TextField(
controller: age1Controler,
keyboardType: TextInputType.number,
decoration:
InputDecoration(labelText: 'Age', border: OutlineInputBorder()));
final studyjob1 = new TextField(
controller: study1Controller,
decoration: InputDecoration(
labelText: 'Study / Job', border: OutlineInputBorder()));
final person1Card = new Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0),
),
elevation: 10,
child: Padding(
padding: EdgeInsets.only(top: 2.0, left: 6.0, right: 6.0, bottom: 2.0),
child: Column(
children: <Widget>[
Text('Person 1'),
SizedBox(height: 3.0),
name1,
SizedBox(height: 10.0),
age1,
SizedBox(height: 10.0),
studyjob1,
SizedBox(height: 10.0),
],
),
),
);
return Scaffold(
appBar: AppBar(
title: Text('New Entry'),
),
body: SingleChildScrollView(
child: Container(
child: Container(
color: Colors.white,
child: Padding(
padding: const EdgeInsets.all(15.0),
child: Column(
children: <Widget>[
person1Card,
SizedBox(
height: 10.0,
),
saveButton
],
),
),
),
),
))
import 'package:flutter/material.dart';
void main() {
runApp(App());
}
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Home(),
);
}
}
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
child: Text('Add entries'),
onPressed: () async {
List<PersonEntry> persons = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SOF(),
),
);
if (persons != null) persons.forEach(print);
},
),
),
);
}
}
class SOF extends StatefulWidget {
#override
_SOFState createState() => _SOFState();
}
class _SOFState extends State<SOF> {
var nameTECs = <TextEditingController>[];
var ageTECs = <TextEditingController>[];
var jobTECs = <TextEditingController>[];
var cards = <Card>[];
Card createCard() {
var nameController = TextEditingController();
var ageController = TextEditingController();
var jobController = TextEditingController();
nameTECs.add(nameController);
ageTECs.add(ageController);
jobTECs.add(jobController);
return Card(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text('Person ${cards.length + 1}'),
TextField(
controller: nameController,
decoration: InputDecoration(labelText: 'Full Name')),
TextField(
controller: ageController,
decoration: InputDecoration(labelText: 'Age')),
TextField(
controller: jobController,
decoration: InputDecoration(labelText: 'Study/ job')),
],
),
);
}
#override
void initState() {
super.initState();
cards.add(createCard());
}
_onDone() {
List<PersonEntry> entries = [];
for (int i = 0; i < cards.length; i++) {
var name = nameTECs[i].text;
var age = ageTECs[i].text;
var job = jobTECs[i].text;
entries.add(PersonEntry(name, age, job));
}
Navigator.pop(context, entries);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(
children: <Widget>[
Expanded(
child: ListView.builder(
itemCount: cards.length,
itemBuilder: (BuildContext context, int index) {
return cards[index];
},
),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: RaisedButton(
child: Text('add new'),
onPressed: () => setState(() => cards.add(createCard())),
),
)
],
),
floatingActionButton:
FloatingActionButton(child: Icon(Icons.done), onPressed: _onDone),
);
}
}
class PersonEntry {
final String name;
final String age;
final String studyJob;
PersonEntry(this.name, this.age, this.studyJob);
#override
String toString() {
return 'Person: name= $name, age= $age, study job= $studyJob';
}
}
You can use a List for your controllers.
For example:
class PersonControllers {
final TextEditingController name;
final TextEditingController age;
final TextEditingController job;
PersonControllers(this.name, this.age, this.job);
}
Then in your widget
final List<PersonControllers> personControllers = List<PersonControllers>();
In your initState
personControllers.add(PersonController(TextEditingController(),TextEditingController(),TextEditingController());
Create a buildCard method:
Widget buildCard(PersonControllers controllers){
return Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0),
),
elevation: 10,
child: Padding(
padding: EdgeInsets.only(top: 2.0, left: 6.0, right: 6.0, bottom: 2.0),
child: Column(
children: <Widget>[
Text('Person 1'),
SizedBox(height: 3.0),
_buildNameField(controllers.name),
SizedBox(height: 10.0),
_buildAgeField(controllers.age),
SizedBox(height: 10.0),
_buildJobField(controllers.job),
SizedBox(height: 10.0),
],
),
),
);
}
Finally in your build method:
return Scaffold(
appBar: AppBar(
title: Text('New Entry'),
),
body: SingleChildScrollView(
child: Container(
child: Container(
color: Colors.white,
child: Padding(
padding: const EdgeInsets.all(15.0),
child: Column(
children: <Widget>[
...personControllers.map((personController) => _buildCard(personController),
SizedBox(
height: 10.0,
),
RaisedButton(
child: Text("Add"),
onPressed: (){
setState((){
personControllers.add(PersonController(
TextEditingController(),
TextEditingController(),
TextEditingController()
});
);
}
),
],
),
),
),
),
))
if you want remove the form also please look at this code may be it is help full for someone
class Test extends StatefulWidget {
const Test({Key? key}) : super(key: key);
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
var nameTECs = <int, TextEditingController>{};
var mailTECs = <int, TextEditingController>{};
List<Entry> entries = [];
var item = <int, Widget>{};
GlobalKey<FormState> _formKey = GlobalKey<FormState>();
#override
void didChangeDependencies() {
super.didChangeDependencies();
item.addAll({0: newMethod(context, 0)});
}
ondDone() {
entries.clear();
print(nameTECs.keys.last);
for (int i = 0; i <= nameTECs.keys.last; i++) {
var name = nameTECs[i]?.value.text;
var mail = mailTECs[i]?.value.text;
// print(mailTECs[i]?.value.text);
if (name != null && mail != null) {
entries.add(Entry(name, mail));
}
}
print(entries);
for (int a = 0; a < entries.length; a++) {
print(entries[a].name);
print(entries[a].email);
}
}
newMethod(
BuildContext context,
int index,
) {
var nameController = TextEditingController();
var mailController = TextEditingController();
nameTECs.addAll({index: nameController});
mailTECs.addAll({index: mailController});
return Column(
children: [
Text(index.toString()),
TextFormField(
controller: nameController,
validator: (value) {
return value!.isEmpty ? 'Enter some text' : null;
},
textFieldType: TextFieldType.NAME,
),
TextFormField(
controller: mailController,
validator: (value) {},
// controller: nameCount,
textFieldType: TextFieldType.NAME,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
FlatButton(
onTap: () {
item.addAll({item.keys.last+1: newMethod(context, item.keys.last + 1)});
setState(() {});
// }
},
child: Text('Add'),
),
FlatButton(
onTap: () {
setState(() {
item.removeWhere((key, value) => key == index);
nameTECs.removeWhere((key, value) => key == index);
mailTECs.removeWhere((key, value) => key == index);
});
},
child: Text('Remove'),
),
],
),
],
);
}
#override
Widget build(BuildContext context) {
print('build');
return Scaffold(
appBar: AppBar(
title: Text('Test'),
),
body: SingleChildScrollView(
child: Form(
key: _formKey,
child: Column(
children: [
ListView.builder(
shrinkWrap: true,
physics: ScrollPhysics(),
itemCount: item.length,
itemBuilder: (context, index) {
return item.values.elementAt(index);
}),
// for (int i = 0; i < widgeta.length; i++) widgeta[i],
AppButton(
onTap: () {
if (_formKey.currentState!.validate()) {
ondDone();
// _formKey.currentState!.save();
setState(() {});
}
},
color: appPrimaryColor,
child: Text('save'),
),
Center(
child: Text('Test', textDirection: TextDirection.rtl),
),
],
),
),
),
);
}
}
class Entry {
final String? name;
final String? email;
Entry(
this.name,
this.email,
);
}
class _View4 extends StatefulWidget {
#override
_View4State createState() => _View4State();
}
class _GroupControllers {
TextEditingController name = TextEditingController();
TextEditingController tel = TextEditingController();
TextEditingController address = TextEditingController();
void dispose() {
name.dispose();
tel.dispose();
address.dispose();
}
}
class _View4State extends State<_View4> {
List<_GroupControllers> _groupControllers = [];
List<TextField> _nameFields = [];
List<TextField> _telFields = [];
List<TextField> _addressFields = [];
#override
void dispose() {
for (final controller in _groupControllers) {
controller.dispose();
}
_okController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
title: Text("Dynamic Group Text Field2"),
),
body: Column(
children: [
_addTile(),
Expanded(child: _listView()),
_okButton(context),
],
)),
);
}
Widget _addTile() {
return ListTile(
title: Icon(Icons.add),
onTap: () {
final group = _GroupControllers();
final nameField = _generateTextField(group.name, "name");
final telField = _generateTextField(group.tel, "mobile");
final addressField = _generateTextField(group.address, "address");
setState(() {
_groupControllers.add(group);
_nameFields.add(nameField);
_telFields.add(telField);
_addressFields.add(addressField);
});
},
);
}
TextField _generateTextField(TextEditingController controller, String hint) {
return TextField(
controller: controller,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: hint,
),
);
}
Widget _listView() {
final children = [
for (var i = 0; i < _groupControllers.length; i++)
Container(
margin: EdgeInsets.all(5),
child: InputDecorator(
child: Column(
children: [
_nameFields[i],
_telFields[i],
_addressFields[i],
],
),
decoration: InputDecoration(
labelText: i.toString(),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
),
),
),
)
];
return SingleChildScrollView(
child: Column(
children: children,
),
);
}
final _okController = TextEditingController();
Widget _okButton(BuildContext context) {
final textField = TextField(
controller: _okController,
keyboardType: TextInputType.number,
decoration: InputDecoration(
border: OutlineInputBorder(),
),
);
final button = ElevatedButton(
onPressed: () async {
final index = int.parse(_okController.text);
String text = "name: ${_groupControllers[index].name.text}\n" +
"tel: ${_groupControllers[index].tel.text}\n" +
"address: ${_groupControllers[index].address.text}\n";
await showMessage(context, text, "Result");
},
child: Text("OK"),
);
return Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Container(
child: textField,
width: 100,
height: 50,
),
button,
],
);
}
}
I've this simple login screen with two TextFormField for email and password. When I try to enter text in any of these text boxes, keyboard appears momentarily and disappears every time I focus on text field to enter data.
class LogInPage extends StatefulWidget {
final String title;
LogInPage({Key key, this.title}) : super(key: key);
#override
_LogInPageState createState() => new _LogInPageState();
}
class _LogInPageState extends State<LogInPage> {
static final formKey = new GlobalKey<FormState>();
String _email;
String _password;
Widget padded({Widget child}) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 8.0),
child: child,
);
}
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Container(
padding: const EdgeInsets.all(16.0),
child: Column(children: [
Card(
child:
Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
Container(
padding: const EdgeInsets.all(16.0),
child: Form(
key: formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
padded(
child: TextFormField(
key: Key('email'),
decoration: InputDecoration(labelText: 'Email'),
autocorrect: false,
validator: (val) => val.isEmpty
? 'Email can\'t be empty.'
: null,
onSaved: (val) => _email = val,
)),
padded(
child: TextFormField(
key: Key('password'),
decoration:
InputDecoration(labelText: 'Password'),
obscureText: true,
autocorrect: false,
validator: (val) => val.isEmpty
? 'Password can\'t be empty.'
: null,
onSaved: (val) => _password = val,
)),
]))),
])),
])));
}
}
This is the form:
EDIT
I think the problem lies in the way I'm calling this page like below. Is it okay to call another page from FutureBuilder ?
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("LogIn Demo"),
),
body: FutureBuilder<FirebaseUser>(
future: Provider.of<FireAuthService>(context).currentUser(),
builder: (context, AsyncSnapshot<FirebaseUser> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.error != null) {
return Text(snapshot.error.toString());
}
return snapshot.hasData
? UserProfilePage(snapshot.data)
: LogInPage(title: 'Login');
} else {
return Container(
child: CircularProgressIndicator(),
);
}
},
),
);
}
Clean your code first and rebuild, perform testing with real device as well.
static GlobalKey<FormState> _formKey = new GlobalKey<FormState>();