0

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.

1 Answers1

0

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'),
            )
          ],
        ),
      ),
    );
  }
}
logancodemaker
  • 582
  • 3
  • 14
  • Thank you, it worked. However I still don't understand how this change is sufficient for the fix – Tiago Fernandes Apr 10 '23 at 13:22
  • I don't know what it is exactly, but I think it's due to the fact that the widget that is in `showModalBottomSheet.builder` is isolated from the context of the widget that called it – logancodemaker Apr 10 '23 at 20:02