Created with <3 with dartpad.dev.
Created
September 29, 2023 09:56
-
-
Save WojuadeAA/ed5517eaf286d3508a3ce2d6b5ab3f65 to your computer and use it in GitHub Desktop.
ReactiveTextField (Riverpod)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import 'package:flutter/material.dart'; | |
| import 'package:flutter_riverpod/flutter_riverpod.dart'; | |
| // This example demonstrates a more reactive approach to creating a TextField. | |
| // this approach saves you from having to mess with controllers and StatefulWidgets. | |
| // Instead you can use your own state management solution. | |
| // Stephan(https://twitter.com/SEGVeenstra) showcased this using Flutter Bloc(https://dartpad.dev/?id=614b3f4ad618e39ee8cb3f20ab9c0801), | |
| //and I'll show how to achieve the same with Riverpod. | |
| class CounterState { | |
| const CounterState({ | |
| this.counter = 0, | |
| this.error, | |
| }); | |
| final int counter; | |
| final String? error; | |
| } | |
| class CounterNotifier extends Notifier<CounterState> { | |
| @override | |
| build() { | |
| return const CounterState(); | |
| } | |
| void increment() { | |
| state = CounterState(counter: state.counter + 1); | |
| } | |
| void setValue(String newValue) { | |
| final parsedValue = int.tryParse(newValue); | |
| if (parsedValue == null) { | |
| state = const CounterState( | |
| error: 'Invalid value', | |
| ); | |
| } else { | |
| state = CounterState( | |
| counter: parsedValue | |
| ); | |
| } | |
| } | |
| void clear() { | |
| state = const CounterState(); | |
| } | |
| } | |
| final counterNotifierProvider = | |
| NotifierProvider<CounterNotifier, CounterState>(() => CounterNotifier()); | |
| void main() { | |
| runApp(const ProviderScope( | |
| child: MyApp(), | |
| )); | |
| } | |
| class MyApp extends StatelessWidget { | |
| const MyApp({super.key}); | |
| @override | |
| Widget build(BuildContext context) { | |
| return MaterialApp( | |
| title: 'Better TextField', | |
| theme: ThemeData( | |
| colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), | |
| useMaterial3: true, | |
| ), | |
| home: const MyHomePage(title: 'ReactiveTextField Demo')); | |
| } | |
| } | |
| class MyHomePage extends StatelessWidget { | |
| const MyHomePage({super.key, required this.title}); | |
| final String title; | |
| @override | |
| Widget build(BuildContext context) { | |
| return Scaffold( | |
| appBar: AppBar( | |
| backgroundColor: Theme.of(context).colorScheme.inversePrimary, | |
| title: Text(title), | |
| ), | |
| body: Consumer( | |
| builder: (context, ref, _) { | |
| final state = ref.watch(counterNotifierProvider); | |
| return Center( | |
| child: Column( | |
| mainAxisAlignment: MainAxisAlignment.center, | |
| children: <Widget>[ | |
| const Text( | |
| 'You have pushed the button this many times:', | |
| ), | |
| Text( | |
| '${state.counter}', | |
| style: Theme.of(context).textTheme.headlineMedium, | |
| ), | |
| SizedBox( | |
| width: 200, | |
| // We use a [ReactiveTextField] (defined at the bottom of the page). | |
| // As you can see, we don't need a controller, and the HomePage | |
| // does not even have to be a StatefulWidget. | |
| // | |
| // Instead we 'connect' it with our state management, by setting | |
| // it's value from state, and calling methods on the cubit directly. | |
| // | |
| // EASY! | |
| child: ReactiveTextField( | |
| text: state.counter.toString(), | |
| onChange: | |
| ref.read(counterNotifierProvider.notifier).setValue, | |
| error: state.error, | |
| ), | |
| ), | |
| TextButton( | |
| onPressed: ref.read(counterNotifierProvider.notifier).clear, | |
| child: const Text('Clear'), | |
| ), | |
| ], | |
| ), | |
| ); | |
| }, | |
| ), | |
| floatingActionButton: Consumer( | |
| builder: (context, ref, child) { | |
| return FloatingActionButton( | |
| onPressed: ref.read(counterNotifierProvider.notifier).increment, | |
| tooltip: 'Increment', | |
| child: const Icon(Icons.add), | |
| ); | |
| }, | |
| ), | |
| ); | |
| } | |
| } | |
| // Let's take a look at the ReactiveTextField widget. | |
| class ReactiveTextField extends StatefulWidget { | |
| const ReactiveTextField({ | |
| super.key, | |
| required this.text, | |
| required this.onChange, | |
| this.error, | |
| }); | |
| final String text; | |
| final void Function(String text) onChange; | |
| final String? error; | |
| @override | |
| State<ReactiveTextField> createState() => _ReactiveTextFieldState(); | |
| } | |
| class _ReactiveTextFieldState extends State<ReactiveTextField> { | |
| // ReactiveTextField will hold onto its own TextEditingController and Focusnode, | |
| // which you otherwise had to create and maintain yourself. | |
| final _controller = TextEditingController(); | |
| final _focusNode = FocusNode(); | |
| @override | |
| void initState() { | |
| super.initState(); | |
| _controller.text = widget.text; | |
| } | |
| @override | |
| void didUpdateWidget(covariant ReactiveTextField oldWidget) { | |
| super.didUpdateWidget(oldWidget); | |
| // This is an important part! | |
| // Without this check, there will be an infinite loop! | |
| if (widget.text != _controller.text) { | |
| _controller.text = widget.text; | |
| _focusNode.unfocus(); | |
| } | |
| } | |
| @override | |
| void dispose() { | |
| _controller.dispose(); | |
| _focusNode.dispose(); | |
| super.dispose(); | |
| } | |
| @override | |
| Widget build(BuildContext context) { | |
| // ReactiveTextField simply wraps a 'normal' TextField. | |
| return TextField( | |
| controller: _controller, | |
| focusNode: _focusNode, | |
| onChanged: widget.onChange, | |
| decoration: InputDecoration( | |
| errorText: widget.error, | |
| ), | |
| ); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment