Rupert  Beatty

Rupert Beatty

1673629500

Simple, Yet Unopinionated Web Application Server Written in Swift

Being perfectionists, we took the best from what we think is the best: power of Play Framework and simplicity of Express.js

Express is an asynchronous, simple, powerful, yet unopinionated web application server written in Swift

Getting started

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

Installation

OS X OS X

First install the following components (if you have not yet):

  • XCode 7.2 or higher
  • Homebrew the latest available version
  • Command Line tools: run xcode-select --install in terminal

Run the following in terminal:

brew tap crossroadlabs/tap
brew install swift-express

Linux Linux

For instructions on how to get Express installed on Linux, please, refer to the installation section in the ducumentation.

Examples

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.

Hello Express:

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

Synchronous vs Asynchronous

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
}

URL params

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"])
}

Serving static files

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.

Serving JSON requests

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"
}

Using template engine

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

If you want more, please, visit our documentation page

Ideology behind

Taking the best of Swift

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!

Features

And heah, the most important feature: Highly passionate development team

Roadmap

  • v0.4: stable version with Swift 3.0
  • v0.5: proper streaming
  • v0.6: new core (based on Reactive Swift)
  • v0.7: more content types available out of the box
  • v0.8: Web Sockets
  • v0.9: hot code reload
  • v1.0: hit the production!

Changelog

v0.4: Swift 3.0

v0.3: linux support

v0.2.1: minor changes

  • Swift modules are installed via Carthage
  • Enabled binary builds on OS X

v0.2: Solid OS X release

  • Much better routing APIs
  • Advanced routing path patterns
  • Possibility to use Regex for routing
  • Greately improved README
  • Some bugfixes

v0.1: Initial Public Release

  • basic routing
  • views and view engines (supports Mustache)
  • JSON rendering as a view
  • query parsing
  • static files serving

Contributing

To get started, sign the Contributor License Agreement.

Crossroad Labs by Crossroad Labs


 Documentation 

    <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>
UPDATE March 2017: Swift Express is back on track and is Swift 3.x compatible. From now on we will suport the latest stable swift builds only. There is no release of the newest version done yet, though you can enjoy it from the "master" branch. Stay tuned.
Version 0.3.x (current stable) notice: Current version works with Xcode 7.2, 7.3 and Linux DEV SNAPSHOT released on 03.01.2016. Upcoming version (0.4.x) will fully support Swift 3.0 and will maintain compatibility with Swift 2.2 (Xcode 7.3). Stay tuned by following us on social networks.

Download Details:

Author: Crossroadlabs
Source Code: https://github.com/crossroadlabs/Express 
License: GPL-3.0, LGPL-3.0 licenses found

#swift #linux #reactive #server 

Simple, Yet Unopinionated Web Application Server Written in Swift

How to Manage Complex Reactive forms in Angular

tldr;

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 FormGroups and FormArrays. In this article, we'll take a look at how this can help us manage a complex form.

What's the Problem?

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.

The Form Setup

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.

Conclusion

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 ControlValueAccessors, 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

#angular #reactive 

How to Manage Complex Reactive forms in Angular

Watermill: Building Event-driven Applications The Easy Way in Go

Watermill

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.

Goals

  • Easy to understand.
  • Universal - event-driven architecture, messaging, stream processing, CQRS - use it for whatever you need.
  • Fast (see Benchmarks).
  • Flexible with middlewares, plugins and Pub/Sub configurations.
  • Resilient - using proven technologies and passing stress tests (see Stability).

Getting Started

Pick what you like the best or see in order:

  1. Follow the Getting Started guide.
  2. See examples below.
  3. Read the full documentation: https://watermill.io/

Examples

Background

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.

Pub/Subs

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:

All Pub/Subs implementation documentation can be found in the documentation.

Contributing

Please check our contributing guide.

Stability

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.

Benchmarks

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/SubPublish (messages / s)Subscribe (messages / s)
Kafka (one node)63,506110,811
Kafka (5 nodes)70,252117,529
NATS76,20838,169
SQL (MySQL)7,299154
SQL (PostgreSQL)4,14298
Google Cloud Pub/Sub7,41639,591
AMQP2,40810,608
GoChannel272,938101,371

Support

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.

Why the name?

It processes streams!

Download Details:

Author: ThreeDotsLabs
Source Code: https://github.com/ThreeDotsLabs/watermill 
License: MIT license

#go #golang #events #reactive #kafka 

Watermill: Building Event-driven Applications The Easy Way in Go
Rupert  Beatty

Rupert Beatty

1665721320

RxSwift: Reactive Programming in Swift

RxSwift

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.

RxSwift Observable Example of a price constantly changing and updating the app's UI

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.

I came here because I want to ...

... understand

... 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: The core of RxSwift, providing the Rx standard as (mostly) defined by ReactiveX. It has no other dependencies.
  • RxCocoa: Provides Cocoa-specific capabilities for general iOS/macOS/watchOS & tvOS app development, such as Shared Sequences, Traits, and much more. It depends on both RxSwift and RxRelay.
  • RxRelay: Provides PublishRelay, BehaviorRelay and ReplayRelay, three simple wrappers around Subjects. It depends on RxSwift.
  • RxTest and RxBlocking: Provides testing capabilities for Rx-based systems. It depends on RxSwift.

Usage

Here's an exampleIn 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)

Requirements

  • Xcode 12.x
  • Swift 5.x

For Xcode 11 and below, use RxSwift 5.x.

Installation

RxSwift doesn't contain any external dependencies.

These are currently the supported installation options:

Manual

Open Rx.xcworkspace, choose RxExample and hit run. This method will build everything and run the sample app

CocoaPods

# 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

XCFrameworks

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 importing RxCocoa.

XCFrameworks instructions

Carthage

Add this to Cartfile

github "ReactiveX/RxSwift" "6.5.0"
$ carthage update

Carthage as a Static Library

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

Swift Package Manager

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

Manually using git submodules

  • Add RxSwift as a submodule
$ git submodule add git@github.com:ReactiveX/RxSwift.git
  • Drag Rx.xcodeproj into Project Navigator
  • Go to Project > Targets > Build Phases > Link Binary With Libraries, click + and select RxSwift, RxCocoa and RxRelay targets

References

Download Details:

Author: ReactiveX
Source Code: https://github.com/ReactiveX/RxSwift 
License: MIT license

#swift #ios #reactive 

RxSwift: Reactive Programming in Swift
Mike  Kozey

Mike Kozey

1661424300

Reactive_forms: A Model-driven Approach to Handling form inputs

Reactive Forms

This is a model-driven approach to handling Forms inputs and validations, heavily inspired in Angular's Reactive Forms.   

Getting Started

For help getting started with Flutter, view the online documentation, which offers tutorials, samples, guidance on mobile development, and a full API reference.

Minimum Requirements

  • Dart SDK: >= 2.17.0 <3.0.0
  • Flutter: >= 3.0.0

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.

Installation and Usage

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.

Creating a form

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>(),
});

Default Values

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.

How to get/set Form data

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',
};

What about Validators?

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.

Predefined validators

FormControl

  • Validators.required
  • Validators.requiredTrue
  • Validators.email
  • Validators.number
  • Validators.min
  • Validators.max
  • Validators.minLength
  • Validators.maxLength
  • Validators.pattern
  • Validators.creditCard
  • Validators.equals
  • Validators.compose
  • Validators.composeOR
  • Validators.any
  • Validators.contains

FormGroup

  • Validators.mustMatch
  • Validators.compare

FormArray

  • Validators.minLength
  • Validators.maxLength
  • Validators.any
  • Validators.contains

Custom Validators

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.

Pattern Validator

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
  }
}

FormGroup validators

There are special validators that can be attached to FormGroup. In the next section we will see an example of that.

What about Password and Password Confirmation?

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'),
]);

Asynchronous Validators :sunglasses:

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:

  • The validator function returns a Future

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.

Debounce time in async validators

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.
);

Composing Validators

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),
      ])
    ],
  ),
});

Groups of Groups :grin:

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.

Dynamic forms with FormArray

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};
}

Arrays of Groups

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>>().

FormBuilder

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.

Groups

// 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]),
});

Arrays

// creates an array
final aliases = fb.array(['john', 'little john']);

Control

// creates a control of type String with a required validator
final control = fb.control<String>('', [Validators.required]);

Control state

// create a group
final group = fb.group(
  // creates a control with default value and disabled state
  'name': fb.state(value: 'john', disabled: true),
);

Nested Controls

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

Reactive Form Widgets

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.

How to customize error messages?

Validation messages can be defined at two different levels:

  1. Reactive Widget level.
  2. Global/Application level.

1. Reactive Widget level.

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? ;)

2. Global/Application level.

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:

Validation messages with error arguments:

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!'),
          ),
        ),
      ),
    );
  }
}

Parameterized validation messages

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

When does Validation Messages begin to show up?

Touching a control

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();

Overriding Reactive Widgets show errors behavior

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.

Enable/Disable Submit button

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:

  1. Separating Submit Button in a different Widget.
  2. Using ReactiveFormConsumer widget.

Separating Submit Button in a different Widget:

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:

  • Obtains the nearest FormGroup up the widget's tree.
  • Registers the current context with the changes in the FormGroup so that if the validity of the FormGroup changes then the current context is rebuilt.

Using ReactiveFormConsumer widget:

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.

Focus/UnFocus a FormControl

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');

Focus flow between Text Fields

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);

How Enable/Disable a widget

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.

How does ReactiveTextField differs from native TextFormField or TextField?

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.

Supported Reactive Form Field Widgets

  • ReactiveTextField
  • ReactiveDropdownField
  • ReactiveSwitch
  • ReactiveCheckbox
  • ReactiveRadio
  • ReactiveSlider
  • ReactiveCheckboxListTile
  • ReactiveSwitchListTile
  • ReactiveRadioListTile

Bonus Field Widgets

  • ReactiveDatePicker
  • ReactiveTimePicker

Other Reactive Forms Widgets

  • ReactiveForm
  • ReactiveFormConsumer
  • ReactiveFormBuilder
  • ReactiveFormArray
  • ReactiveValueListenableBuilder
  • ReactiveStatusListenableBuilder

Advanced Reactive Field Widgets

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.

ReactiveTextField

We have explain the common usage of a ReactiveTextField along this documentation.

ReactiveDropdownField

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.

ReactiveValueListenableBuilder to listen when value changes in a FormControl

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,
        ),
      ],
    )
  );
}

ReactiveForm vs ReactiveFormBuilder which one?

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:

  • The form is complex enough.
  • You need to listen for changes in some child control to execute some business logic.
  • You are using some State Management library like Provider or Bloc.
  • Using a StatefulWidget to declare a very simple form is something that really doesn't bother you.

You should use ReactiveFormBuilder if:

  • The form is quite simple enough and doesn't need a separate Provider/Bloc state.
  • You don't want to use a StatefulWidget to declare the FormGroup.

But the final decision is really up to you, you can use any of them in any situations ;)

Reactive Forms + Provider plugin :muscle:

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 + code generation πŸ€–

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.

How create a custom Reactive Widget?

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.

What is not Reactive Forms

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.

What is Reactive Forms

  • Reactive Forms provides a model-driven approach to handling form inputs whose values change over time. It's heavily inspired in Angular Reactive Form.
  • It lets you focus on business logic and save you time from collect, validate and mantain synchronization between your models and widgets.
  • Remove boilerplate code and brings you the posibility to write clean code defining a separation between model and UI with minimal efforts.
  • And it integrates perfectly well with common state management libraries like Provider, Bloc and many others good libraries the community has created.

Migrate versions

Visit Migration Guide to see more details about different version breaking changes.

Use this package as a library

Depend on it

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.

Import it

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,
  ),
);

Download Details:

Author: joanpablo
Source Code: https://github.com/joanpablo/reactive_forms 
License: MIT license

#flutter #dart #reactive #forms 

Reactive_forms: A Model-driven Approach to Handling form inputs
Nat  Grady

Nat Grady

1661303760

Yonder: A Reactive Web Framework Built on Shiny

yonder

An opinionated shiny framework.

Introduction

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!

Examples

For examples of inputs and elements built using yonder please check out the documentation, https://nteetor.github.io/yonder/.

Installation

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")

Related work

Download Details:

Author: nteetor
Source Code: https://github.com/nteetor/yonder 
License: View license

#r #framework #reactive 

Yonder: A Reactive Web Framework Built on Shiny
Nat  Grady

Nat Grady

1660066860

Shiny: Easy interactive Web Applications with R

shiny

Easily build rich and productive interactive web apps in R β€” no HTML/CSS/JavaScript required.

Features

  • An intuitive and extensible reactive programming model which makes it easy to transform existing R code into a "live app" where outputs automatically react to new user input.
    • Compared to event-based programming, reactivity allows Shiny to do the minimum amount of work when input(s) change, and allows humans to more easily reason about complex MVC logic.
  • A prebuilt set of highly sophisticated, customizable, and easy-to-use widgets (e.g., plots, tables, sliders, dropdowns, date pickers, and more).
  • An attractive default look based on Bootstrap which can also be easily customized with the bslib package or avoided entirely with more direct R bindings to HTML/CSS/JavaScript.
  • Seamless integration with R Markdown, making it easy to embed numerous applications natively within a larger dynamic document.
  • Tools for improving and monitoring performance, including native support for async programming, caching, load testing, and more.
  • Modules: a framework for reducing code duplication and complexity.
  • An ability to bookmark application state and/or generate code to reproduce output(s).
  • A rich ecosystem of extension packages for more custom widgets, input validation, unit testing, and more.

Installation

To install the stable version from CRAN:

install.packages("shiny")

Getting Started

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.

Getting Help

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.

Contributing

We welcome contributions to the shiny package. Please see our CONTRIBUTING.md file for detailed guidelines of how to contribute.

R version support

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.

Download Details:

Author: rstudio
Source Code: https://github.com/rstudio/shiny 
License: View license

#r #reactive #webdevelopment 

Shiny: Easy interactive Web Applications with R
Rocio  O'Keefe

Rocio O'Keefe

1659786060

Flutter_mobx: Flutter integration for MobX

flutter_mobx

Flutter integration with MobX.dart.

Provides the Observer widget that listens to observables and automatically rebuilds on changes.

Example

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

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.

Installing

Use this package as a library

Depend on it

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.

Import it

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

#flutter #dart #reactive 

Flutter_mobx: Flutter integration for MobX

Callbag-gamepads: Callbag Source for Connected Gamepad inputs

Callbag-gamepads

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
  });

Verified to work with

Learn more

Author: aaronshaf
Source Code: https://github.com/aaronshaf/callbag-gamepads 
License: MIT license

#javascript #game #reactive 

Callbag-gamepads: Callbag Source for Connected Gamepad inputs

Callbag-create: Creates A New Callbag Given an Optional Producer

callbag-create

Creates a new Callbag given an optional producer that dictates how to emit values and complete the Callbag.

npm install callbag-create

Examples

With a Producer

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

With a Producer returning a clean-up logic

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

With a Noop Producer

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
  })
);

Without a Producer

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

#javascript #create #reactive 

Callbag-create: Creates A New Callbag Given an Optional Producer

Callbag-basics: Tiny and Fast Reactive/iterable Programming Library

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:

  • Supports reactive stream programming
  • Supports iterable programming (also!)
  • Same operator works for both of the above
  • Tiny! Weighs just 7kB
  • Fast! Faster than xstream and RxJS
  • Extensible: no core library! Everything is a utility function

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.

Usage

npm install callbag-basics

Import operators and factories:

const {forEach, fromIter, map, filter, pipe} = require('callbag-basics');

Try it online

Reactive programming examples

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

Iterable programming examples

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

API

The list below shows what's included.

Source factories

Sink factories

Transformation operators

Filtering operators

Combination operators

Utilities

More

Terminology

  • source: a callbag that delivers data
  • sink: a callbag that receives data
  • puller sink: a sink that actively requests data from the source
  • pullable source: a source that delivers data only on demand (on receiving a request)
  • listener sink: a sink that passively receives data from the source
  • listenable source: source which sends data to the sink without waiting for requests
  • operator: a callbag based on another callbag which applies some operation

Contributing

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

#javascript #reactive #observability 

Callbag-basics: Tiny and Fast Reactive/iterable Programming Library
Hunter  Krajcik

Hunter Krajcik

1656711660

Reactter: A Light, Powerful and Reactive State Management

A light, powerful and reactive state management.

By using Reactter you get:

  • Reduce significantly boilerplate code.
  • Improve code readability.
  • Unidirectional data flow.
  • Control re-render widget tree.
  • Reuse state using custom hooks.

Quickstart

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';

Usage

Create a 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.

Using UseState hook

UseState is a hook that allow to manage a state.

INFO: The different with other management state is that not use Stream. We know that Stream 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 with context argument, not need to add it on listenHooks, but is required declarate it as late.

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 another Object.

NOTE: If you want to force notify, execute update method which UseState exposes it.

Using UseEffect hook

UseEffect 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 to UseEffect, the callback don't execute on lifecycle willMount, and the cleanup don't execute on lifecycle willUnmount.

NOTE: If you want to execute a UseEffect immediately, use UseEffect.dispatchEffect instead of the context argument.

Wrap with 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, use onInit method which UseContext exposes for access its instance and put the data you need.

NOTE: You can use id parameter of UseContext for create a different instance of same ReactterContext class.

Control re-render with 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}");
          },
        ),
      ],
    );
  },
)

Access to 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],
);
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> and context.watchId<T> watch all or some of the specified ReactterHook dependencies and when it will change, re-render widgets in the scope of ReactterProvider or ReactterBuilder.

NOTE: These methods mentioned above uses ReactterProvider.contextOf<T>

Lifecycle of 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'));
  }
}
  1. Initialized: Class's constructor is the first one that is executed after the instance has been created.
  2. onWillMount: Will trigger before the ReactterContext instance will mount in the tree by ReactterProvider.
  3. onDidMount: Will trigger after the ReactterContext instance did mount in the tree by ReactterProvider.
  4. onWillUpdate: Will trigger before the ReactterContext instance will update by any ReactterHook.
  5. onDidUpdate: Will trigger after the ReactterContext instance did update by any ReactterHook.
  6. onWillUnmount: Will trigger before the ReactterContext instance will unmount in the tree by ReactterProvider.

NOTE: UseContext has onInit parameter which is execute between constructor and onWillMount, you can use to access to instance and putin data before mount.

Create a 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}");
  }
}

Using UseAsyncState hook

UseAsyncState 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 from resolve method. Like example shown above, which type argument send is Data 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),
      ),
    );
  },
)

Create a custom hook

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 provides update method which notify to context 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);
  }
}

Global state

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.

Installing

Use this package as a library

Depend on it

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.

Import it

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(),
    );
  }
}

Resources

Roadmap

We want keeping adding features for Reactter, those are some we have in mind order by priority:

V3

  • Async context.
  • Make Reactter easy for debugging.
  • Structure proposal for large projects.
  • Improve performance and do benchmark.

Contribute

If you want to contribute don't hesitate to create an issue or pull-request in Reactter repository.

You can:

  • Add a new custom hook.
  • Add a new widget.
  • Add examples.
  • Provide new features.
  • Report bugs.
  • Report situations difficult to implement.
  • Report an unclear error.
  • Report unclear documentation.
  • Write articles or make videos teaching how to use Reactter.

Any idea is welcome!

Author: 2devs-team
Source Code: https://github.com/2devs-team/reactter 
License: MIT license

#flutter #dart  #hooks #reactive 

Reactter: A Light, Powerful and Reactive State Management

Spring 5 Reactive Playground Sample

Spring Reactive Sample

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.

Docs

Read online: https://hantsy.github.io/spring-reactive-sample/

Sample Codes

The following table list all sample codes related to the above posts.

Spring Samples

namedescription
vanillaThe initial application, includes basic spring-webflux feature, use a main class to start up the application
vanilla-jettySame as vanilla, but use Jetty as target runtime
vanilla-tomcatSame as vanilla, but use Reactor Netty as target runtime
vanilla-undertowSame as vanilla, but use Undertow as target runtime
java8Java 8 CompletableFuture and @Async example
java9Same 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
rxjava3Same as vanilla, but use Rxjava3 instead of Reactor, since Spring 5.3.0
smallrye-mutinySame as vanilla, but use SmallRye Mutiny instead of Reactor, since Spring 5.3.10
warReplace 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.
routesUse RouterFunction instead of controller in vanilla
register-beanProgrammatic approach to register all beans in ApplicatonContext at the application bootstrap
data-neo4jSpring Data Neo4j reactive example
data-mongoSpring Data Mongo Reactive example
data-mongo-pageableSpring Data Mongo Reactive example with pagination support
data-mongo-transactionSpring Data Mongo Reactive example with Transaction support
data-redisSpring Data Redis Reactive example
data-redis-messageSpring Data Redis Reactive Example with ReactiveRedisMessageListenerContainer
data-cassandraSpring Data Cassandra Reactive example
data-couchbaseSpring Data Couchbase Reactive example
securityBased on vanilla, add Spring Security Reactive support
security-formSame as security, login form example
security-user-propertiesSame as security, but use users.properties to store users
security-methodReplace URI based configuration with method level constraints
security-data-mongoBased on data-mongo and security, replace with dummy users in hard codes with Mongo driven store
multipartMultipart request handling and file uploading
multipart-data-mongoMultipart and file uploading, but data in Mongo via Spring Data Mongo Reactive GridFsTemplate
mvc-thymeleafTraditional web application, use Thymeleaf as template engine
mvc-mustacheTraditional web application, use Mustache as template engine
mvc-freemarkerTraditional web application, use freemarker as template engine
sseServer Send Event example
websocketWebSocket example
web-filterWebFilter example
groovyWritten in groovy
groovy-dslGroovy DSL bean definition example
clientExample of WebClient to shake hands with backend reactive APIs
kotlinWritten in kotlin
kotlin-routesUse kotlin functional approach to declare beans and bootstrap the application programmatically
kotlin-dslKotlin DSL bean definition example
sessionSpring Session Example
session-headerSpring Session WebSessionIdResolver Example
session-data-redisSpring Data Redis based ReactiveSessionRepository Example
session-data-mongoSpring Data Mongo based ReactiveSessionRepository Example
exception-handlerException Handler Example
integrationSpring Integration Example
integration-dslSpring Integration Java 8 DSL Example
restdocsSpring RestDocs Example

Spring Boot Samples

namedescription
boot-startSpring Boot example, including 3 Maven profiles to switch to Jetty, Tomcat, Undertow as target runtime
boot-start-routesSimple RouterFunction example
boot-mvc-thymeleafSame as mvc-thymeleaf, but based on Spring Boot
boot-mvc-mustacheSame as mvc-mustache, but based on Spring Boot
boot-mvc-freemarkerSame as mvc-freemarker, but based on Spring Boot
boot-groovyWritten in Groovy
boot-kotlinWritten in Kotlin
boot-kotlin-dslKotlin specific BeanDefinitionDSL Example
boot-redisExample of using ReactiveRedisConnection and RouterFunction
boot-data-redisSpring Data Redis Example
boot-data-neo4jSpring Data Neo4j example (Spring Boot 2.4)
boot-neo4jSpring Data Neo4j using ReactiveNeo4jOperations (Spring Boot 2.4)
boot-neo4j-cypherSpring Data Neo4j using ReacitveNeo4jClient (Spring Boot 2.4)
boot-data-cassandraSpring Data Cassandra Example
boot-data-couchbaseSpring Data Couchbase Example
boot-data-elasticsearchSpring Data ElasticSearch Example
boot-data-mongoSpring Data Mongo Example(Repository, Auditing, testcontainers)
boot-data-mongo-querydslSpring Data Mongo Example with QueryDSL support
boot-data-mongo-gridfsSpring Data Mongo Example with Gridfs support
boot-data-mongo-tailableSpring Data Mongo tailable document example
boot-exception-handlerGlobal Exception Handler

Legacy Codes

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.

namedescription
data-r2dbcSpring Data R2dbc Example. (Deprecated, go to hantsy/spring-r2dbc-sample to update yourself)
data-r2dbc-postgresqlSpring Data R2dbc Example, but use PostgreSQL instead(Deprecated)
boot-r2dbcSpring Data R2dbc example using DatabaseClient(Deprecated)
boot-data-r2dbcSpring Data R2dbc example(Deprecated)
boot-data-r2dbc-auditing@EnableR2dbcAuditing example(Deprecated)
boot-data-r2dbc-postgresqlSame as boot-data-r2dbc, but use PostgresSQL instead(Deprecated)
boot-data-r2dbc-mysqlSame as boot-data-r2dbc, but use MySQL instead(Deprecated)
boot-data-r2dbc-mssqlSame as boot-data-r2dbc, but use MS SQL instead(Deprecated)
boot-neo4j-rxSDN Rx Example but use ReactiveNeo4jClient(Deprecated)
boot-neo4j-rx-cypherSDN Rx Example using Cypher queries(Deprecated)
boot-data-neo4j-rxSDN Rx Example(Deprecated)
rxjavaSame as vanilla, but use Rxjava instead of Reactor
rxjava-jdbcAccessing database with rxjava-jdbc. NOTE: rxjava-jdbc is a wrapper of blocking Jdbc APIs
rxjava2Same as vanilla, but use Rxjava2 instead of Reactor
rxjava2-jdbcAccessing database with rxjava2-jdbc. NOTE: rxjava2-jdbc is a wrapper of blocking Jdbc APIs

References

Download Details:
Author: hantsy
Source Code: https://github.com/hantsy/spring-reactive-sample
License: GPL-3.0 License

#spring  #spring-boot  #java #reactive 

Spring 5 Reactive Playground Sample
Gordon  Murray

Gordon Murray

1647231120

Reactive forms: Model-driven Approach to Handling Forms inputs

Reactive Forms

This is a model-driven approach to handling Forms inputs and validations, heavily inspired in Angular's Reactive Forms.

Getting Started

For help getting started with Flutter, view the online documentation, which offers tutorials, samples, guidance on mobile development, and a full API reference.

Minimum Requirements

  • Dart SDK: >=2.12.0 <3.0.0
  • Flutter: >= 2.8.0

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.

Installation and Usage

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.

Creating a form

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>(),
});

Default Values

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.

How to get/set Form data

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',
};

What about Validators?

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.

Predefined validators

FormControl

  • Validators.required
  • Validators.requiredTrue
  • Validators.email
  • Validators.number
  • Validators.min
  • Validators.max
  • Validators.minLength
  • Validators.maxLength
  • Validators.pattern
  • Validators.creditCard
  • Validators.equals
  • Validators.compose
  • Validators.composeOR
  • Validators.any
  • Validators.contains

FormGroup

  • Validators.mustMatch
  • Validators.compare

FormArray

  • Validators.minLength
  • Validators.maxLength
  • Validators.any
  • Validators.contains

Custom Validators

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.

Pattern Validator

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
  }
}

FormGroup validators

There are special validators that can be attached to FormGroup. In the next section we will see an example of that.

What about Password and Password Confirmation?

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'),
]);

Asynchronous Validators :sunglasses:

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:

  • The validator function returns a Future

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.

Debounce time in async validators

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.
);

Composing Validators

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),
      ])
    ],
  ),
});

Groups of Groups :grin:

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.

Dynamic forms with FormArray

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};
}

Arrays of Groups

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>>().

FormBuilder

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.

Groups

// 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]),
});

Arrays

// creates an array
final aliases = fb.array(['john', 'little john']);

Control

// creates a control of type String with a required validator
final control = fb.control<String>('', [Validators.required]);

Control state

// create a group
final group = fb.group(
  // creates a control with default value and disabled state
  'name': fb.state(value: 'john', disabled: true),
);

Nested Controls

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

Reactive Form Widgets

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.

How to customize error messages?

@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? ;)

When does Validation Messages begin to show up?

Touching a control

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();

Overriding Reactive Widgets show errors behavior

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.

Enable/Disable Submit button

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:

  1. Separating Submit Button in a different Widget.
  2. Using ReactiveFormConsumer widget.

Separating Submit Button in a different Widget:

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:

  • Obtains the nearest FormGroup up the widget's tree.
  • Registers the current context with the changes in the FormGroup so that if the validity of the FormGroup changes then the current context is rebuilt.

Using ReactiveFormConsumer widget:

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.

Focus/UnFocus a FormControl

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');

Focus flow between Text Fields

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);

How Enable/Disable a widget

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.

How does ReactiveTextField differs from native TextFormField or TextField?

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.

Supported Reactive Form Field Widgets

  • ReactiveTextField
  • ReactiveDropdownField
  • ReactiveSwitch
  • ReactiveCheckbox
  • ReactiveRadio
  • ReactiveSlider
  • ReactiveCheckboxListTile
  • ReactiveSwitchListTile
  • ReactiveRadioListTile

Bonus Field Widgets

  • ReactiveDatePicker
  • ReactiveTimePicker

Other Reactive Forms Widgets

  • ReactiveForm
  • ReactiveFormConsumer
  • ReactiveFormBuilder
  • ReactiveFormArray
  • ReactiveValueListenableBuilder
  • ReactiveStatusListenableBuilder

Advanced Reactive Field Widgets

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.

ReactiveTextField

We have explain the common usage of a ReactiveTextField along this documentation.

ReactiveDropdownField

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.

ReactiveValueListenableBuilder to listen when value changes in a FormControl

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,
        ),
      ],
    )
  );
}

ReactiveForm vs ReactiveFormBuilder which one?

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:

  • The form is complex enough.
  • You need to listen for changes in some child control to execute some business logic.
  • You are using some State Management library like Provider or Bloc.
  • Using a StatefulWidget to declare a very simple form is something that really doesn't bother you.

You should use ReactiveFormBuilder if:

  • The form is quite simple enough and doesn't need a separate Provider/Bloc state.
  • You don't want to use a StatefulWidget to declare the FormGroup.

But the final decision is really up to you, you can use any of them in any situations ;)

Reactive Forms + Provider plugin :muscle:

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.

How create a custom Reactive Widget?

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.

What is not Reactive Forms

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.

What is Reactive Forms

  • Reactive Forms provides a model-driven approach to handling form inputs whose values change over time. It's heavily inspired in Angular Reactive Form.
  • It lets you focus on business logic and save you time from collect, validate and mantain synchronization between your models and widgets.
  • Remove boilerplate code and brings you the posibility to write clean code defining a separation between model and UI with minimal efforts.
  • And it integrates perfectly well with common state management libraries like Provider, Bloc and many others good libraries the community has created.

Migrate versions

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

#flutter #angular #dart #reactive 

Reactive forms: Model-driven Approach to Handling Forms inputs

Ref Vs Reactive in Vue 3 (10 Minutes)

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?

#vue #reactive 

Ref Vs Reactive in Vue 3 (10 Minutes)