Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to attach a listener to a TextEditingController located inside a state ? #80

Closed
arthurgustin opened this issue Sep 21, 2018 · 6 comments

Comments

@arthurgustin
Copy link

Hi @brianegan
I want to attach a listener to TextEditingController that is created in a factory inside a state and I wonder what is the best way to do it ?

@brianegan
Copy link
Owner

In this case, you have two options:

  1. Wrap your Form Widget in a StoreConnector that converts the store into an onTextChanged callback that dispatches the correct action.
  2. Grab the store from the Widget hierarchy using the didChangeDependencies method. Note: This method may be called multiple times so you need to take care!

I generally prefer option #1, but thought I'd quickly write up some code to show the different options so you can decide for yourself which pattern ya like!

class TextChangedAction {
  final String string;

  TextChangedAction(this.string);
}

//
// Option #1: Wrap your Form in a StoreConnector that creates the appropriate
// dispatch function for you
//
class FormPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StoreConnector<int, void Function(String)>(
      converter: (store) =>
          (String text) => store.dispatch(TextChangedAction(text)),
      builder: (context, onTextChanged) => MyForm(onTextChanged: onTextChanged),
    );
  }
}

class MyForm extends StatefulWidget {
  final void Function(String) onTextChanged;

  const MyForm({Key key, this.onTextChanged}) : super(key: key);

  @override
  _MyFormState createState() => _MyFormState();
}

class _MyFormState extends State<MyForm> {
  final _controller = TextEditingController();

  @override
  void initState() {
    super.initState();
    _controller.addListener(_onTextChanged);
  }

  @override
  void dispose() {
    super.dispose();
    _controller.removeListener(_onTextChanged);
  }

  void _onTextChanged() {
    widget.onTextChanged(_controller.text);
  }

  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: _controller,
    );
  }
}

//
// Options 2: Grab the Store from the Widget hierarchy using
// `didChangeDependencies`
//
class MyOtherForm extends StatefulWidget {
  @override
  MyOtherFormState createState() => MyOtherFormState();
}

class MyOtherFormState extends State<MyOtherForm> {
  final _controller = TextEditingController();
  VoidCallback _listener;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();

    if (_listener == null) {
      _listener = () {
        StoreProvider.of<AppState>(context)
            .dispatch(TextChangedAction(_controller.text));
      };
      _controller.addListener(_listener);
    }
  }

  @override
  void dispose() {
    super.dispose();
    _controller.removeListener(_listener);
  }

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

@arthurgustin
Copy link
Author

Mmmh, I don't like any of those solutions... I will try number 1 and see if I'm satisfied.
However, I would love to attach the listener inside my state, I don't want to go deep in the tree to do this :/

@brianegan
Copy link
Owner

Ack, sorry my friend. If ya can find a better solution, please share :)

@kishaningithub
Copy link

kishaningithub commented Nov 7, 2018

@arthurgustin Why not use StoreProvider.of(context) inside your listener ?

Example

  TextEditingController _areaTextInputController =
      TextEditingController(text: "1");

  void changeAreaInState() {
    num area = double.tryParse(_areaTextInputController.text) ?? 0;
    Store<AppState> store = StoreProvider.of(context);
    store.dispatch(AreaChangedAction(area));
  }

  @override
  void initState() {
    super.initState();
    _areaTextInputController.addListener(changeAreaInState);
  }

  @override
  void dispose() {
    _areaTextInputController.removeListener(changeAreaInState);
    _areaTextInputController.dispose();
    super.dispose();
  }

@arthurgustin
Copy link
Author

arthurgustin commented Nov 7, 2018 via email

@hkirk
Copy link

hkirk commented Jun 7, 2019

@brianegan Im trying to use the first solution you made above, to save directly to firebase when the users types data. Can't quite addopt this pattern to my use case. I've setup a onInit and onDispose in the StoreConnector

    onInit: (store) => store.dispatch(RequestMonthlyGoalDataEventsAction()),
    onDispose: (store) => store.dispatch(CancelMonthlyGoalDataEventsAction()),
    ...

These works fine, I correct lave data to firebase and remove the middleware when the widget is removed. So that is fine.

Problem comes when I will show the values from Firebase in the TextFields.

class MyForm extends StatefulWidget {
  final void Function(String) onTextChanged;
  final string value;
  
  const MyForm({Key key, this.onTextChanged, this.value}) : super(key: key);
  
  @override
   _MyFormState createState() => _MyFormState();
}

I can show the values when they are available through the local store, but when I get an update from firebase, through the middleware, I cannot update the TextFields in my StatefullWidget. Any ideas?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants