Skip to content

Instantly share code, notes, and snippets.

@WojuadeAA
Created September 29, 2023 09:56
Show Gist options
  • Select an option

  • Save WojuadeAA/ed5517eaf286d3508a3ce2d6b5ab3f65 to your computer and use it in GitHub Desktop.

Select an option

Save WojuadeAA/ed5517eaf286d3508a3ce2d6b5ab3f65 to your computer and use it in GitHub Desktop.
ReactiveTextField (Riverpod)

ReactiveTextField (Riverpod)

Created with <3 with dartpad.dev.

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