I want to have a simple TextField and a TextButton inside a BottomSheet. The TextButton should only be enabled if there is some text in the TextField. However it only enables/disables the button when I click the screen or enter. I want it to change in real time.
Here is my full code:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyBottomSheet(),
);
}
}
class MyBottomSheet extends StatefulWidget {
@override
_MyBottomSheetState createState() => _MyBottomSheetState();
}
class _MyBottomSheetState extends State<MyBottomSheet> {
late TextEditingController _textController;
bool _isButtonDisabled = true;
@override
void initState() {
super.initState();
_textController = TextEditingController();
_textController.addListener(_onTextChanged);
}
void _onTextChanged() {
setState(() {
_isButtonDisabled = _textController.text.isEmpty;
});
}
@override
void dispose() {
_textController.dispose();
super.dispose();
}
void _showBottomSheet() {
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom
),
child: Container(
height: 300,
width: double.infinity,
child: Column(
children: [
TextField(
controller: _textController,
autofocus: true,
decoration: const InputDecoration(
contentPadding: EdgeInsets.symmetric(horizontal: 15),
hintText: 'Enter name...',
),
),
TextButton(
onPressed: _isButtonDisabled ? null : () {
},
child: const Text('Save'),
)
],
),
),
);
}
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('TEST'),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked ,
floatingActionButton: FloatingActionButton(
shape: CircleBorder(),
onPressed: (_showBottomSheet),
child: Icon(Icons.add_rounded),
),
bottomNavigationBar: BottomAppBar(
height: 60,
shape: CircularNotchedRectangle(),
notchMargin: 8.0,
clipBehavior: Clip.antiAlias,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
onPressed: () {},
icon: Icon(Icons.more_horiz_rounded),
),
IconButton(
onPressed: () {},
icon: Icon(Icons.menu_rounded)
)
],
),
),
);
}
void _onSubmit() {
// Handle form submission here
}
}
I tried the same approach outside the BottomSheet and it works fine.
You must move Modal content into a separated StatefulWidget like bellow
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyBottomSheet(),
);
}
}
class MyBottomSheet extends StatefulWidget {
@override
State<MyBottomSheet> createState() => _MyBottomSheetState();
}
class _MyBottomSheetState extends State<MyBottomSheet> {
late TextEditingController _textController;
@override
void initState() {
super.initState();
_textController = TextEditingController();
}
@override
void dispose() {
_textController.dispose();
super.dispose();
}
void _showBottomSheet() {
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return ModalContent(controller: _textController);
}
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('TEST'),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked ,
floatingActionButton: FloatingActionButton(
shape: const CircleBorder(),
onPressed: (_showBottomSheet),
child: const Icon(Icons.add_rounded),
),
bottomNavigationBar: BottomAppBar(
height: 60,
shape: const CircularNotchedRectangle(),
notchMargin: 8.0,
clipBehavior: Clip.antiAlias,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
onPressed: () {},
icon: const Icon(Icons.more_horiz_rounded),
),
IconButton(
onPressed: () {},
icon: const Icon(Icons.menu_rounded)
)
],
),
),
);
}
void _onSubmit() {
// Handle form submission here
}
}
class ModalContent extends StatefulWidget {
final TextEditingController controller;
const ModalContent({super.key, required this.controller});
@override
State<ModalContent> createState() => _ModalContentState();
}
class _ModalContentState extends State<ModalContent> {
bool _isButtonDisabled = true;
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom
),
child: Container(
height: 300,
width: double.infinity,
child: Column(
children: [
TextField(
controller: widget.controller,
autofocus: true,
decoration: const InputDecoration(
contentPadding: EdgeInsets.symmetric(horizontal: 15),
hintText: 'Enter name...',
),
onChanged: (value) {
print("Value is empty : ${value.isEmpty}");
setState(() {
_isButtonDisabled = value.isEmpty;
});
}
),
TextButton(
onPressed: _isButtonDisabled ? null : () {
},
child: const Text('Save'),
)
],
),
),
);
}
}