-
-
Couldn't load subscription status.
- Fork 560
WIP: Implement Streams for Forms #1162
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
WIP: Implement Streams for Forms #1162
Conversation
Codecov Report
@@ Coverage Diff @@ ## main #1162 +/- ## ========================================== + Coverage 84.93% 84.96% +0.02% ========================================== Files 19 19 Lines 697 705 +8 ========================================== + Hits 592 599 +7 - Misses 105 106 +1
📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more |
I stylized the example app. You can see the changes in realtime in a table.
Peek.2022年12月06日.01-10.mp4
@erayerdin take a look in this article. Help me a lot when I've implemented this tests
Tests are done. Added a section to readme. There are still things to do, though. Currently, StreamBuilder strangely needs setState to refresh its content in the example app. I'll try to write a StatelessWidget and investigate it.
I will also try how Submit and Reset buttons behave in the example app.
So, I've changed example/main.dart on my local machine. This is what I've done, and it's the ideal usecase in my mind.
The Minimum Ideal Usecase
class CompleteForm extends StatelessWidget { final _formKey = GlobalKey<FormBuilderState>(); CompleteForm({super.key}); @override Widget build(BuildContext context) { return FormBuilder( key: _formKey, child: Scaffold( body: Padding( padding: const EdgeInsets.all(8), child: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ FormBuilderTextField(name: 'text1'), StreamBuilder( stream: _formKey.currentState!.onChanged, builder: (context, AsyncSnapshot<FormBuilderFields> snapshot) { return Text( snapshot.data?.values.first.value.toString() ?? 'null'); }, ), ], ), ), ), ), ); } }
Why do I consider this as ideal?
- It's a stateless widget. Flutter won't unnecessarily hold states for rerendering like stateful widget.
- Stateless widgets are much more readable and comprehensible than stateless ones.
This doesn't work though. The reason is currentState in _formKey.currentState!.onChanged is null when the app is first loaded. Thus the whole app crashes saying null-check operator used in a null value.
This can be avoided by hot reloading or hot refreshing the app, but a real world user won't be able to do that.
What Works
class CompleteForm extends StatefulWidget { const CompleteForm({super.key}); @override State<CompleteForm> createState() => _CompleteFormState(); } class _CompleteFormState extends State<CompleteForm> { final _formKey = GlobalKey<FormBuilderState>(); @override Widget build(BuildContext context) { return FormBuilder( key: _formKey, onChanged: () { setState(() {}); }, child: Scaffold( body: Padding( padding: const EdgeInsets.all(8), child: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ FormBuilderTextField(name: 'text1'), StreamBuilder( stream: _formKey.currentState?.onChanged, builder: (context, AsyncSnapshot<FormBuilderFields> snapshot) { return Text( snapshot.data?.values.first.value.toString() ?? 'null'); }, ), ], ), ), ), ), ); } }
This one works because we do setState on form's each onChanged.
Why do I not find this ideal usecase?
- Again, readability.
onChangedon form constantly doessetStateon each keypress, which is frequent enough that it is not desirable. It might cause some performance and consistency issues in some edge cases (like performance overhead or keyboard buttons not responding in some cases as the user is fast-typing).
So, from this point on, I need to figure out a way to make sure that currentState is not null and keep the devs on StatelessWidget. I'm not quite sure as to how, but I will figure it out somehow I guess.
These days, I'm busy with work, so I need to have a hold on this. Will come back to it tho.
It's been almost a month. I've asked this question on SO. Let's see if someone can figure it out.
This comment from 1157 does not do anything good in our case.
dadagov125
commented
Feb 27, 2025
Hi! I think onChanged is not a good name for creating a stream subscription. Developers are used to seeing onChanged as ValueChanged from the foundation library.
I think it could be simplified by simply calling it .stream or onChangedStream as an option.
Uh oh!
There was an error while loading. Please reload this page.
Connection with issue(s)
Close #1155
Solution description
FormBuilderStatenow has aStreamSubscriptionnamedonChangedso that people can subscribe to the stream of changes and react to the changes.This PR is a WIP (work-in-progress) and should not be merged yet.
_formKey.currentState!.onChanged.listen((FormBuilderFields fields) {})to listen to the form changes._formKey.currentState!.onChanged.cancel()to dispose of the stream.Screenshots or Videos
Peek.2022年12月05日.15-29.mp4
This is what I added in example project, which is not very elegant to look at. I will refactor this later on. This is only for demo purposes.
As you can see,
StreamBuilderreacts to the changes live and re-renders each time the form is updated.To Do