1673629500
Express is an asynchronous, simple, powerful, yet unopinionated web application server written in Swift
First make sure, please, you have followed the installation section steps.
Create a project:
swift-express init HelloExpress
cd HelloExpress
swift-express bootstrap
open HelloExpress.xcodeproj
Create new API:
app.get("/myecho") { request in
.ok(request.query["message"]?.first)
}
Run from xCode or command line with:
swift-express build
swift-express run
Test it in the browser: http://localhost:9999/myecho?message=Hello
A complete Swift Express command line documentation can be found here: https://github.com/crossroadlabs/ExpressCommandLine
First install the following components (if you have not yet):
xcode-select --install
in terminalRun the following in terminal:
brew tap crossroadlabs/tap
brew install swift-express
For instructions on how to get Express installed on Linux, please, refer to the installation section in the ducumentation.
Create a project as it is described in the getting started section. Now you can start playing with examples.
All the examples can be found in Demo
project inside the main repo.
app.get("/hello") { request in
.ok(AnyContent(str: "<h1>Hello Express!!!</h1>", contentType: "text/html"))
}
Launch the app and follow the link: http://localhost:9999/hello?message=Hello
If you don't know what this is you might want to better skip it for now to the next section: URL params. To get more information see this first. We have our APIs based on Future pattern. Our implementation is based on BrightFutures, thanks @Thomvis!
Express can handle it both ways. All your syncronous code will be executed in a separate queue in a traditional way, so if you are a fan of this approach - it will work (like in "Hello Express" example above).
Still if you want to benefit from asynchronicity, we provide a very powerful API set that accepts futures as result of your handler.
Let's assume you have following function somewhere:
func calcFactorial(num:Double) -> Future<Double, AnyError>
it's a purely asyncronous function that returns future. It would be really nice if it could be handled asynchronously as well in a nice functional way. Here is an example of how it could be done.
// (request -> Future<Action<AnyContent>, AnyError> in) - this is required to tell swift you want to return a Future
// hopefully inference in swift will get better eventually and just "request in" will be enough
app.get("/factorial/:num(\\d+)") { request -> Future<Action<AnyContent>, AnyError> in
// get the number from the url
let num = request.params["num"].flatMap{Double($0)}.getOrElse(0)
// get the factorial Future. Returns immediately - non-blocking
let factorial = calcFactorial(num)
//map the result of future to Express Action
let future = factorial.map { fac in
Action.ok(String(fac))
}
//return the future
return future
}
Let's get our echo example from Getting Started a bit further. Our routing engine, which is largely based on NodeJS analog path-to-regex. You can read the complete documentation on how to use path patterns here. Now an example with URL param:
//:param - this is how you define a part of URL you want to receive through request object
app.get("/echo/:param") { request in
//here you get the param from request: request.params["param"]
.ok(request.params["param"])
}
app.get("/:file+", action: StaticAction(path: "public", param:"file"))
The code above tells Express to serve all static files from the public folder recursively. If you want to serve just the first level in folder, use:
app.get("/:file", action: StaticAction(path: "public", param:"file"))
The difference is just in the pattern: /:file
versus /:file+
. For more information see our routing section.
First of all we need to register the JSON view in the system:
//now we can refer to this view by name
app.views.register(JsonView())
Let's say we want to build a simple API for users registration. We want our API consumers to POST
to /api/user
a JSON object and get a JSON
response back.
app.post("/api/user") { request in
//check if JSON has arrived
guard let json = request.body?.asJSON() else {
return Action.ok("Invalid request")
}
//check if JSON object has username field
guard let username = json["username"].string else {
return Action.ok("Invalid request")
}
//compose the response as a simple dictionary
let response =
["status": "ok",
"description": "User with username '" + username + "' created succesfully"]
//render disctionary as json (remember the one we've registered above?)
return .render(JsonView.name, context: response)
}
Lines above will do the job. Post this JSON
:
{
"username": "swiftexpress"
}
to our api URL: http://localhost:9999/api/user
(don't forget application/json
content type header) and you will get this response:
{
"status": "ok",
"description": "User with username 'swiftexpress' created succesfully"
}
First of all you need to switch the template engine on:
//we recommend mustache template engine
app.views.register(StencilViewEngine())
Now create a file called hello.stencil
in the views
directory:
<html>
<body>
<h1>Hello from Stencil: {{user}}</h1>
</body>
</html>
Add a new request handler:
//user as an url param
app.get("/hello/:user.html") { request in
//get user
let user = request.params["user"]
//if there is a user - create our context. If there is no user, context will remain nil
let context = user.map {["user": $0]}
//render our template named "hello"
return .render("hello", context: context)
}
Now follow the link to see the result: http://localhost:9999/hello/express.html
Swift essentially is a new generation programming language combining simplicity and all the modern stuff like functional programming.
We were inspired (and thus influenced) mainly by two modern web frameworks: Express.js and Play. So, we are trying to combine the best of both worlds taking simplicity from Express.js and modern robust approach of Play
Let us know if we are on the right path! Influence the project, create feature requests, API change requests and so on. While we are in our early stages, it's easy to change. We are open to suggestions!
v0.4: Swift 3.0
v0.3: linux support
v0.2.1: minor changes
v0.2: Solid OS X release
v0.1: Initial Public Release
To get started, sign the Contributor License Agreement.
<h5 align="right"><a href="http://demo.swiftexpress.io/">Live 🐧 server running Demo <img src="https://cdn0.iconfinder.com/data/icons/glyphpack/34/play-circle-32.png" height=16/></a></h5>
<h5 align="right"><a href="http://swiftexpress.io/">Eating our own dog food <img src="https://cdn0.iconfinder.com/data/icons/glyphpack/147/globe-full-32.png" height=16/></a></h5>
<br/>
</p>
Author: Crossroadlabs
Source Code: https://github.com/crossroadlabs/Express
License: GPL-3.0, LGPL-3.0 licenses found
1671809160
We've all had to manage complex forms in our Angular apps, and it can get messy really quickly. Making sure to use all that Angular has to offer for reactive forms helps make the experience better. This can be done by using nested FormGroup
s and FormArray
s. In this article, we'll take a look at how this can help us manage a complex form.
I work at a health insurance company for my full time job. As you can imagine, we deal with a lot of forms, and some of them are very complex. For example, one form allows an employer to add an employee and their dependents to an insurance policy. The employee form alone has about 20 fields that need to be filled out, and then each dependent has 8 required fields and an optional set of address fields. There is no limit to the number of dependents that can be added to the form. The logic in the front end began to quickly grow out of control, with us manually checking to see if the form data was valid (instead of using the FormGroup.valid
attribute) and making sure that the reusable forms were emitting the data to the parent component to be saved. The complexity caused us to hesitate any time we needed to edit the page. Finally I decided it was time to take another look at the page and simplify it and leverage the power of Angular and what it gives us for free.
I've been working on a form at work that allows an employer to add an employee and their dependents to their health plan. The form can get complicated, since there can be multiple dependents. We were reusing the (reactive) dependent form for each dependent that was added, but were manually determining if the required fields were filled out. Each time the valuesChanged
observable emitted a new value, we'd determine the validity of the form. We were doing something similar with the employee form. In addition, we also were manually taking the value of the form and emitting that to the parent container so it could be submitted when necessary.
All this was really complicated, so I did a little refactoring. The layout was similar: there was a container component for the page, an employee form component, and 0 or more dependent forms. But instead of the container component storing the value of each form after responding to a new Output
emission, the children components now emit their form to the container component when the form is initialized and stored in a different form there. I know, this is confusing, but this visualization should help:
// container.component.ts
export class ContainerComponent {
public form: FormGroup;
constructor(private _fb: FormBuilder) {}
ngOnInit() {
this.form = this._fb.group({
employeeForm: null,
dependents: this._fb.array([])
})
}
get dependentSubForms () {
return this.form.get('dependents') as FormArray;
}
employeeFormReady(form: FormGroup) {
this.form.setControl('employeeForm', form);
}
dependentSubFormReady(form: FormGroup) {
this.dependentSubForms.push(form);
}
}
In this container component, we create the main form. This is how we'll manage the data from the employee form and all the dependents. When a form is initialized, we'll add it to the main form object. In the case of the employee form, we set the employeeForm
control to a FormGroup
, which is contained in a child component. The dependent forms are managed essentially the same way, but they are added to a FormArray
instead of a single control.
At this point, the form in ContainerComponent
will get all updated values from the children components, as well as set its validity based on the forms coming from the children components. When we're ready to get the value from all the forms, the form
object in the ContainerComponent
will contain all the data entered.
You can play with a demo here on StackBlitz.
This may seem a little complicated, but it is easier than the alternative method that we were managing previously. Angular is really powerful, and there's no need for us to do its job for it. This manner is also flexible. It can be used in the way I did above, but I also used the same format recently to build a multi step form. Each time a new portion of the form was shown on the page, we added a new control to the form. It made managing each step really easy.
This is also not the only way to do this. One way that was suggested was using ControlValueAccessor
s, and that is another way that I'll look into in the future to see how it compares to what we're now doing.
Original article source at: https://www.prestonlamb.com
1666594800
Watermill is a Go library for working efficiently with message streams. It is intended for building event driven applications, enabling event sourcing, RPC over messages, sagas and basically whatever else comes to your mind. You can use conventional pub/sub implementations like Kafka or RabbitMQ, but also HTTP or MySQL binlog if that fits your use case.
Pick what you like the best or see in order:
Building distributed and scalable services is rarely as easy as some may suggest. There is a lot of hidden knowledge that comes with writing such systems. Just like you don't need to know the whole TCP stack to create a HTTP REST server, you shouldn't need to study all of this knowledge to start with building message-driven applications.
Watermill's goal is to make communication with messages as easy to use as HTTP routers. It provides the tools needed to begin working with event-driven architecture and allows you to learn the details on the go.
At the heart of Watermill there is one simple interface:
func(*Message) ([]*Message, error)
Your handler receives a message and decides whether to publish new message(s) or return an error. What happens next is up to the middlewares you've chosen.
You can find more about our motivations in our Introducing Watermill blog post.
All publishers and subscribers have to implement an interface:
type Publisher interface {
Publish(topic string, messages ...*Message) error
Close() error
}
type Subscriber interface {
Subscribe(ctx context.Context, topic string) (<-chan *Message, error)
Close() error
}
Supported Pub/Subs:
github.com/ThreeDotsLabs/watermill-amqp/v2
)github.com/ThreeDotsLabs/watermill-bolt
)github.com/ThreeDotsLabs/watermill-firestore
)github.com/ThreeDotsLabs/watermill-googlecloud
)github.com/ThreeDotsLabs/watermill-http
)github.com/ThreeDotsLabs/watermill-io
)github.com/ThreeDotsLabs/watermill-kafka/v2
)github.com/ThreeDotsLabs/watermill-nats
)github.com/ThreeDotsLabs/watermill-sql
)All Pub/Subs implementation documentation can be found in the documentation.
Please check our contributing guide.
Watermill v1.0.0 has been released and is production-ready. The public API is stable and will not change without changing the major version.
To ensure that all Pub/Subs are stable and safe to use in production, we created a set of tests that need to pass for each of the implementations before merging to master. All tests are also executed in stress mode - that means that we are running all the tests 20x in parallel.
All tests are run with the race condition detector enabled (-race
flag in tests).
For more information about debugging tests, you should check tests troubleshooting guide.
Initial tools for benchmarking Pub/Subs can be found in watermill-benchmark.
All benchmarks are being done on a single 16 CPU VM instance, running one binary and dependencies in Docker Compose.
These numbers are meant to serve as a rough estimate of how fast messages can be processed by different Pub/Subs. Keep in mind that the results can be vastly different, depending on the setup and configuration (both much lower and higher).
Here's the short version for message size of 16 bytes.
Pub/Sub | Publish (messages / s) | Subscribe (messages / s) |
---|---|---|
Kafka (one node) | 63,506 | 110,811 |
Kafka (5 nodes) | 70,252 | 117,529 |
NATS | 76,208 | 38,169 |
SQL (MySQL) | 7,299 | 154 |
SQL (PostgreSQL) | 4,142 | 98 |
Google Cloud Pub/Sub | 7,416 | 39,591 |
AMQP | 2,408 | 10,608 |
GoChannel | 272,938 | 101,371 |
If you didn't find the answer to your question in the documentation, feel free to ask us directly!
Please join us on the #watermill
channel on the Three Dots Labs Discord.
Every bit of feedback is very welcome and appreciated. Please submit it using the survey.
It processes streams!
Author: ThreeDotsLabs
Source Code: https://github.com/ThreeDotsLabs/watermill
License: MIT license
1665721320
Rx is a generic abstraction of computation expressed through Observable<Element>
interface, which lets you broadcast and subscribe to values and other events from an Observable
stream.
RxSwift is the Swift-specific implementation of the Reactive Extensions standard.
While this version aims to stay true to the original spirit and naming conventions of Rx, this project also aims to provide a true Swift-first API for Rx APIs.
Cross platform documentation can be found on ReactiveX.io.
Like other Rx implementations, RxSwift's intention is to enable easy composition of asynchronous operations and streams of data in the form of Observable
objects and a suite of methods to transform and compose these pieces of asynchronous work.
KVO observation, async operations, UI Events and other streams of data are all unified under abstraction of sequence. This is the reason why Rx is so simple, elegant and powerful.
... understand
Single
, Completable
, Maybe
, Driver
, and ControlProperty
... and why do they exist?... install
... hack around
... interact
... compare
... understand the structure
RxSwift is as compositional as the asynchronous work it drives. The core unit is RxSwift itself, while other dependencies can be added for UI Work, testing, and more.
It comprises five separate components depending on each other in the following way:
┌──────────────┐ ┌──────────────┐
│ RxCocoa ├────▶ RxRelay │
└───────┬──────┘ └──────┬───────┘
│ │
┌───────▼──────────────────▼───────┐
│ RxSwift │
└───────▲──────────────────▲───────┘
│ │
┌───────┴──────┐ ┌──────┴───────┐
│ RxTest │ │ RxBlocking │
└──────────────┘ └──────────────┘
RxSwift
and RxRelay
.PublishRelay
, BehaviorRelay
and ReplayRelay
, three simple wrappers around Subjects. It depends on RxSwift
.RxSwift
.Here's an example | In Action |
---|---|
Define search for GitHub repositories ... | ![]() |
let searchResults = searchBar.rx.text.orEmpty .throttle(.milliseconds(300), scheduler: MainScheduler.instance) .distinctUntilChanged() .flatMapLatest { query -> Observable<[Repository]> in if query.isEmpty { return .just([]) } return searchGitHub(query) .catchAndReturn([]) } .observe(on: MainScheduler.instance) | |
... then bind the results to your tableview | |
searchResults .bind(to: tableView.rx.items(cellIdentifier: "Cell")) { (index, repository: Repository, cell) in cell.textLabel?.text = repository.name cell.detailTextLabel?.text = repository.url } .disposed(by: disposeBag) |
For Xcode 11 and below, use RxSwift 5.x.
RxSwift doesn't contain any external dependencies.
These are currently the supported installation options:
Open Rx.xcworkspace, choose RxExample
and hit run. This method will build everything and run the sample app
# Podfile
use_frameworks!
target 'YOUR_TARGET_NAME' do
pod 'RxSwift', '6.5.0'
pod 'RxCocoa', '6.5.0'
end
# RxTest and RxBlocking make the most sense in the context of unit/integration tests
target 'YOUR_TESTING_TARGET' do
pod 'RxBlocking', '6.5.0'
pod 'RxTest', '6.5.0'
end
Replace YOUR_TARGET_NAME
and then, in the Podfile
directory, type:
$ pod install
Each release starting with RxSwift 6 includes *.xcframework
framework binaries.
Simply drag the needed framework binaries to your Frameworks, Libraries, and Embedded Content section under your target's General tab.
Note: If you're using
RxCocoa
, be sure to also drag RxCocoaRuntime.xcframework before importingRxCocoa
.
Add this to Cartfile
github "ReactiveX/RxSwift" "6.5.0"
$ carthage update
Carthage defaults to building RxSwift as a Dynamic Library.
If you wish to build RxSwift as a Static Library using Carthage you may use the script below to manually modify the framework type before building with Carthage:
carthage update RxSwift --platform iOS --no-build
sed -i -e 's/MACH_O_TYPE = mh_dylib/MACH_O_TYPE = staticlib/g' Carthage/Checkouts/RxSwift/Rx.xcodeproj/project.pbxproj
carthage build RxSwift --platform iOS
Note: There is a critical cross-dependency bug affecting many projects including RxSwift in Swift Package Manager. We've filed a bug (SR-12303) in early 2020 but have no answer yet. Your mileage may vary. A partial workaround can be found here.
Create a Package.swift
file.
// swift-tools-version:5.0
import PackageDescription
let package = Package(
name: "RxTestProject",
dependencies: [
.package(url: "https://github.com/ReactiveX/RxSwift.git", .exact("6.5.0"))
],
targets: [
.target(name: "RxTestProject", dependencies: ["RxSwift", "RxCocoa"])
]
)
$ swift build
To build or test a module with RxTest dependency, set TEST=1
.
$ TEST=1 swift test
$ git submodule add git@github.com:ReactiveX/RxSwift.git
Rx.xcodeproj
into Project NavigatorProject > Targets > Build Phases > Link Binary With Libraries
, click +
and select RxSwift
, RxCocoa
and RxRelay
targetsAuthor: ReactiveX
Source Code: https://github.com/ReactiveX/RxSwift
License: MIT license
1661424300
This is a model-driven approach to handling Forms inputs and validations, heavily inspired in Angular's Reactive Forms.
For help getting started with Flutter, view the online documentation, which offers tutorials, samples, guidance on mobile development, and a full API reference.
For using Reactive Forms in projects below Flutter 2.8.0 please use the version <= 10.7.0 of Reactive Forms.
For using Reactive Forms in projects below Flutter 2.2.0 please use the version <= 10.2.0 of Reactive Forms.
For using Reactive Forms in projects with Flutter 1.17.0 please use the version 7.6.3 of Reactive Forms.
Reactive Forms v8.x includes the intl package. If a version conflict is present, then you should use dependency_overrides to temporarily override all references to intl and set the one that better fits your needs.
Once you're familiar with Flutter you may install this package adding reactive_forms
to the dependencies list of the pubspec.yaml
file as follow:
dependencies:
flutter:
sdk: flutter
reactive_forms: ^14.1.0
Then run the command flutter packages get
on the console.
A form is composed by multiple fields or controls.
To declare a form with the fields name and email is as simple as:
final form = FormGroup({
'name': FormControl<String>(value: 'John Doe'),
'email': FormControl<String>(),
});
Notice in the example above that in the case of the name we have also set a default value, in the case of the email the default value is null.
Given the FormGroup:
final form = FormGroup({
'name': FormControl<String>(value: 'John Doe'),
'email': FormControl<String>(value: 'johndoe@email.com'),
});
You can get the value of a single FormControl as simple as:
String get name() => this.form.control('name').value;
But you can also get the complete Form data as follows:
print(form.value);
The previous code prints the following output:
{
"name": "John Doe",
"email": "johndoe@email.com"
}
FormGroup.value returns an instance of Map<String, dynamic> with each field and its value.
To set value to controls you can use two approaches:
// set value directly to the control
this.form.control('name').value = 'John';
// set value to controls by setting value to the form
this.form.value = {
'name': 'John',
'email': 'john@email.com',
};
You can add validators to a FormControl as follows:
final form = FormGroup({
'name': FormControl<String>(validators: [Validators.required]),
'email': FormControl<String>(validators: [
Validators.required,
Validators.email,
]),
});
If at least one FormControl is invalid then the FormGroup is invalid
There are common predefined validators, but you can implement custom validators too.
A custom FormControl validator is a function that receives the control to validate and returns a Map. If the value of the control is valid the function must returns null otherwise returns a Map with a key and custom information, in the previous example we just set true as custom information.
Let's implement a custom validator that validates a control's value must be true:
final form = FormGroup({
'acceptLicense': FormControl<bool>(
value: false,
validators: [_requiredTrue], // custom validator
),
});
/// Validates that control's value must be `true`
Map<String, dynamic> _requiredTrue(AbstractControl<dynamic> control) {
return control.isNotNull &&
control.value is bool &&
control.value == true
? null
: {'requiredTrue': true};
}
You can see the current implementation of predefined validators in the source code to see more examples.
Validator.pattern is a validator that comes with Reactive Forms. Validation using regular expressions have been always a very useful tool to solve validation requirements. Let's see how we can validate American Express card numbers:
American Express card numbers start with 34 or 37 and have 15 digits.
const americanExpressCardPattern = r'^3[47][0-9]{13}$';
final cardNumber = FormControl<String>(
validators: [Validators.pattern(americanExpressCardPattern)],
);
cardNumber.value = '395465465421'; // not a valid number
expect(cardNumber.valid, false);
expect(cardNumber.hasError('pattern'), true);
The above code is a Unit Test extracted from Reactive Forms tests.
If we print the value of FormControl.errors:
print(cardNumber.errors);
We will get a Map like this:
{
"pattern": {
"requiredPattern": "^3[47][0-9]{13}$",
"actualValue": 395465465421
}
}
There are special validators that can be attached to FormGroup. In the next section we will see an example of that.
There are some cases where we want to implement a Form where a validation of a field depends on the value of another field. For example a sign-up form with email and emailConfirmation or password and passwordConfirmation.
For that cases we could implement a custom validator and attach it to the FormGroup, let's see an example:
final form = FormGroup({
'name': FormControl<String>(validators: [Validators.required]),
'email': FormControl<String>(validators: [Validators.required, Validators.email]),
'password': FormControl<String>(validators: [
Validators.required,
Validators.minLength(8),
]),
'passwordConfirmation': FormControl<String>(),
}, validators: [
_mustMatch('password', 'passwordConfirmation')
]);
Notice the use of *Validators.minLength(8)*
In the previous code we have added two more fields to the form: password and passwordConfirmation, both fields are required and the password must be at least 8 characters length.
However the most important thing here is that we have attached a validator to the FormGroup. This validator is a custom validator and the implementation follows as:
ValidatorFunction _mustMatch(String controlName, String matchingControlName) {
return (AbstractControl<dynamic> control) {
final form = control as FormGroup;
final formControl = form.control(controlName);
final matchingFormControl = form.control(matchingControlName);
if (formControl.value != matchingFormControl.value) {
matchingFormControl.setErrors({'mustMatch': true});
// force messages to show up as soon as possible
matchingFormControl.markAsTouched();
} else {
matchingFormControl.removeError('mustMatch');
}
return null;
};
}
Fortunately you don't have to implement a custom must match validator because we have already included it into the code of the reactive_forms package so you should reuse it. The previous form definition becomes into:
final form = FormGroup({
'name': FormControl<String>(validators: [Validators.required]),
'email': FormControl<String>(validators: [Validators.required, Validators.email]),
'emailConfirmation': FormControl<String>(),
'password': FormControl<String>(validators: [Validators.required, Validators.minLength(8)]),
'passwordConfirmation': FormControl<String>(),
}, validators: [
Validators.mustMatch('email', 'emailConfirmation'),
Validators.mustMatch('password', 'passwordConfirmation'),
]);
Some times you want to perform a validation against a remote server, this operations are more time consuming and need to be done asynchronously.
For example you want to validate that the email the user is currently typing in a registration form is unique and is not already used in your application. Asynchronous Validators are just another tool so use it wisely.
Asynchronous Validators are very similar to their synchronous counterparts, with the following difference:
Asynchronous validation executes after the synchronous validation, and is performed only if the synchronous validation is successful. This check allows forms to avoid potentially expensive async validation processes (such as an HTTP request) if the more basic validation methods have already found invalid input.
After asynchronous validation begins, the form control enters a pending state. You can inspect the control's pending property and use it to give visual feedback about the ongoing validation operation.
Code speaks more than a thousand words :) so let's see an example.
Let's implement the previous mentioned example: the user is typing the email in a registration Form and you want to validate that the email is unique in your System. We will implement a custom async validator for that purpose.
final form = FormGroup({
'email': FormControl<String>(
validators: [
Validators.required, // traditional required and email validators
Validators.email,
],
asyncValidators: [_uniqueEmail], // custom asynchronous validator :)
),
});
We have declared a simple Form with an email field that is required and must have a valid email value, and we have include a custom async validator that will validate if the email is unique. Let's see the implementation of our new async validator:
/// just a simple array to simulate a database of emails in a server
const inUseEmails = ['johndoe@email.com', 'john@email.com'];
/// Async validator example that simulates a request to a server
/// and validates if the email of the user is unique.
Future<Map<String, dynamic>> _uniqueEmail(AbstractControl<dynamic> control) async {
final error = {'unique': false};
final emailAlreadyUsed = await Future.delayed(
Duration(seconds: 5), // a delay to simulate a time consuming operation
() => inUseEmails.contains(control.value),
);
if (emailAlreadyUsed) {
control.markAsTouched();
return error;
}
return null;
}
Note the use of control.markAsTouched() to force the validation message to show up as soon as possible.
The previous implementation was a simple function that receives the AbstractControl and returns a Future that completes 5 seconds after its call and performs a simple check: if the value of the control is contained in the server array of emails.
If you want to see Async Validators in action with a full example using widgets and animations to feedback the user we strong advice you to visit our Wiki. We have not included the full example in this README.md file just to simplify things here and to not anticipate things that we will see later in this doc.
Asynchronous validators have a debounce time that is useful if you want to minimize requests to a remote API. The debounce time is set in milliseconds and the default value is 250 milliseconds.
You can set a different debounce time as an optionally argument in the FormControl constructor.
final control = FormControl<String>(
asyncValidators: [_uniqueEmail],
asyncValidatorsDebounceTime: 1000, // sets 1 second of debounce time.
);
To explain what Composing Validators is, let's see an example:
We want to validate a text field of an authentication form. In this text field the user can write an email or a phone number and we want to make sure that the information is correctly formatted. We must validate that input is a valid email or a valid phone number.
final phonePattern = '<some phone regex pattern>';
final form = FormGroup({
'user': FormControl<String>(
validators: [
Validators.composeOR([
Validators.email,
Validators.pattern(phonePattern),
])
],
),
});
Note that Validators.composeOR receives a collection of validators as argument and returns a validator.
With Validators.composeOR we are saying to FormControl that if at least one validator evaluate as VALID then the control is VALID it's not necessary that both validators evaluate to valid.
Another example could be to validate multiples Credit Card numbers. In that case you have several regular expression patterns for each type of credit card. So the user can introduce a card number and if the information match with at least one pattern then the information is considered as valid.
final form = FormGroup({
'cardNumber': FormControl<String>(
validators: [
Validators.composeOR([
Validators.pattern(americanExpressCardPattern),
Validators.pattern(masterCardPattern),
Validators.pattern(visaCardPattern),
])
],
),
});
FormGroup is not restricted to contains only FormControl, it can nest others FormGroup so you can create more complex Forms.
Supose you have a Registration Wizzard with several screens. Each screen collect specific information and at the end you want to collect all that information as one piece of data:
final form = FormGroup({
'personal': FormGroup({
'name': FormControl<String>(validators: [Validators.required]),
'email': FormControl<String>(validators: [Validators.required]),
}),
'phone': FormGroup({
'phoneNumber': FormControl<String>(validators: [Validators.required]),
'countryIso': FormControl<String>(validators: [Validators.required]),
}),
'address': FormGroup({
'street': FormControl<String>(validators: [Validators.required]),
'city': FormControl<String>(validators: [Validators.required]),
'zip': FormControl<String>(validators: [Validators.required]),
}),
});
Note how we have set the data type to a FormControl, although this is not mandatory when declaring a Form, we highly recommend this syntax as good practice or to use the FormBuilder syntax.
Using FormBuilder (read FormBuilder section below):
final form = fb.group({
'personal': fb.group({
'name': ['', Validators.required],
'email': ['', Validators.required],
}),
'phone': fb.group({
'phoneNumber': ['', Validators.required],
'countryIso': ['', Validators.required],
}),
'address': fb.group({
'street': ['', Validators.required],
'city': ['', Validators.required],
'zip': ['', Validators.required],
}),
});
You can collect all data using FormGroup.value:
void _printFormData(FormGroup form) {
print(form.value);
}
The previous method outputs a Map as the following one:
{
"personal": {
"name": "...",
"email": "..."
},
"phone": {
"phoneNumber": "...",
"countryIso": "..."
},
"address": {
"street": "...",
"city": "...",
"zip": "..."
}
}
And of course you can access to a nested FormGroup as following:
FormGroup personalForm = form.control('personal');
A simple way to create a wizard is for example to wrap a PageView within a ReactiveForm and each Page inside the PageView can contains a ReactiveForm to collect specific data.
FormArray is an alternative to FormGroup for managing any number of unnamed controls. As with FormGroup instances, you can dynamically insert and remove controls from FormArray instances, and the form array instance value and validation status is calculated from its child controls.
You don't need to define a key for each control by name, so this is a great option if you don't know the number of child values in advance.
Let's see a simple example:
final form = FormGroup({
'emails': FormArray<String>([]), // an empty array of emails
});
We have defined just an empty array. Let's define another array with two controls:
final form = FormGroup({
'emails': FormArray<String>([
FormControl<String>(value: 'john@email.com'),
FormControl<String>(value: 'susan@email.com'),
]),
});
Note that you don't have to specify the name of the controls inside of the array.
If we output the value of the previous form group we will get something like this:
print(form.value);
{
"emails": ["john@email.com", "susan@email.com"]
}
Let's dynamically add another control:
final array = form.control('emails') as FormArray<String>;
// adding another email
array.add(
FormControl<String>(value: 'caroline@email.com'),
);
print(form.value);
{
"emails": ["john@email.com", "susan@email.com", "caroline@email.com"]
}
Another way of add controls is to assign values directly to the array:
// Given: an empty array of strings
final array = FormArray<String>([]);
// When: set value to array
array.value = ["john@email.com", "susan@email.com", "caroline@email.com"];
// Then: the array is no longer empty
expect(array.controls.length, 3);
// And: array has a control for each inserted value
expect(array.controls('0').value, "john@email.com");
expect(array.controls('1').value, "susan@email.com");
expect(array.controls('2').value, "caroline@email.com");
To get a control from the array you must pass the index position as a String. This is because FormGroup and FormArray inherited from the same parent class and FormControl gets the controls by name (String).
A more advanced example:
// an array of contacts
final contacts = ['john@email.com', 'susan@email.com', 'caroline@email.com'];
// a form with a list of selected emails
final form = FormGroup({
'selectedEmails': FormArray<bool>([], // an empty array of controls
validators: [emptyAddressee], // validates that at least one email is selected
),
});
// get the array of controls
final formArray = form.control('selectedEmails') as FormArray<bool>;
// populates the array of controls.
// for each contact add a boolean form control to the array.
formArray.addAll(
contacts.map((email) => FormControl<bool>(value: true)).toList(),
);
// validates that at least one email is selected
Map<String, dynamic> emptyAddressee(AbstractControl control) {
final emails = (control as FormArray<bool>).value;
return emails.any((isSelected) => isSelected)
? null
: {'emptyAddressee': true};
}
You can also create arrays of groups:
// an array of groups
final addressArray = FormArray([
FormGroup({
'city': FormControl<String>(value: 'Sofia'),
'zipCode': FormControl<int>(value: 1000),
}),
FormGroup({
'city': FormControl<String>(value: 'Havana'),
'zipCode': FormControl<int>(value: 10400),
}),
]);
Another example using FormBuilder:
// an array of groups using FormBuilder
final addressArray = fb.array([
fb.group({'city': 'Sofia', 'zipCode': 1000}),
fb.group({'city': 'Havana', 'zipCode': 10400}),
]);
or just:
// an array of groups using a very simple syntax
final addressArray = fb.array([
{'city': 'Sofia', 'zipCode': 1000},
{'city': 'Havana', 'zipCode': 10400},
]);
You can iterate over groups as follow:
final cities = addressArray.controls
.map((control) => control as FormGroup)
.map((form) => form.control('city').value)
.toList();
A common mistake is to declare an array of groups as FormArray<FormGroup>.
An array of FormGroup must be declared as FormArray() or as FormArray<Map<String, dynamic>>().
The FormBuilder provides syntactic sugar that shortens creating instances of a FormGroup, FormArray and FormControl. It reduces the amount of boilerplate needed to build complex forms.
// creates a group
final form = fb.group({
'name': 'John Doe',
'email': ['', Validators.required, Validators.email],
'password': Validators.required,
});
The previous code is equivalent to the following one:
final form = FormGroup({
'name': FormControl<String>(value: 'John Doe'),
'email': FormControl<String>(value: '', validators: [Validators.required, Validators.email]),
'password': FormControl<String>(validators: [Validators.required]),
});
// creates an array
final aliases = fb.array(['john', 'little john']);
// creates a control of type String with a required validator
final control = fb.control<String>('', [Validators.required]);
// create a group
final group = fb.group(
// creates a control with default value and disabled state
'name': fb.state(value: 'john', disabled: true),
);
To retrieves nested controls you can specify the name of the control as a dot-delimited string that define the path to the control:
final form = FormGroup({
'address': FormGroup({
'city': FormControl<String>(value: 'Sofia'),
'zipCode': FormControl<int>(value: 1000),
}),
});
// get nested control value
final city = form.control('address.city');
print(city.value); // outputs: Sofia
So far we have only defined our model-driven form, but how do we bind the form definition with our Flutter widgets? Reactive Forms Widgets is the answer ;)
Let's see an example:
@override
Widget build(BuildContext context) {
return ReactiveForm(
formGroup: this.form,
child: Column(
children: <Widget>[
ReactiveTextField(
formControlName: 'name',
),
ReactiveTextField(
formControlName: 'email',
),
ReactiveTextField(
formControlName: 'password',
obscureText: true,
),
],
),
);
}
The example above ignores the emailConfirmation and passwordConfirmation fields previously seen for simplicity.
Validation messages can be defined at two different levels:
Each reactive widget like ReactiveTextField
, ReactiveDropdownField
, and all others have the property validationMessages
as an argument of their constructors. In order to define custom validation messages at widget level, just provide the property validationMessages
with the corresponding text values for each error as shown below:
@override
Widget build(BuildContext context) {
return ReactiveForm(
formGroup: this.form,
child: Column(
children: <Widget>[
ReactiveTextField(
formControlName: 'name',
validationMessages: {
'required': (error) => 'The name must not be empty'
},
),
ReactiveTextField(
formControlName: 'email',
validationMessages: {
'required': (error) => 'The email must not be empty',
'email': (error) => 'The email value must be a valid email'
},
),
ReactiveTextField(
formControlName: 'password',
obscureText: true,
validationMessages: {
'required': (error) => 'The password must not be empty',
'minLength': (error) => 'The password must have at least 8 characters'
},
),
],
),
);
}
Reactive Forms have an utility class called ValidationMessage that brings access to common validation messages: required, email, pattern and so on. So instead of write 'required' you could use ValidationMessage.required as the key of validation messages:
return ReactiveTextField( formControlName: 'email', validationMessages: { ValidationMessage.required: (error) => 'The email must not be empty', ValidationMessage.email: (error) => 'The email value must be a valid email', }, ),
nice isn't it? ;)
You can also define custom validation messages at a higher level, for example, at the application level. When a reactive widget looks for an error message text, it first looks at widget level definition, if it doesn't find any config at widget level then it looks at the global config definition.
The global definition of validation messages allows you to define error messages in a centralized way and relieves you to define validation messages on each reactive widget of your application.
In order to define these configs at a higher level use the widget ReactiveFormConfig and define the validationMessages
.
Here is an example of the global definition for custom validation messages:
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return ReactiveFormConfig(
validationMessages: {
ValidationMessage.required: (error) => 'Field must not be empty',
ValidationMessage.email: (error) => 'Must enter a valid email',
},
child: MaterialApp(
home: Scaffold(
body: const Center(
child: Text('Hello Flutter Reactive Forms!'),
),
),
),
);
}
}
You can enrich the validation messages using parameters of the error instance. In the next example we are giving a more complete validation error to the user:
final form = FormGroup({
'password': FormControl<String>(
validators: [Validators.minLength(8)],
),
});
ReactiveTextField(
formControlName: 'password',
validationMessage: {
ValidationMessages.minLength: (error) =>
'The password must be at least ${(error as Map)['requiredLength']} characters long'
},
)
This will show the message: The password must be at least 8 characters long
Even when the FormControl is invalid, validation messages will begin to show up when the FormControl is touched. That means when the user taps on the ReactiveTextField widget and then remove focus or completes the text edition.
You can initialize a FormControl as touched to force the validation messages to show up at the very first time the widget builds.
final form = FormGroup({
'name': FormControl<String>(
value: 'John Doe',
validators: [Validators.required],
touched: true,
),
});
When you set a value to a FormControl from code and want to show up validations messages you must call FormControl.markAsTouched() method:
set name(String newName) {
final formControl = this.form.control('name');
formControl.value = newName;
formControl.markAsTouched();// if newName is invalid then validation messages will show up in UI
}
To mark all children controls of a FormGroup and FormArray you must call markAllAsTouched().
final form = FormGroup({ 'name': FormControl<String>( value: 'John Doe', validators: [Validators.required], touched: true, ), }); // marks all children as touched form.markAllAsTouched();
The second way to customize when to show error messages is to override the method showErrors in reactive widgets.
Let's suppose you want to show validation messages not only when it is invalid and touched (default behavior), but also when it's dirty:
ReactiveTextField(
formControlName: 'email',
// override default behavior and show errors when: INVALID, TOUCHED and DIRTY
showErrors: (control) => control.invalid && control.touched && control.dirty,
),
A control becomes dirty when its value change through the UI.
The method setErrors of the controls can optionally mark it as dirty too.
For a better User Experience some times we want to enable/disable the Submit button based on the validity of the Form. Getting this behavior, even in such a great framework as Flutter, some times can be hard and can lead to have individual implementations for each Form of the same application plus boilerplate code.
We will show you two different approaches to accomplish this very easily:
Let's add a submit button to our Form:
@override
Widget build(BuildContext context) {
return ReactiveForm(
formGroup: this.form,
child: Column(
children: <Widget>[
ReactiveTextField(
formControlName: 'email',
),
ReactiveTextField(
formControlName: 'password',
obscureText: true,
),
MySubmitButton(),
],
),
);
}
The above is a simple sign-in form with email, password, and a submit button.
Now let's see the implementation of the MySubmitButton widget:
class MySubmitButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final form = ReactiveForm.of(context);
return RaisedButton(
child: Text('Submit'),
onPressed: form.valid ? _onPressed : null,
);
}
void _onPressed() {
print('Hello Reactive Forms!!!');
}
}
Notice the use of ReactiveForm.of(context) to get access to the nearest FormGroup up the widget's tree.
In the previous example we have separated the implementation of the submit button in a different widget. The reasons behind this is that we want to re-build the submit button each time the validity of the FormGroup changes. We don't want to rebuild the entire Form, but just the button.
How is that possible? Well, the answer is in the expression:
final form = ReactiveForm.of(context);
The expression above have two important responsibilities:
ReactiveFormConsumer widget is a wrapped around the ReactiveForm.of(context) expression so that we can reimplement the previous example as follows:
@override
Widget build(BuildContext context) {
return ReactiveForm(
formGroup: this.form,
child: Column(
children: <Widget>[
ReactiveTextField(
formControlName: 'email',
),
ReactiveTextField(
formControlName: 'password',
obscureText: true,
),
ReactiveFormConsumer(
builder: (context, form, child) {
return RaisedButton(
child: Text('Submit'),
onPressed: form.valid ? _onSubmit : null,
);
},
),
],
),
);
}
void _onSubmit() {
print('Hello Reactive Forms!!!');
}
It is entirely up to you to decide which of the above two approaches to use, but note that to access the FormGroup via ReactiveForm.of(context) the consumer widget must always be down in the tree of the ReactiveForm widget.
There are some cases where we want to add or remove focus on a UI TextField without the interaction of the user. For that particular cases you can use FormControl.focus() or FormControl.unfocus() methods.
final form = fb.group({'name': 'John Doe'});
FormControl control = form.control('name');
control.focus(); // UI text field get focus and the device keyboard pop up
control.unfocus(); // UI text field lose focus
You can also set focus directly from the Form like:
final form = fb.group({'name': ''});
form.focus('name'); // UI text field get focus and the device keyboard pop up
final form = fb.group({
'person': fb.group({
'name': '',
}),
});
// set focus to a nested control
form.focus('person.name');
Another example is when you have a form with several text fields and each time the user completes edition in one field you want to request next focus field using the keyboard actions:
final form = fb.group({
'name': ['', Validators.required],
'email': ['', Validators.required, Validators.email],
'password': ['', Validators.required],
});
@override
Widget build(BuildContext context) {
return ReactiveForm(
formGroup: this.form,
child: Column(
children: <Widget>[
ReactiveTextField(
formControlName: 'name',
textInputAction: TextInputAction.next,
onSubmitted: () => this.form.focus('email'),
),
ReactiveTextField(
formControlName: 'email',
textInputAction: TextInputAction.next,
onSubmitted: () => this.form.focus('password'),
),
ReactiveTextField(
formControlName: 'password',
obscureText: true,
),
],
),
);
}
When you remove focus of a control, the control is marked as touched, that means that the validation error messages will show up in UI. To prevent validation messages to show up you can optionally set argument touched to false.
// remove the focus to the control and marks it as untouched. this.form.unfocus(touched: false);
To disabled a widget like ReactiveTextField all you need to do is to mark the control as disabled:
final form = FormGroup({
'name': FormControl<String>(),
});
FormControl control = form.control('name');
// the control is disabled and also the widget in UI is disabled.
control.markAsDisabled();
When a control is disabled it is exempt from validation checks and excluded from the aggregate value of any parent. Its status is DISABLED.
To retrieves all values of a FormGroup or FormArray regardless of disabled status in children use FormControl.rawValue or FormArray.rawValue respectively.
ReactiveTextField has more in common with TextFormField that with TextField. As we all know TextFormField is a wrapper around the TextField widget that brings some extra capabilities such as Form validations with properties like autovalidate and validator. In the same way ReactiveTextField is a wrapper around TextField that handle the features of validations in a own different way.
ReactiveTextField has all the properties that you can find in a common TextField, it can be customizable as much as you want just as a simple TextField or a TextFormField. In fact must of the code was taken from the original TextFormField and ported to have a reactive behavior that binds itself to a FormControl in a two-way binding.
Below is an example of how to create some ReactiveTextField with some common properties:
@override
Widget build(BuildContext context) {
return ReactiveForm(
formGroup: this.form,
child: Column(
children: <Widget>[
ReactiveTextField(
formControlName: 'name',
decoration: InputDecoration(
labelText: 'Name',
),
textCapitalization: TextCapitalization.words,
textAlign: TextAlign.center,
style: TextStyle(backgroundColor: Colors.white),
),
ReactiveTextField(
formControlName: 'phoneNumber',
decoration: InputDecoration(
labelText: 'Phone number',
),
keyboardType: TextInputType.number,
),
ReactiveTextField(
formControlName: 'password',
obscureText: true,
decoration: InputDecoration(
labelText: 'Password',
),
),
],
),
);
}
Because of the two-binding capability of the ReactiveTextField with a FormControl the widget don't include properties as controller, validator, autovalidate, onSaved, the FormControl is responsible for handling validation as well as changes notifications.
It does include some events like onChanged, onTab, onEditingComplete, and onSubmitted.
We are trying to keep reactive_forms
from bloating with third party dependencies this is why there is a separate library reactive_forms_widgets
which is under construction yet that provides a variety of more advanced field widgets. To know more about how to install it please visit the library repo and read the documentation about the widgets it contains.
flutter_advanced_switch
dropdown_search
file_picker
image_picker
multi_image_picker
CupertinoSegmentedControl
signature
flutter_touch_spin
RangeSlider
sleek_circular_slider
CupertinoTextField
flutter_rating_bar
macos_ui
pinput
CupertinoSwitch
pin_code_fields
CupertinoSlidingSegmentedControl
CupertinoSlider
flutter_colorpicker
month_picker_dialog
RawAutocomplete
flutter_typeahead
pin_input_text_field
direct_select
markdown_editable_textinput
code_text_field
phone_form_field
extended_text_field
CupertinoSlidingSegmentedControl
We have explain the common usage of a ReactiveTextField along this documentation.
ReactiveDropdownField as all the other reactive field widgets is almost the same as its native version DropdownButtonFormField but adding two-binding capabilities. The code is ported from the original native implementation. It have all the capability of styles and themes of the native version.
final form = FormGroup({
'payment': FormControl<int>(validators: [Validators.required]),
});
@override
Widget build(BuildContext context) {
return ReactiveForm(
formGroup: this.form,
child: Column(
children: <Widget>[
ReactiveDropdownField<int>(
formControlName: 'payment',
hint: Text('Select payment...'),
items: [
DropdownMenuItem(
value: 0,
child: Text('Free'),
),
DropdownMenuItem(
value: 1,
child: Text('Visa'),
),
DropdownMenuItem(
value: 2,
child: Text('Mastercard'),
),
DropdownMenuItem(
value: 3,
child: Text('PayPal'),
),
],
),
],
),
);
}
As you can see from the above example the usage of ReactiveDropdownField is almost the same as the usage of a common DropdownButtonFormField, except for the additional formControlName and validationMessages properties.
If you want to rebuild a widget each time a FormControl value changes you could use the ReactiveValueListenableBuilder widget.
In the following example we are listening for changes in lightIntensity. We change that value with a ReactiveSlider and show all the time the value in a Text widget:
final form = FormGroup({
'lightIntensity': FormControl<double>(value: 50.0),
});
@override
Widget build(BuildContext context) {
return ReactiveForm(
formGroup: this.form,
child: Column(
children: <Widget>[
ReactiveValueListenableBuilder<double>(
formControlName: 'lightIntensity',
builder: (context, value, child) {
return Text('lights at ${value?.toStringAsFixed(2)}%');
},
),
ReactiveSlider(
formControlName: 'lightIntensity',
max: 100.0,
),
],
)
);
}
Both widgets are responsible for exposing the FormGroup to descendants widgets in the tree. Let see an example:
// using ReactiveForm
@override
Widget build(BuildContext context) {
return ReactiveForm(
formGroup: this.form,
child: ReactiveTextField(
formControlName: 'email',
),
);
}
// using ReactiveFormBuilder
@override
Widget build(BuildContext context) {
return ReactiveFormBuilder(
form: () => this.form,
builder: (context, form, child) {
return ReactiveTextField(
formControlName: 'email',
);
},
);
}
The main differences are that ReactiveForm is a StatelessWidget so it doesn't save the instance of the FormGroup. You must declare the instance of the FormGroup in a StatefulWidget or resolve it from some Provider (state management library).
// Using ReactiveForm in a StatelessWidget and resolve the FormGroup from a provider
class SignInForm extends StatelessWidget {
@override
Widget build(BuildContext context) {
final viewModel = Provider.of<SignInViewModel>(context, listen: false);
return ReactiveForm(
formGroup: viewModel.form,
child: ReactiveTextField(
formControlName: 'email',
),
);
}
}
// Using ReactiveForm in a StatefulWidget and declaring FormGroup in the state.
class SignInForm extends StatefulWidget {
@override
_SignInFormState createState() => _SignInFormState();
}
class _SignInFormState extends State<SignInForm> {
final form = fb.group({
'email': Validators.email,
});
@override
Widget build(BuildContext context) {
return ReactiveForm(
formGroup: this.form,
child: ReactiveTextField(
formControlName: 'email',
),
);
}
}
If you declare a FormGroup in a StatelessWidget the group will be destroyed a created each time the instance of the StatelessWidget is destroyed and created, so you must preserve the FormGroup in a state or in a Bloc/Provider/etc.
By the other hand ReactiveFormBuilder is implemented as a StatefulWidget so it holds the created FormGroup in its state. That way is safe to declares the FormGroup in a StatelessWidget or get it from a Bloc/Provider/etc.
class SignInForm extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ReactiveFormBuilder(
form: () => fb.group({'email': Validators.email}),
builder: (context, form, child) {
return ReactiveTextField(
formControlName: 'email',
);
},
);
}
}
You should use ReactiveForm if:
You should use ReactiveFormBuilder if:
But the final decision is really up to you, you can use any of them in any situations ;)
Although Reactive Forms can be used with any state management library or even without any one at all, Reactive Forms gets its maximum potential when is used in combination with a state management library like the Provider plugin.
This way you can separate UI logic from business logic and you can define the FormGroup inside a business logic class and then exposes that class to widgets with mechanism like the one Provider plugin brings.
ReactiveFormsGenerator is the code generator for reactive_forms which will save you tons of time and make your forms type safe.
There is no reason write code manually! Let the code generation work for you.
Reactive Forms is not limited just to common widgets in Forms like text, dropdowns, sliders switch fields and etc, you can easily create custom widgets that two-way binds to FormControls and create your own set of Reactive Widgets ;)
In our Wiki you can find a tutorial of how to create your custom Reactive Widget.
You can also check Star Rating with Flutter Reactive Forms post as another example of a custom reactive widget.
Reactive Forms is not a fancy widgets package. It is not a library that brings some new Widgets with new shapes, colors or animations. It lets you to decide the shapes, colors, and animations you want for your widgets, but frees you from the responsibility of gathering and validating the data. And keeps the data in sync between your model and your widgets.
Reactive Forms does not pretend to replace the native widgets that you commonly use in your Flutter projects like TextFormField, DropdownButtonFormField or CheckboxListTile. Instead of that it brings new two-way binding capabilities and much more features to those same widgets.
Visit Migration Guide to see more details about different version breaking changes.
Run this command:
With Flutter:
$ flutter pub add reactive_forms
This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get
):
dependencies:
reactive_forms: ^14.1.0
Alternatively, your editor might support flutter pub get
. Check the docs for your editor to learn more.
Now in your Dart code, you can use:
import 'package:reactive_forms/reactive_forms.dart';
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:reactive_forms/reactive_forms.dart';
import 'package:reactive_forms_example/samples/add_dynamic_controls_sample.dart';
import 'package:reactive_forms_example/samples/array_sample.dart';
import 'package:reactive_forms_example/samples/complex_sample.dart';
import 'package:reactive_forms_example/samples/date_picker_sample.dart';
import 'package:reactive_forms_example/samples/disable_form_sample.dart';
import 'package:reactive_forms_example/samples/login_sample.dart';
import 'package:reactive_forms_example/samples/simple_sample.dart';
void main() {
runApp(ReactiveFormsApp());
}
class Routes {
static const complex = '/';
static const simple = '/simple';
static const addDynamicControls = '/add-dynamic-controls';
static const disableFormSample = '/disable-form-sample';
static const arraySample = '/array-sample';
static const datePickerSample = '/date-picker-sample';
static const reactiveFormWidgets = '/reactive-form-widgets';
static const loginSample = '/login-sample';
}
class ReactiveFormsApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ReactiveFormConfig(
validationMessages: {
ValidationMessage.required: (_) => 'Field is mandatory',
ValidationMessage.email: (_) => 'Must enter a valid email',
'uniqueEmail': (_) => 'This email is already in use',
},
child: MaterialApp(
theme: customTheme,
routes: <String, WidgetBuilder>{
Routes.complex: (_) => ComplexSample(),
Routes.simple: (_) => SimpleSample(),
Routes.addDynamicControls: (_) => AddDynamicControlsSample(),
Routes.disableFormSample: (_) => DisableFormSample(),
Routes.arraySample: (_) => ArraySample(),
Routes.loginSample: (_) => LoginSample(),
Routes.datePickerSample: (_) => DatePickerSample(),
//Routes.reactiveFormWidgets: (_) => ReactiveFormWidgetsSample(),
},
),
);
}
}
final customTheme = ThemeData.light().copyWith(
inputDecorationTheme: const InputDecorationTheme(
border: OutlineInputBorder(),
floatingLabelBehavior: FloatingLabelBehavior.auto,
alignLabelWithHint: true,
),
);
Author: joanpablo
Source Code: https://github.com/joanpablo/reactive_forms
License: MIT license
1661303760
An opinionated shiny framework.
yonder is designed to make building pragmatic applications fun and rewarding. On the UI side yonder features new reactive inputs such as navInput()
, chipInput()
and menuInput()
, as well as the latest Bootstrap components. On the server side yonder includes tools for showing alerts and toasts, displaying modal and popovers, hiding and showing panes of content, and more!
For examples of inputs and elements built using yonder please check out the documentation, https://nteetor.github.io/yonder/.
You may install a stable version of yonder from CRAN.
install.packages("yonder")
Alternatively, the development version of yonder may be installed from GitHub.
# install.packages("remotes")
remotes::install_github("nteetor/yonder")
Author: nteetor
Source Code: https://github.com/nteetor/yonder
License: View license
1660066860
Easily build rich and productive interactive web apps in R — no HTML/CSS/JavaScript required.
To install the stable version from CRAN:
install.packages("shiny")
Once installed, load the library and run an example:
library(shiny)
# Launches an app, with the app's source code included
runExample("06_tabsets")
# Lists more prepackaged examples
runExample()
For more examples and inspiration, check out the Shiny User Gallery.
For help with learning fundamental Shiny programming concepts, check out the Mastering Shiny book and the Shiny Tutorial. The former is currently more up-to-date with modern Shiny features, whereas the latter takes a deeper, more visual, dive into fundamental concepts.
To ask a question about Shiny, please use the RStudio Community website.
For bug reports, please use the issue tracker and also keep in mind that by writing a good bug report, you're more likely to get help with your problem.
We welcome contributions to the shiny package. Please see our CONTRIBUTING.md file for detailed guidelines of how to contribute.
Shiny is supported on the latest release version of R, as well as the previous four minor release versions of R. For example, if the latest release R version is 4.1, then that version is supported, as well as 4.0, 3.6, 3.5, and 3.4.
Author: rstudio
Source Code: https://github.com/rstudio/shiny
License: View license
1659786060
Flutter integration with MobX.dart.
Provides the Observer
widget that listens to observables and automatically rebuilds on changes.
class CounterExample extends StatefulWidget {
const CounterExample({Key key}) : super(key: key);
@override
_CounterExampleState createState() => _CounterExampleState();
}
class _CounterExampleState extends State<CounterExample> {
final _counter = Counter();
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
title: const Text('Counter'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Observer(
builder: (_) => Text(
'${_counter.value}',
style: const TextStyle(fontSize: 20),
)),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _counter.increment,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
Notice the use of the Observer
widget that listens to _counter.value
, an observable, and rebuilds on changes.
You can go here for more examples
Observer(Widget Function(BuildContext context) builder)
The builder
function will be monitored by MobX and tracks all the observables that are being used inside it. When any of the observables change, builder will be called again to rebuild the Widget
. This gives you a seamless way to create a reactive Widget
.
Note that the Observer
will print a warning if no observables are discovered in the builder
function.
Run this command:
With Flutter:
$ flutter pub add flutter_mobx
This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get
):
dependencies:
flutter_mobx: ^2.0.6+1
Alternatively, your editor might support flutter pub get
. Check the docs for your editor to learn more.
Now in your Dart code, you can use:
import 'package:flutter_mobx/flutter_mobx.dart';
Author: Mobxjs
Source Code: https://github.com/mobxjs/mobx.dart
License: MIT license
1658416440
Callbag source for connected gamepad inputs
yarn add callbag-gamepads
const pipe = require("callbag-pipe");
const forEach = require("callbag-for-each");
const gamepads = require("callbag-gamepads");
pipe(
gamepads,
forEach(gamepads => {
// have fun
})
);
If you're blessed with the pipeline operator:
gamepads
|> forEach(gamepads => {
// have fun
});
Author: aaronshaf
Source Code: https://github.com/aaronshaf/callbag-gamepads
License: MIT license
1658408940
Creates a new Callbag given an optional producer that dictates how to emit values and complete the Callbag.
npm install callbag-create
const create = require('callbag-create');
const forEach = require('callbag-for-each');
const pipe = require('callbag-pipe');
pipe(
create((next, error, done) => {
next('a');
next('b');
done();
next('c');
}),
forEach((v) => {
console.log(v);
})
);
// logs 'a', 'b', then completes.
// Calling next('c') does nothing since done() was called and terminated the callbag
const create = require('callbag-create');
const forEach = require('callbag-for-each');
const pipe = require('callbag-pipe');
const unsubscribe = pipe(
create((next, error, done) => {
const id = setTimeout(() => {
next('a');
done();
}, 1000);
return () => {
clearTimeout(id);
};
}),
subscribe({
next(v) {
console.log(v);
},
complete() {
console.log('Done()');
}
})
);
unsubscribe();
// logs nothing since it was unsubscribed before emitting and the timeout is cleared
Equivalent to xstream and RxJs never(). Never emits the completion message.
const create = require('callbag-create');
const forEach = require('callbag-for-each');
const pipe = require('callbag-pipe');
pipe(
create(() => {}),
forEach((v) => {
console.log(v); // void
})
);
Equivalent to xstream and RxJs empty(). Emits no value and immediatelly emits the completion message.
const create = require('callbag-create');
const subscribe = require('callbag-subscribe');
const pipe = require('callbag-pipe');
pipe(
create(),
subscribe({
next(v) {
console.log(v);
},
complete() {
console.log('Done()');
}
}) // => Done()
);
Author: franciscotln
Source Code: https://github.com/franciscotln/callbag-create
License: MIT license
1658401440
Callbag basics 👜
Basic callbag factories and operators to get started with. Callbag is just a spec, but callbag-basics
is a real library you can use.
Highlights:
Imagine a hybrid between an Observable and an (Async)Iterable, that's what callbags are all about. In addition, the internals are tiny because it's all done with a few simple callbacks, following the callbag spec. As a result, it's tiny and fast.
npm install callbag-basics
Import operators and factories:
const {forEach, fromIter, map, filter, pipe} = require('callbag-basics');
Log XY coordinates of click events on <button>
elements:
const {forEach, fromEvent, map, filter, pipe} = require('callbag-basics');
pipe(
fromEvent(document, 'click'),
filter(ev => ev.target.tagName === 'BUTTON'),
map(ev => ({x: ev.clientX, y: ev.clientY})),
forEach(coords => console.log(coords))
);
// {x: 110, y: 581}
// {x: 295, y: 1128}
// ...
Pick the first 5 odd numbers from a clock that ticks every second, then start observing them:
const {forEach, interval, map, filter, take, pipe} = require('callbag-basics');
pipe(
interval(1000),
map(x => x + 1),
filter(x => x % 2),
take(5),
forEach(x => console.log(x))
);
// 1
// 3
// 5
// 7
// 9
From a range of numbers, pick 5 of them and divide them by 4, then start pulling those one by one:
const {forEach, fromIter, take, map, pipe} = require('callbag-basics');
function* range(from, to) {
let i = from;
while (i <= to) {
yield i;
i++;
}
}
pipe(
fromIter(range(40, 99)), // 40, 41, 42, 43, 44, 45, 46, ...
take(5), // 40, 41, 42, 43, 44
map(x => x / 4), // 10, 10.25, 10.5, 10.75, 11
forEach(x => console.log(x))
);
// 10
// 10.25
// 10.5
// 10.75
// 11
The list below shows what's included.
The Callbag philosophy is: build it yourself. :) You can send pull requests, but even better, don't depend on the repo owner accepting it. Just fork the project, customize it as you wish, and publish your fork on npm. As long as it follows the callbag spec, everything will be interoperable! :)
Author: staltz
Source Code: https://github.com/staltz/callbag-basics
License: MIT license
1656711660
By using Reactter
you get:
In your flutter project add the dependency:
Run this command with Flutter:
flutter pub add reactter
or add a line like this to your package's pubspec.yaml
:
dependencies:
reactter: ^2.3.2
Now in your Dart code, you can use:
import 'package:reactter/reactter.dart';
ReactterContext
ReactterContext
is a abstract class with functionality to manages hooks (like UseState
, UseEffect
) and lifecycle events.
You can use it's functionalities, creating a class that extends it:
class AppContext extends ReactterContext {}
RECOMMENDED: Name class with
Context
suffix, for easy locatily.
UseState
hookUseState
is a hook that allow to manage a state.
INFO: The different with other management state is that not use
Stream
. We know thatStream
consumes a lot of memory and we had decided to use the simple publish-subscribe pattern.
You can add it on any part of class, with context(this
) argument(RECOMMENDED):
class AppContext extends ReactterContext {
late final count = UseState(0, this);
}
or add it on listenHooks
method which ReactterContext
exposes it:
class AppContext extends ReactterContext {
final count = UseState(0);
AppContext() {
listenHooks([count]);
}
}
NOTE: If you add
UseState
withcontext
argument, not need to add it onlistenHooks
, but is required declarate it aslate
.
UseState
exposes value
property that helps to read and writter its state:
class AppContext extends ReactterContext {
late final count = UseState(0, this);
AppContext() {
print("Prev state: ${count.value}");
count.value = 10;
print("Current state: ${count.value}")
}
}
A UseState
notifies that its state has changed when the previous state is different from the current state.
NOTE: If its state is a
Object
, not detect internal changes, only when states is anotherObject
.NOTE: If you want to force notify, execute
update
method whichUseState
exposes it.
UseEffect
hookUseEffect
is a hook that allow to manage side-effect.
You can add it on constructor of class:
class AppContext extends ReactterContext {
late final count = UseState(0, this);
late final isOdd = UseState(false, this);
AppContext() {
UseEffect((){
isOdd.value = count.value % 2 != 0;
}, [count], this);
}
}
NOTE: If you don't add
context
argument toUseEffect
, thecallback
don't execute on lifecyclewillMount
, and thecleanup
don't execute on lifecyclewillUnmount
.NOTE: If you want to execute a
UseEffect
immediately, useUseEffect.dispatchEffect
instead of thecontext
argument.
ReactterProvider
and UseContext
ReactterProvider
is a wrapper widget of a InheritedWidget
witch helps exposes the ReactterContext
that are defined using UseContext
on contexts
parameter.
class MyWidget extends StatelessWidget {
const MyWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ReactterProvider(
contexts: [
UseContext(
() => AppContext(),
),
UseContext(
() => ConfigContext(),
id: 'App',
onInit: (appConfigContext) {
appConfigContext.config.value = 'new state';
},
),
UseContext(
() => ConfigContext(),
id: 'User'
),
],
builder: (context, _) {
final appContext = context.watch<AppContext>();
final appConfigContext = context.watchId<ConfigContext>('App');
final userConfigContext = context.watchId<ConfigContext>('User');
return [...]
},
);
}
}
For more information about context.watch[...]
go to here.
RECOMMENDED: Don't use
ReactterContext
class with parameters to prevent conflicts. Instead of it, useonInit
method whichUseContext
exposes for access its instance and put the data you need.NOTE: You can use
id
parameter ofUseContext
for create a different instance of sameReactterContext
class.
ReactterBuilder
ReactterBuilder
create a scope where isolates the widget tree which will be re-rendering when all or some of the specified ReactterHook
dependencies on listenHooks
has changed.
ReactterProvider(
contexts: [
UseContext(() => AppContext()),
],
builder: (context, child) {
// This builder is render only one time.
// But if you use context.watch<T> or context.watchId<T> here,
// it forces re-render this builder together ReactterBuilder's builder.
final appContext = context.read<AppContext>();
return Column(
children: [
Text("stateA: ${appContext.stateA.value}"),
ReactterBuilder<AppContext>(
listenHooks: (appContext) => [appContext.stateB],
builder: (appContext, context, child){
// This builder is re-render when only stateB changes
return Text("stateB: ${appContext.stateB.value}");
},
),
ReactterBuilder<AppContext>(
listenHooks: (appContext) => [appContext.stateC],
builder: (appContext, context, child){
// This builder is re-render when only stateC changes
return Text("stateC: ${appContext.stateC.value}");
},
),
],
);
},
)
ReactterContext
Reactter provides additional methods to BuildContext
for access your ReactterContext
. These are:
context.watch<T>
: Get the ReactterContext
instance of the specified type and watch context's states or states defined on first parameter.final watchContext = context.watch<WatchContext>();
final watchHooksContext = context.watch<WatchHooksContext>(
(ctx) => [ctx.stateA, ctx.stateB],
);
context.watchId<T>
: Get the ReactterContext
instance of the specified type and id defined on first parameter and watch context's states or states defined on second parameter.final watchIdContext = context.watchId<WatchIdContext>('id');
final watchHooksIdContext = context.watchId<WatchHooksIdContext>(
'id',
(ctx) => [ctx.stateA, ctx.stateB],
);
context.read<T>
: Get the ReactterContext
instance of the specified type.final readContext = context.read<ReadContext>();
context.readId<T>
: Get the ReactterContext
instance of the specified type and id defined on first parameter.final readIdContext = context.readId<ReadIdContext>('id');
NOTE:
context.watch<T>
andcontext.watchId<T>
watch all or some of the specifiedReactterHook
dependencies and when it will change, re-render widgets in the scope ofReactterProvider
orReactterBuilder
.NOTE: These methods mentioned above uses
ReactterProvider.contextOf<T>
ReactterContext
ReactterContext
provides lifecycle methods that are invoked in different stages of the instance’s existence.
class AppContext extends ReactterContext {
AppContext() {
print('1. Initialized');
onWillMount(() => print('2. Before mount'));
onDidMount(() => print('3. Mounted'));
onWillUpdate(() => print('4. Before update'));
onDidUpdate(() => print('5. Updated'));
onWillUnmount(() => print('6. Before unmounted'));
}
}
onWillMount
: Will trigger before the ReactterContext
instance will mount in the tree by ReactterProvider
.onDidMount
: Will trigger after the ReactterContext
instance did mount in the tree by ReactterProvider
.onWillUpdate
: Will trigger before the ReactterContext
instance will update by any ReactterHook
.onDidUpdate
: Will trigger after the ReactterContext
instance did update by any ReactterHook
.onWillUnmount
: Will trigger before the ReactterContext
instance will unmount in the tree by ReactterProvider
.NOTE:
UseContext
hasonInit
parameter which is execute between constructor andonWillMount
, you can use to access to instance and putin data before mount.
ReactterComponent
ReactterComponent
is a StatelessWidget
class that wrap render
with ReactterProvider
and UseContext
.
class CounterComponent extends ReactterComponent<AppContext> {
const CounterComponent({Key? key}) : super(key: key);
@override
get builder => () => AppContext();
@override
get id => 'uniqueId';
@override
listenHooks(appContext) => [appContext.stateA];
@override
Widget render(appContext, context) {
return Text("StateA: ${appContext.stateA.value}");
}
}
UseAsyncState
hookUseAsyncState
is a hook with the same functionality as UseState
but providing a asyncValue
which it will be obtain when execute resolve
method.
This is a example:
class AppContext extends ReactterContext {
late final state = UseAsyncState<String?, Data>(null, _resolveState, this);
AppContext() {
_init();
}
Future<void> _init() async {
await state.resolve(Data(prop: true, prop2: "test"));
print("State resolved with: ${state.value}");
}
Future<String> _resolveState([Data arg]) async {
return await api.getState(arg.prop, arg.prop2);
}
}
NOTE: If you want send argument to
asyncValue
method, need to defined a type arg which its send fromresolve
method. Like example shown above, which type argument send isData
class.
UseAsyncState
provides when
method, which can be used for get a widget depending of it's state, like that:
ReactterProvider(
contexts: [
UseContext(() => AppContext()),
],
builder: (context, child) {
final appContext = context.watch<AppContext>();
return appContext.state.when(
standby: (value) => Text("Standby: " + value),
loading: () => const CircularProgressIndicator(),
done: (value) => Text(value),
error: (error) => const Text(
"Ha ocurrido un error al completar la solicitud",
style: TextStyle(color: Colors.red),
),
);
},
)
For create a custom hook, you should be create a class that extends of ReactterHook
.
This is a example:
class UseCount extends ReactterHook {
int _count = 0;
int get value => _count;
UseCount(int initial, [ReactterContext? context])
: _count = initial,
super(context);
void increment() => update(() => _count += 1);
void decrement() => update(() => _count -= 1);
}
RECOMMENDED: Name class with
Use
preffix, for easy locatily.NOTE:
ReactterHook
providesupdate
method which notify tocontext
that has changed.
and use it like that:
class AppContext extends ReactterContext {
late final count = UseCount(0, this);
AppContext() {
UseEffect(() {
Future.delayed(
const Duration(secounds: 1),
count.increment,
);
}, [count], this);
}
}
The reactter's hooks can be defined as static for access its as global way:
class Global {
static final flag = UseState(false);
static final count = UseCount(0);
// Create a class factory for run it as singleton way.
// This way, the initial logic can be executed.
static final Global _inst = Global._init();
factory Global() => _inst;
Global._init() {
UseEffect(
() async {
await Future.delayed(const Duration(seconds: 1));
doCount();
},
[count],
UseEffect.dispatchEffect,
);
}
static void doCount() {
if (count.value <= 0) {
flag.value = true;
}
if (count.value >= 10) {
flag.value = false;
}
flag.value ? count.increment() : count.decrement();
}
}
// It's need to instance it for can execute Global._init(This executes one time only).
final global = Global();
This is a example that how you could use it:
class AppContext extends ReactterContext {
late final isOdd = UseState(false, this);
AppContext() {
UseEffect((){
isOdd.value = Global.count.value % 2 != 0;
}, [Global.count], this);
}
}
NOTE: If you want to execute some logic when initialize the global class you need to use the class factory and then instance it to run as singleton way.
Run this command:
With Flutter:
$ flutter pub add reactter
This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get
):
dependencies:
reactter: ^2.3.3
Alternatively, your editor might support flutter pub get
. Check the docs for your editor to learn more.
Now in your Dart code, you can use:
import 'package:reactter/reactter.dart';
example/lib/main.dart
import 'package:example/examples/animation/animation_example.dart';
import 'package:flutter/material.dart';
import 'examples/api/api_example.dart';
import 'examples/counter/counter_example.dart';
import 'examples/shopping_cart/shopping_cart_example.dart';
import 'examples/todos/todos_example.dart';
import 'examples/tree/tree_example.dart';
Future<void> main() async {
runApp(const MyApp());
}
final items = <ListItem>[
ExampleItem(
"Counter",
"Increment and decrement counter",
[
"ReactterBuilder",
"ReactterContext",
"ReactterProvider",
"UseContext",
"UseState",
],
const CounterExample(),
),
ExampleItem(
"Todos",
"Add, remove todo to list, mark, unmark todo as completed and filter todo list",
[
"ReactterBuilder",
"ReactterComponent",
"ReactterContext",
"ReactterProvider",
"UseContext",
"UseState",
"UseEffect",
],
const TodosExamples(),
),
ExampleItem(
"Shopping Cart",
"Add, remove product to cart and checkout",
[
"ReactterBuilder",
"ReactterComponent",
"ReactterContext",
"ReactterProvider",
"UseContext",
"UseState",
"UseEffect",
],
const ShoppingCartExample(),
),
ExampleItem(
"Tree widget",
"Add, remove and hide child widget with counter.",
[
"ReactterBuilder",
"ReactterComponent",
"ReactterContext",
"ReactterProvider",
"UseContext",
"UseState",
],
const TreeExample(),
),
ExampleItem(
"Github Search",
"Search user or repository and show info about it.",
[
"ReactterBuilder",
"ReactterContext",
"ReactterComponent",
"ReactterProvider",
"UseContext",
"UseAsyncState",
"UseState",
],
const ApiExample(),
),
ExampleItem(
"Animate widget",
"Change size, shape and color.",
[
"ReactterBuilder",
"ReactterContext",
"ReactterHook",
"ReactterProvider",
"ReactterSubscribersManager",
"UseContext",
],
const AnimationExample(),
),
];
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: const Text("Reactter Examples"),
),
body: ListView.builder(
itemCount: items.length,
itemBuilder: (BuildContext context, int index) {
final item = items[index];
return ListTile(
onTap: item is HeadingItem
? null
: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => (item as ExampleItem).view,
),
),
contentPadding:
const EdgeInsets.symmetric(vertical: 8, horizontal: 16)
.copyWith(top: item is HeadingItem ? 16 : 8),
title: item.buildTitle(context),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
item.buildSubtitle(context),
const SizedBox(
height: 4,
),
item.buildTags(context),
],
),
);
},
),
),
);
}
}
/// The base class for the different types of items the list can contain.
abstract class ListItem {
/// The title line to show in a list item.
Widget buildTitle(BuildContext context);
/// The subtitle line, if any, to show in a list item.
Widget buildSubtitle(BuildContext context);
Widget buildTags(BuildContext context);
}
/// A ListItem that contains data to display a heading.
class HeadingItem implements ListItem {
final String heading;
HeadingItem(this.heading);
@override
Widget buildTitle(BuildContext context) {
return Text(
heading,
style: Theme.of(context).textTheme.headline5,
);
}
@override
Widget buildSubtitle(BuildContext context) => const SizedBox.shrink();
@override
Widget buildTags(BuildContext context) => const SizedBox.shrink();
}
/// A ListItem that contains data to display a message.
class ExampleItem implements ListItem {
final String sender;
final String body;
final List<String> tags;
final Widget view;
ExampleItem(this.sender, this.body, this.tags, this.view);
@override
Widget buildTitle(BuildContext context) => Text(sender);
@override
Widget buildSubtitle(BuildContext context) => Text(body);
@override
Widget buildTags(BuildContext context) {
tags.sort();
return Wrap(
direction: Axis.horizontal,
spacing: 4,
children: tags
.map<Widget>(
(tag) => Text(
tag,
style: Theme.of(context)
.textTheme
.caption!
.copyWith(color: Colors.blue),
),
)
.toList(),
);
}
}
We want keeping adding features for Reactter
, those are some we have in mind order by priority:
Reactter
easy for debugging.Contribute
If you want to contribute don't hesitate to create an issue or pull-request in Reactter repository.
You can:
Any idea is welcome!
Author: 2devs-team
Source Code: https://github.com/2devs-team/reactter
License: MIT license
1648013941
This is a sandbox project for demonstrating Reactive Streams support in Spring framework and Spring ecosystem.
I've also maintained a series of repos related to ReativeStreams and the latest Spring 5.
Read online: https://hantsy.github.io/spring-reactive-sample/
The following table list all sample codes related to the above posts.
name | description |
---|---|
vanilla | The initial application, includes basic spring-webflux feature, use a main class to start up the application |
vanilla-jetty | Same as vanilla, but use Jetty as target runtime |
vanilla-tomcat | Same as vanilla, but use Reactor Netty as target runtime |
vanilla-undertow | Same as vanilla, but use Undertow as target runtime |
java8 | Java 8 CompletableFuture and @Async example |
java9 | Same as vanilla, Java 9 Flow API support is not ready in Spring 5.0.0.REALESE, planned in 5.0.1, see issue SPR-16052 and the original discussion on stackoverflow |
rxjava3 | Same as vanilla, but use Rxjava3 instead of Reactor, since Spring 5.3.0 |
smallrye-mutiny | Same as vanilla, but use SmallRye Mutiny instead of Reactor, since Spring 5.3.10 |
war | Replace the manual bootstrap class in vanilla with Spring ApplicationInitializer , it can be packaged as a war file to be deployed into an external servlet container. |
routes | Use RouterFunction instead of controller in vanilla |
register-bean | Programmatic approach to register all beans in ApplicatonContext at the application bootstrap |
data-neo4j | Spring Data Neo4j reactive example |
data-mongo | Spring Data Mongo Reactive example |
data-mongo-pageable | Spring Data Mongo Reactive example with pagination support |
data-mongo-transaction | Spring Data Mongo Reactive example with Transaction support |
data-redis | Spring Data Redis Reactive example |
data-redis-message | Spring Data Redis Reactive Example with ReactiveRedisMessageListenerContainer |
data-cassandra | Spring Data Cassandra Reactive example |
data-couchbase | Spring Data Couchbase Reactive example |
security | Based on vanilla, add Spring Security Reactive support |
security-form | Same as security, login form example |
security-user-properties | Same as security, but use users.properties to store users |
security-method | Replace URI based configuration with method level constraints |
security-data-mongo | Based on data-mongo and security, replace with dummy users in hard codes with Mongo driven store |
multipart | Multipart request handling and file uploading |
multipart-data-mongo | Multipart and file uploading, but data in Mongo via Spring Data Mongo Reactive GridFsTemplate |
mvc-thymeleaf | Traditional web application, use Thymeleaf as template engine |
mvc-mustache | Traditional web application, use Mustache as template engine |
mvc-freemarker | Traditional web application, use freemarker as template engine |
sse | Server Send Event example |
websocket | WebSocket example |
web-filter | WebFilter example |
groovy | Written in groovy |
groovy-dsl | Groovy DSL bean definition example |
client | Example of WebClient to shake hands with backend reactive APIs |
kotlin | Written in kotlin |
kotlin-routes | Use kotlin functional approach to declare beans and bootstrap the application programmatically |
kotlin-dsl | Kotlin DSL bean definition example |
session | Spring Session Example |
session-header | Spring Session WebSessionIdResolver Example |
session-data-redis | Spring Data Redis based ReactiveSessionRepository Example |
session-data-mongo | Spring Data Mongo based ReactiveSessionRepository Example |
exception-handler | Exception Handler Example |
integration | Spring Integration Example |
integration-dsl | Spring Integration Java 8 DSL Example |
restdocs | Spring RestDocs Example |
name | description |
---|---|
boot-start | Spring Boot example, including 3 Maven profiles to switch to Jetty, Tomcat, Undertow as target runtime |
boot-start-routes | Simple RouterFunction example |
boot-mvc-thymeleaf | Same as mvc-thymeleaf, but based on Spring Boot |
boot-mvc-mustache | Same as mvc-mustache, but based on Spring Boot |
boot-mvc-freemarker | Same as mvc-freemarker, but based on Spring Boot |
boot-groovy | Written in Groovy |
boot-kotlin | Written in Kotlin |
boot-kotlin-dsl | Kotlin specific BeanDefinitionDSL Example |
boot-redis | Example of using ReactiveRedisConnection and RouterFunction |
boot-data-redis | Spring Data Redis Example |
boot-data-neo4j | Spring Data Neo4j example (Spring Boot 2.4) |
boot-neo4j | Spring Data Neo4j using ReactiveNeo4jOperations (Spring Boot 2.4) |
boot-neo4j-cypher | Spring Data Neo4j using ReacitveNeo4jClient (Spring Boot 2.4) |
boot-data-cassandra | Spring Data Cassandra Example |
boot-data-couchbase | Spring Data Couchbase Example |
boot-data-elasticsearch | Spring Data ElasticSearch Example |
boot-data-mongo | Spring Data Mongo Example(Repository, Auditing, testcontainers) |
boot-data-mongo-querydsl | Spring Data Mongo Example with QueryDSL support |
boot-data-mongo-gridfs | Spring Data Mongo Example with Gridfs support |
boot-data-mongo-tailable | Spring Data Mongo tailable document example |
boot-exception-handler | Global Exception Handler |
Some example codes are becoming deprecated as time goes by, eg. the SDN Rx project which was maintained by the Neo4j team is discontinued now, it is highly recommended to migrate to the official Spring Data Neo4j.
And Spring Data R2dbc 1.2 added a lot of breaking changes, so I created another Spring R2dbc Sample repository to introduce the new features.
Spring removed support of RxJava/RxJava2, and other projects, such as Spring Data will remove RxJava/RxJava2 support soon.
name | description |
---|---|
data-r2dbc | Spring Data R2dbc Example. (Deprecated, go to hantsy/spring-r2dbc-sample to update yourself) |
data-r2dbc-postgresql | Spring Data R2dbc Example, but use PostgreSQL instead(Deprecated) |
boot-r2dbc | Spring Data R2dbc example using DatabaseClient (Deprecated) |
boot-data-r2dbc | Spring Data R2dbc example(Deprecated) |
boot-data-r2dbc-auditing | @EnableR2dbcAuditing example(Deprecated) |
boot-data-r2dbc-postgresql | Same as boot-data-r2dbc, but use PostgresSQL instead(Deprecated) |
boot-data-r2dbc-mysql | Same as boot-data-r2dbc, but use MySQL instead(Deprecated) |
boot-data-r2dbc-mssql | Same as boot-data-r2dbc, but use MS SQL instead(Deprecated) |
boot-neo4j-rx | SDN Rx Example but use ReactiveNeo4jClient (Deprecated) |
boot-neo4j-rx-cypher | SDN Rx Example using Cypher queries(Deprecated) |
boot-data-neo4j-rx | SDN Rx Example(Deprecated) |
rxjava | Same as vanilla, but use Rxjava instead of Reactor |
rxjava-jdbc | Accessing database with rxjava-jdbc. NOTE: rxjava-jdbc is a wrapper of blocking Jdbc APIs |
rxjava2 | Same as vanilla, but use Rxjava2 instead of Reactor |
rxjava2-jdbc | Accessing database with rxjava2-jdbc. NOTE: rxjava2-jdbc is a wrapper of blocking Jdbc APIs |
Download Details:
Author: hantsy
Source Code: https://github.com/hantsy/spring-reactive-sample
License: GPL-3.0 License
1647231120
Reactive Forms
This is a model-driven approach to handling Forms inputs and validations, heavily inspired in Angular's Reactive Forms.
For help getting started with Flutter, view the online documentation, which offers tutorials, samples, guidance on mobile development, and a full API reference.
For using Reactive Forms in projects below Flutter 2.8.0 please use the version <= 10.7.0 of Reactive Forms.
For using Reactive Forms in projects below Flutter 2.2.0 please use the version <= 10.2.0 of Reactive Forms.
For using Reactive Forms in projects with Flutter 1.17.0 please use the version 7.6.3 of Reactive Forms.
Reactive Forms v8.x includes the intl package. If a version conflict is present, then you should use dependency_overrides to temporarily override all references to intl and set the one that better fits your needs.
Once you're familiar with Flutter you may install this package adding reactive_forms
to the dependencies list of the pubspec.yaml
file as follow:
dependencies:
flutter:
sdk: flutter
reactive_forms: ^11.0.1
Then run the command flutter packages get
on the console.
A form is composed by multiple fields or controls.
To declare a form with the fields name and email is as simple as:
final form = FormGroup({
'name': FormControl<String>(value: 'John Doe'),
'email': FormControl<String>(),
});
Notice in the example above that in the case of the name we have also set a default value, in the case of the email the default value is null.
Given the FormGroup:
final form = FormGroup({
'name': FormControl<String>(value: 'John Doe'),
'email': FormControl<String>(value: 'johndoe@email.com'),
});
You can get the value of a single FormControl as simple as:
String get name() => this.form.control('name').value;
But you can also get the complete Form data as follows:
print(form.value);
The previous code prints the following output:
{
"name": "John Doe",
"email": "johndoe@email.com"
}
FormGroup.value returns an instance of Map<String, dynamic> with each field and its value.
To set value to controls you can use two approaches:
// set value directly to the control
this.form.control('name').value = 'John';
// set value to controls by setting value to the form
this.form.value = {
'name': 'John',
'email': 'john@email.com',
};
You can add validators to a FormControl as follows:
final form = FormGroup({
'name': FormControl<String>(validators: [Validators.required]),
'email': FormControl<String>(validators: [
Validators.required,
Validators.email,
]),
});
If at least one FormControl is invalid then the FormGroup is invalid
There are common predefined validators, but you can implement custom validators too.
A custom FormControl validator is a function that receives the control to validate and returns a Map. If the value of the control is valid the function must returns null otherwise returns a Map with a key and custom information, in the previous example we just set true as custom information.
Let's implement a custom validator that validates a control's value must be true:
final form = FormGroup({
'acceptLicense': FormControl<bool>(
value: false,
validators: [_requiredTrue], // custom validator
),
});
/// Validates that control's value must be `true`
Map<String, dynamic> _requiredTrue(AbstractControl<dynamic> control) {
return control.isNotNull &&
control.value is bool &&
control.value == true
? null
: {'requiredTrue': true};
}
You can see the current implementation of predefined validators in the source code to see more examples.
Validator.pattern is a validator that comes with Reactive Forms. Validation using regular expressions have been always a very useful tool to solve validation requirements. Let's see how we can validate American Express card numbers:
American Express card numbers start with 34 or 37 and have 15 digits.
const americanExpressCardPattern = r'^3[47][0-9]{13}$';
final cardNumber = FormControl<String>(
validators: [Validators.pattern(americanExpressCardPattern)],
);
cardNumber.value = '395465465421'; // not a valid number
expect(cardNumber.valid, false);
expect(cardNumber.hasError('pattern'), true);
The above code is a Unit Test extracted from Reactive Forms tests.
If we print the value of FormControl.errors:
print(cardNumber.errors);
We will get a Map like this:
{
"pattern": {
"requiredPattern": "^3[47][0-9]{13}$",
"actualValue": 395465465421
}
}
There are special validators that can be attached to FormGroup. In the next section we will see an example of that.
There are some cases where we want to implement a Form where a validation of a field depends on the value of another field. For example a sign-up form with email and emailConfirmation or password and passwordConfirmation.
For that cases we could implement a custom validator and attach it to the FormGroup, let's see an example:
final form = FormGroup({
'name': FormControl<String>(validators: [Validators.required]),
'email': FormControl<String>(validators: [Validators.required, Validators.email]),
'password': FormControl<String>(validators: [
Validators.required,
Validators.minLength(8),
]),
'passwordConfirmation': FormControl<String>(),
}, validators: [
_mustMatch('password', 'passwordConfirmation')
]);
Notice the use of Validators.minLength(8)
In the previous code we have added two more fields to the form: password and passwordConfirmation, both fields are required and the password must be at least 8 characters length.
However the most important thing here is that we have attached a validator to the FormGroup. This validator is a custom validator and the implementation follows as:
ValidatorFunction _mustMatch(String controlName, String matchingControlName) {
return (AbstractControl<dynamic> control) {
final form = control as FormGroup;
final formControl = form.control(controlName);
final matchingFormControl = form.control(matchingControlName);
if (formControl.value != matchingFormControl.value) {
matchingFormControl.setErrors({'mustMatch': true});
// force messages to show up as soon as possible
matchingFormControl.markAsTouched();
} else {
matchingFormControl.removeError('mustMatch');
}
return null;
};
}
Fortunately you don't have to implement a custom must match validator because we have already included it into the code of the reactive_forms package so you should reuse it. The previous form definition becomes into:
final form = FormGroup({
'name': FormControl<String>(validators: [Validators.required]),
'email': FormControl<String>(validators: [Validators.required, Validators.email]),
'emailConfirmation': FormControl<String>(),
'password': FormControl<String>(validators: [Validators.required, Validators.minLength(8)]),
'passwordConfirmation': FormControl<String>(),
}, validators: [
Validators.mustMatch('email', 'emailConfirmation'),
Validators.mustMatch('password', 'passwordConfirmation'),
]);
Some times you want to perform a validation against a remote server, this operations are more time consuming and need to be done asynchronously.
For example you want to validate that the email the user is currently typing in a registration form is unique and is not already used in your application. Asynchronous Validators are just another tool so use it wisely.
Asynchronous Validators are very similar to their synchronous counterparts, with the following difference:
Asynchronous validation executes after the synchronous validation, and is performed only if the synchronous validation is successful. This check allows forms to avoid potentially expensive async validation processes (such as an HTTP request) if the more basic validation methods have already found invalid input.
After asynchronous validation begins, the form control enters a pending state. You can inspect the control's pending property and use it to give visual feedback about the ongoing validation operation.
Code speaks more than a thousand words :) so let's see an example.
Let's implement the previous mentioned example: the user is typing the email in a registration Form and you want to validate that the email is unique in your System. We will implement a custom async validator for that purpose.
final form = FormGroup({
'email': FormControl<String>(
validators: [
Validators.required, // traditional required and email validators
Validators.email,
],
asyncValidators: [_uniqueEmail], // custom asynchronous validator :)
),
});
We have declared a simple Form with an email field that is required and must have a valid email value, and we have include a custom async validator that will validate if the email is unique. Let's see the implementation of our new async validator:
/// just a simple array to simulate a database of emails in a server
const inUseEmails = ['johndoe@email.com', 'john@email.com'];
/// Async validator example that simulates a request to a server
/// and validates if the email of the user is unique.
Future<Map<String, dynamic>> _uniqueEmail(AbstractControl<dynamic> control) async {
final error = {'unique': false};
final emailAlreadyUsed = await Future.delayed(
Duration(seconds: 5), // a delay to simulate a time consuming operation
() => inUseEmails.contains(control.value),
);
if (emailAlreadyUsed) {
control.markAsTouched();
return error;
}
return null;
}
Note the use of control.markAsTouched() to force the validation message to show up as soon as possible.
The previous implementation was a simple function that receives the AbstractControl and returns a Future that completes 5 seconds after its call and performs a simple check: if the value of the control is contained in the server array of emails.
If you want to see Async Validators in action with a full example using widgets and animations to feedback the user we strong advice you to visit our Wiki. We have not included the full example in this README.md file just to simplify things here and to not anticipate things that we will see later in this doc.
Asynchronous validators have a debounce time that is useful if you want to minimize requests to a remote API. The debounce time is set in milliseconds and the default value is 250 milliseconds.
You can set a different debounce time as an optionally argument in the FormControl constructor.
final control = FormControl(
asyncValidators: [_uniqueEmail],
asyncValidatorsDebounceTime: 1000, // sets 1 second of debounce time.
);
To explain what Composing Validators is, let's see an example:
We want to validate a text field of an authentication form. In this text field the user can write an email or a phone number and we want to make sure that the information is correctly formatted. We must validate that input is a valid email or a valid phone number.
final phonePattern = '<some phone regex pattern>';
final form = FormGroup({
'user': FormControl<String>(
validators: [
Validators.composeOR([
Validators.email,
Validators.pattern(phonePattern),
])
],
),
});
Note that Validators.composeOR receives a collection of validators as argument and returns a validator.
With Validators.composeOR we are saying to FormControl that if at least one validator evaluate as VALID then the control is VALID it's not necessary that both validators evaluate to valid.
Another example could be to validate multiples Credit Card numbers. In that case you have several regular expression patterns for each type of credit card. So the user can introduce a card number and if the information match with at least one pattern then the information is considered as valid.
final form = FormGroup({
'cardNumber': FormControl<String>(
validators: [
Validators.composeOR([
Validators.pattern(americanExpressCardPattern),
Validators.pattern(masterCardPattern),
Validators.pattern(visaCardPattern),
])
],
),
});
FormGroup is not restricted to contains only FormControl, it can nest others FormGroup so you can create more complex Forms.
Supose you have a Registration Wizzard with several screens. Each screen collect specific information and at the end you want to collect all that information as one piece of data:
final form = FormGroup({
'personal': FormGroup({
'name': FormControl<String>(validators: [Validators.required]),
'email': FormControl<String>(validators: [Validators.required]),
}),
'phone': FormGroup({
'phoneNumber': FormControl<String>(validators: [Validators.required]),
'countryIso': FormControl<String>(validators: [Validators.required]),
}),
'address': FormGroup({
'street': FormControl<String>(validators: [Validators.required]),
'city': FormControl<String>(validators: [Validators.required]),
'zip': FormControl<String>(validators: [Validators.required]),
}),
});
Note how we have set the data type to a FormControl, although this is not mandatory when declaring a Form, we highly recommend this syntax as good practice or to use the FormBuilder syntax.
Using FormBuilder (read FormBuilder section below):
final form = fb.group({
'personal': fb.group({
'name': ['', Validators.required],
'email': ['', Validators.required],
}),
'phone': fb.group({
'phoneNumber': ['', Validators.required],
'countryIso': ['', Validators.required],
}),
'address': fb.group({
'street': ['', Validators.required],
'city': ['', Validators.required],
'zip': ['', Validators.required],
}),
});
You can collect all data using FormGroup.value:
void _printFormData(FormGroup form) {
print(form.value);
}
The previous method outputs a Map as the following one:
{
"personal": {
"name": "...",
"email": "..."
},
"phone": {
"phoneNumber": "...",
"countryIso": "..."
},
"address": {
"street": "...",
"city": "...",
"zip": "..."
}
}
And of course you can access to a nested FormGroup as following:
FormGroup personalForm = form.control('personal');
A simple way to create a wizard is for example to wrap a PageView within a ReactiveForm and each Page inside the PageView can contains a ReactiveForm to collect specific data.
FormArray is an alternative to FormGroup for managing any number of unnamed controls. As with FormGroup instances, you can dynamically insert and remove controls from FormArray instances, and the form array instance value and validation status is calculated from its child controls.
You don't need to define a key for each control by name, so this is a great option if you don't know the number of child values in advance.
Let's see a simple example:
final form = FormGroup({
'emails': FormArray<String>([]), // an empty array of emails
});
We have defined just an empty array. Let's define another array with two controls:
final form = FormGroup({
'emails': FormArray<String>([
FormControl<String>(value: 'john@email.com'),
FormControl<String>(value: 'susan@email.com'),
]),
});
Note that you don't have to specify the name of the controls inside of the array.
If we output the value of the previous form group we will get something like this:
print(form.value);
{
"emails": ["john@email.com", "susan@email.com"]
}
Let's dynamically add another control:
final array = form.control('emails') as FormArray<String>;
// adding another email
array.add(
FormControl<String>(value: 'caroline@email.com'),
);
print(form.value);
{
"emails": ["john@email.com", "susan@email.com", "caroline@email.com"]
}
Another way of add controls is to assign values directly to the array:
// Given: an empty array of strings
final array = FormArray<String>([]);
// When: set value to array
array.value = ["john@email.com", "susan@email.com", "caroline@email.com"];
// Then: the array is no longer empty
expect(array.controls.length, 3);
// And: array has a control for each inserted value
expect(array.controls('0').value, "john@email.com");
expect(array.controls('1').value, "susan@email.com");
expect(array.controls('2').value, "caroline@email.com");
To get a control from the array you must pass the index position as a String. This is because FormGroup and FormArray inherited from the same parent class and FormControl gets the controls by name (String).
A more advanced example:
// an array of contacts
final contacts = ['john@email.com', 'susan@email.com', 'caroline@email.com'];
// a form with a list of selected emails
final form = FormGroup({
'selectedEmails': FormArray<bool>([], // an empty array of controls
validators: [emptyAddressee], // validates that at least one email is selected
),
});
// get the array of controls
final formArray = form.control('selectedEmails') as FormArray<bool>;
// populates the array of controls.
// for each contact add a boolean form control to the array.
formArray.addAll(
contacts.map((email) => FormControl<bool>(value: true)).toList(),
);
// validates that at least one email is selected
Map<String, dynamic> emptyAddressee(AbstractControl control) {
final emails = (control as FormArray<bool>).value;
return emails.any((isSelected) => isSelected)
? null
: {'emptyAddressee': true};
}
You can also create arrays of groups:
// an array of groups
final addressArray = FormArray([
FormGroup({
'city': FormControl<String>(value: 'Sofia'),
'zipCode': FormControl<int>(value: 1000),
}),
FormGroup({
'city': FormControl<String>(value: 'Havana'),
'zipCode': FormControl<int>(value: 10400),
}),
]);
Another example using FormBuilder:
// an array of groups using FormBuilder
final addressArray = fb.array([
fb.group({'city': 'Sofia', 'zipCode': 1000}),
fb.group({'city': 'Havana', 'zipCode': 10400}),
]);
or just:
// an array of groups using a very simple syntax
final addressArray = fb.array([
{'city': 'Sofia', 'zipCode': 1000},
{'city': 'Havana', 'zipCode': 10400},
]);
You can iterate over groups as follow:
final cities = addressArray.controls
.map((control) => control as FormGroup)
.map((form) => form.control('city').value)
.toList();
A common mistake is to declare an array of groups as FormArray<FormGroup>.
An array of FormGroup must be declared as FormArray() or as FormArray<Map<String, dynamic>>().
The FormBuilder provides syntactic sugar that shortens creating instances of a FormGroup, FormArray and FormControl. It reduces the amount of boilerplate needed to build complex forms.
// creates a group
final form = fb.group({
'name': 'John Doe',
'email': ['', Validators.required, Validators.email],
'password': Validators.required,
});
The previous code is equivalent to the following one:
final form = FormGroup({
'name': FormControl<String>(value: 'John Doe'),
'email': FormControl<String>(value: '', validators: [Validators.required, Validators.email]),
'password': FormControl(validators: [Validators.required]),
});
// creates an array
final aliases = fb.array(['john', 'little john']);
// creates a control of type String with a required validator
final control = fb.control<String>('', [Validators.required]);
// create a group
final group = fb.group(
// creates a control with default value and disabled state
'name': fb.state(value: 'john', disabled: true),
);
To retrieves nested controls you can specify the name of the control as a dot-delimited string that define the path to the control:
final form = FormGroup({
'address': FormGroup({
'city': FormControl<String>(value: 'Sofia'),
'zipCode': FormControl<int>(value: 1000),
}),
});
// get nested control value
final city = form.control('address.city');
print(city.value); // outputs: Sofia
So far we have only defined our model-driven form, but how do we bind the form definition with our Flutter widgets? Reactive Forms Widgets is the answer ;)
Let's see an example:
@override
Widget build(BuildContext context) {
return ReactiveForm(
formGroup: this.form,
child: Column(
children: <Widget>[
ReactiveTextField(
formControlName: 'name',
),
ReactiveTextField(
formControlName: 'email',
),
ReactiveTextField(
formControlName: 'password',
obscureText: true,
),
],
),
);
}
The example above ignores the emailConfirmation and passwordConfirmation fields previously seen for simplicity.
@override
Widget build(BuildContext context) {
return ReactiveForm(
formGroup: this.form,
child: Column(
children: <Widget>[
ReactiveTextField(
formControlName: 'name',
validationMessages: (control) => {
'required': 'The name must not be empty'
},
),
ReactiveTextField(
formControlName: 'email',
validationMessages: (control) => {
'required': 'The email must not be empty',
'email': 'The email value must be a valid email'
},
),
ReactiveTextField(
formControlName: 'password',
obscureText: true,
validationMessages: (control) => {
'required': 'The password must not be empty',
'minLenght': 'The password must have at least 8 characters'
},
),
],
),
);
}
Reactive Forms have an utility class called ValidationMessage that brings access to common validation messages: required, email, pattern and so on. So instead of write 'required' you could use ValidationMessage.required as the key of validation messages:
return ReactiveTextField( formControlName: 'email', validationMessages: (control) => { ValidationMessage.required: 'The email must not be empty', ValidationMessage.email: 'The email value must be a valid email', }, ),
nice isn't it? ;)
Even when the FormControl is invalid, validation messages will begin to show up when the FormControl is touched. That means when the user taps on the ReactiveTextField widget and then remove focus or completes the text edition.
You can initialize a FormControl as touched to force the validation messages to show up at the very first time the widget builds.
final form = FormGroup({
'name': FormControl(
value: 'John Doe',
validators: [Validators.required],
touched: true,
),
});
When you set a value to a FormControl from code and want to show up validations messages you must call FormControl.markAsTouched() method:
set name(String newName) {
final formControl = this.form.control('name');
formControl.value = newName;
formControl.markAsTouched();// if newName is invalid then validation messages will show up in UI
}
To mark all children controls of a FormGroup and FormArray you must call markAllAsTouched().
final form = FormGroup({ 'name': FormControl( value: 'John Doe', validators: [Validators.required], touched: true, ), }); // marks all children as touched form.markAllAsTouched();
The second way to customize when to show error messages is to override the method showErrors in reactive widgets.
Let's suppose you want to show validation messages not only when it is invalid and touched (default behavior), but also when it's dirty:
ReactiveTextField(
formControlName: 'email',
// override default behavior and show errors when: INVALID, TOUCHED and DIRTY
showErrors: (control) => control.invalid && control.touched && control.dirty,
),
A control becomes dirty when its value change through the UI.
The method setErrors of the controls can optionally mark it as dirty too.
For a better User Experience some times we want to enable/disable the Submit button based on the validity of the Form. Getting this behavior, even in such a great framework as Flutter, some times can be hard and can lead to have individual implementations for each Form of the same application plus boilerplate code.
We will show you two different approaches to accomplish this very easily:
Let's add a submit button to our Form:
@override
Widget build(BuildContext context) {
return ReactiveForm(
formGroup: this.form,
child: Column(
children: <Widget>[
ReactiveTextField(
formControlName: 'email',
),
ReactiveTextField(
formControlName: 'password',
obscureText: true,
),
MySubmitButton(),
],
),
);
}
The above is a simple sign-in form with email, password, and a submit button.
Now let's see the implementation of the MySubmitButton widget:
class MySubmitButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final form = ReactiveForm.of(context);
return RaisedButton(
child: Text('Submit'),
onPressed: form.valid ? _onPressed : null,
);
}
void _onPressed() {
print('Hello Reactive Forms!!!');
}
}
Notice the use of ReactiveForm.of(context) to get access to the nearest FormGroup up the widget's tree.
In the previous example we have separated the implementation of the submit button in a different widget. The reasons behind this is that we want to re-build the submit button each time the validity of the FormGroup changes. We don't want to rebuild the entire Form, but just the button.
How is that possible? Well, the answer is in the expression:
final form = ReactiveForm.of(context);
The expression above have two important responsibilities:
ReactiveFormConsumer widget is a wrapped around the ReactiveForm.of(context) expression so that we can reimplement the previous example as follows:
@override
Widget build(BuildContext context) {
return ReactiveForm(
formGroup: this.form,
child: Column(
children: <Widget>[
ReactiveTextField(
formControlName: 'email',
),
ReactiveTextField(
formControlName: 'password',
obscureText: true,
),
ReactiveFormConsumer(
builder: (context, form, child) {
return RaisedButton(
child: Text('Submit'),
onPressed: form.valid ? _onSubmit : null,
);
},
),
],
),
);
}
void _onSubmit() {
print('Hello Reactive Forms!!!');
}
It is entirely up to you to decide which of the above two approaches to use, but note that to access the FormGroup via ReactiveForm.of(context) the consumer widget must always be down in the tree of the ReactiveForm widget.
There are some cases where we want to add or remove focus on a UI TextField without the interaction of the user. For that particular cases you can use FormControl.focus() or FormControl.unfocus() methods.
final form = fb.group({'name': 'John Doe'});
FormControl control = form.control('name');
control.focus(); // UI text field get focus and the device keyboard pop up
control.unfocus(); // UI text field lose focus
You can also set focus directly from the Form like:
final form = fb.group({'name': ''});
form.focus('name'); // UI text field get focus and the device keyboard pop up
final form = fb.group({
'person': fb.group({
'name': '',
}),
});
// set focus to a nested control
form.focus('person.name');
Another example is when you have a form with several text fields and each time the user completes edition in one field you want to request next focus field using the keyboard actions:
final form = fb.group({
'name': ['', Validators.required],
'email': ['', Validators.required, Validators.email],
'password': ['', Validators.required],
});
@override
Widget build(BuildContext context) {
return ReactiveForm(
formGroup: this.form,
child: Column(
children: <Widget>[
ReactiveTextField(
formControlName: 'name',
textInputAction: TextInputAction.next,
onSubmitted: () => this.form.focus('email'),
),
ReactiveTextField(
formControlName: 'email',
textInputAction: TextInputAction.next,
onSubmitted: () => this.form.focus('password'),
),
ReactiveTextField(
formControlName: 'password',
obscureText: true,
),
],
),
);
}
When you remove focus of a control, the control is marked as touched, that means that the validation error messages will show up in UI. To prevent validation messages to show up you can optionally set argument touched to false.
// remove the focus to the control and marks it as untouched. this.form.unfocus(touched: false);
To disabled a widget like ReactiveTextField all you need to do is to mark the control as disabled:
final form = FormGroup({
'name': FormControl(),
});
FormControl control = form.control('name');
// the control is disabled and also the widget in UI is disabled.
control.markAsDisabled();
When a control is disabled it is exempt from validation checks and excluded from the aggregate value of any parent. Its status is DISABLED.
To retrieves all values of a FormGroup or FormArray regardless of disabled status in children use FormControl.rawValue or FormArray.rawValue respectively.
ReactiveTextField has more in common with TextFormField that with TextField. As we all know TextFormField is a wrapper around the TextField widget that brings some extra capabilities such as Form validations with properties like autovalidate and validator. In the same way ReactiveTextField is a wrapper around TextField that handle the features of validations in a own different way.
ReactiveTextField has all the properties that you can find in a common TextField, it can be customizable as much as you want just as a simple TextField or a TextFormField. In fact must of the code was taken from the original TextFormField and ported to have a reactive behavior that binds itself to a FormControl in a two-way binding.
Below is an example of how to create some ReactiveTextField with some common properties:
@override
Widget build(BuildContext context) {
return ReactiveForm(
formGroup: this.form,
child: Column(
children: <Widget>[
ReactiveTextField(
formControlName: 'name',
decoration: InputDecoration(
labelText: 'Name',
),
textCapitalization: TextCapitalization.words,
textAlign: TextAlign.center,
style: TextStyle(backgroundColor: Colors.white),
),
ReactiveTextField(
formControlName: 'phoneNumber',
decoration: InputDecoration(
labelText: 'Phone number',
),
keyboardType: TextInputType.number,
),
ReactiveTextField(
formControlName: 'password',
obscureText: true,
decoration: InputDecoration(
labelText: 'Password',
),
),
],
),
);
}
Because of the two-binding capability of the ReactiveTextField with a FormControl the widget don't include properties as controller, validator, autovalidate, onSaved, onChanged, onEditingComplete, onFieldSubmitted, the FormControl is responsible for handling validation as well as changes notifications.
We are trying to keep reactive_forms
from bloating with third party dependencies this is why there is a separate library reactive_forms_widgets
which is under construction yet that provides a variety of more advanced field widgets. To know more about how to install it please visit the library repo and read the documentation about the widgets it contains.
flutter_advanced_switch
dropdown_search
file_picker
image_picker
multi_image_picker
CupertinoSegmentedControl
signature
flutter_touch_spin
RangeSlider
sleek_circular_slider
CupertinoTextField
flutter_rating_bar
macos_ui
pinput
CupertinoSwitch
pin_code_fields
CupertinoSlidingSegmentedControl
CupertinoSlider
flutter_colorpicker
month_picker_dialog
RawAutocomplete
flutter_typeahead
pin_input_text_field
direct_select
markdown_editable_textinput
code_text_field
phone_form_field
extended_text_field
CupertinoSlidingSegmentedControl
We have explain the common usage of a ReactiveTextField along this documentation.
ReactiveDropdownField as all the other reactive field widgets is almost the same as its native version DropdownButtonFormField but adding two-binding capabilities. The code is ported from the original native implementation. It have all the capability of styles and themes of the native version.
final form = FormGroup({
'payment': FormControl(validators: [Validators.required]),
});
@override
Widget build(BuildContext context) {
return ReactiveForm(
formGroup: this.form,
child: Column(
children: <Widget>[
ReactiveDropdownField<int>(
formControlName: 'payment',
hint: Text('Select payment...'),
items: [
DropdownMenuItem(
value: 0,
child: Text('Free'),
),
DropdownMenuItem(
value: 1,
child: Text('Visa'),
),
DropdownMenuItem(
value: 2,
child: Text('Mastercard'),
),
DropdownMenuItem(
value: 3,
child: Text('PayPal'),
),
],
),
],
),
);
}
As you can see from the above example the usage of ReactiveDropdownField is almost the same as the usage of a common DropdownButtonFormField, except for the additional formControlName and validationMessages properties.
If you want to rebuild a widget each time a FormControl value changes you could use the ReactiveValueListenableBuilder widget.
In the following example we are listening for changes in lightIntensity. We change that value with a ReactiveSlider and show all the time the value in a Text widget:
final form = FormGroup({
'lightIntensity': FormControl<double>(value: 50.0),
});
@override
Widget build(BuildContext context) {
return ReactiveForm(
formGroup: this.form,
child: Column(
children: <Widget>[
ReactiveValueListenableBuilder<double>(
formControlName: 'lightIntensity',
builder: (context, value, child) {
return Text('lights at ${value?.toStringAsFixed(2)}%');
},
),
ReactiveSlider(
formControlName: 'lightIntensity',
max: 100.0,
),
],
)
);
}
Both widgets are responsible for exposing the FormGroup to descendants widgets in the tree. Let see an example:
// using ReactiveForm
@override
Widget build(BuildContext context) {
return ReactiveForm(
formGroup: this.form,
child: ReactiveTextField(
formControlName: 'email',
),
);
}
// using ReactiveFormBuilder
@override
Widget build(BuildContext context) {
return ReactiveFormBuilder(
form: () => this.form,
builder: (context, form, child) {
return ReactiveTextField(
formControlName: 'email',
);
},
);
}
The main differences are that ReactiveForm is a StatelessWidget so it doesn't save the instance of the FormGroup. You must declare the instance of the FormGroup in a StatefulWidget or resolve it from some Provider (state management library).
// Using ReactiveForm in a StatelessWidget and resolve the FormGroup from a provider
class SignInForm extends StatelessWidget {
@override
Widget build(BuildContext context) {
final viewModel = Provider.of<SignInViewModel>(context, listen: false);
return ReactiveForm(
formGroup: viewModel.form,
child: ReactiveTextField(
formControlName: 'email',
),
);
}
}
// Using ReactiveForm in a StatefulWidget and declaring FormGroup in the state.
class SignInForm extends StatefulWidget {
@override
_SignInFormState createState() => _SignInFormState();
}
class _SignInFormState extends State<SignInForm> {
final form = fb.group({
'email': Validators.email,
});
@override
Widget build(BuildContext context) {
return ReactiveForm(
formGroup: this.form,
child: ReactiveTextField(
formControlName: 'email',
),
);
}
}
If you declare a FormGroup in a StatelessWidget the group will be destroyed a created each time the instance of the StatelessWidget is destroyed and created, so you must preserve the FormGroup in a state or in a Bloc/Provider/etc.
By the other hand ReactiveFormBuilder is implemented as a StatefulWidget so it holds the created FormGroup in its state. That way is safe to declares the FormGroup in a StatelessWidget or get it from a Bloc/Provider/etc.
class SignInForm extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ReactiveFormBuilder(
form: () => fb.group({'email': Validators.email}),
builder: (context, form, child) {
return ReactiveTextField(
formControlName: 'email',
);
},
);
}
}
You should use ReactiveForm if:
You should use ReactiveFormBuilder if:
But the final decision is really up to you, you can use any of them in any situations ;)
Although Reactive Forms can be used with any state management library or even without any one at all, Reactive Forms gets its maximum potential when is used in combination with a state management library like the Provider plugin.
This way you can separate UI logic from business logic and you can define the FormGroup inside a business logic class and then exposes that class to widgets with mechanism like the one Provider plugin brings.
Reactive Forms is not limited just to common widgets in Forms like text, dropdowns, sliders switch fields and etc, you can easily create custom widgets that two-way binds to FormControls and create your own set of Reactive Widgets ;)
In our Wiki you can find a tutorial of how to create your custom Reactive Widget.
You can also check Star Rating with Flutter Reactive Forms post as another example of a custom reactive widget.
Reactive Forms is not a fancy widgets package. It is not a library that brings some new Widgets with new shapes, colors or animations. It lets you to decide the shapes, colors, and animations you want for your widgets, but frees you from the responsibility of gathering and validating the data. And keeps the data in sync between your model and your widgets.
Reactive Forms does not pretend to replace the native widgets that you commonly use in your Flutter projects like TextFormField, DropdownButtonFormField or CheckboxListTile. Instead of that it brings new two-way binding capabilities and much more features to those same widgets.
Visit Migration Guide to see more details about different version breaking changes.
Author: Joanpablo
Source Code: https://github.com/joanpablo/reactive_forms
License: MIT License
1646751672
With Vue 3 now being the official version of Vue, I thought we would look at one of the few significant changes with the addition of the composition API, which is creating data and making it reactive
Timestamps:
0:00 Introduction to vue data
1:15 Creating data
2:15 Update data
3:30 Make data reactive with ref()
7:30 Make data reactive with reactive()
9:45 Limitations of using reactive()
12:10 Conclusion, what should you use?