jar_form 1.0.1 copy "jar_form: ^1.0.1" to clipboard
jar_form: ^1.0.1 copied to clipboard

Reactive form management for Flutter with JAR validation schema support.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:jar/jar.dart';
import 'package:jar_form/controller.dart';
import 'package:jar_form/field.dart';
import 'package:jar_form/field/config.dart';
import 'package:jar_form/form.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'JAR Form Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: SignupScreen(),
    );
  }
}

class SignupScreen extends StatefulWidget {
  @override
  _SignupScreenState createState() => _SignupScreenState();
}

class _SignupScreenState extends State<SignupScreen> {
  final _formController = JarFormController();
  late final JarObject _signupSchema;

  final _asyncUsernameValidator = (dynamic value) async {
    await Future.delayed(const Duration(milliseconds: 800));
    return value == 'admin' ? 'Username already taken' : null;
  };

  @override
  void initState() {
    super.initState();

    _signupSchema = Jar.object({
      'username': Jar.string()
          .min(3, 'Username must be at least 3 characters')
          .max(20, 'Username cannot exceed 20 characters')
          .required('Username is required'),
      'email': Jar.string()
          .email('Invalid email format')
          .required('Email is required'),
      'password': Jar.string()
          .min(8, 'Password must be at least 8 characters')
          .matches(
            r'(?=.*[A-Z])',
            'Password must contain at least one uppercase letter',
          )
          .required('Password is required'),
      'confirmPassword': Jar.string()
          .equalTo('password', 'Passwords must match')
          .required('Please confirm your password'),
      'age': Jar.number()
          .min(18, 'You must be at least 18 years old')
          .required('Age is required'),
      'termsAccepted': Jar.boolean()
          .isTrue('You must accept the terms and conditions')
          .required('Please respond to the terms'),
    });

    _formController.register(
      'username',
      JarFieldConfig<String>(
        schema: _signupSchema.fields['username']
            as JarSchema<String, JarSchema<String, dynamic>>,
        asyncValidators: [_asyncUsernameValidator],
      ),
    );
  }

  @override
  void dispose() {
    _formController.dispose();
    super.dispose();
  }

  Future<void> _onSubmit(Map<String, dynamic> values) async {
    await Future.delayed(const Duration(seconds: 1));

    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(
        content: Text('Signup successful!'),
        backgroundColor: Colors.green,
      ),
    );

    print('Form submitted with values:');
    print(values);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('JAR Form Example'),
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16.0),
        child: JarForm(
          controller: _formController,
          schema: _signupSchema,
          onSubmit: _onSubmit,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              _buildUsernameField(),
              const SizedBox(height: 16),
              _buildEmailField(),
              const SizedBox(height: 16),
              _buildPasswordField(),
              const SizedBox(height: 16),
              _buildConfirmPasswordField(),
              const SizedBox(height: 16),
              _buildAgeField(),
              const SizedBox(height: 16),
              _buildTermsField(),
              const SizedBox(height: 24),
              _buildSubmitButton(),
              const SizedBox(height: 16),
              _buildResetButton(),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildUsernameField() {
    return JarFormField<String>(
      name: 'username',
      builder: (state) => TextField(
        //should pass value for maintain sync when value changes
        decoration: InputDecoration(
          labelText: 'Username',
          errorText: state.error,
          border: const OutlineInputBorder(),
          helperText: 'Try "admin" to see async validation error',
          suffixIcon: state.isValidating
              ? const SizedBox(
                  width: 20,
                  height: 20,
                  child: CircularProgressIndicator(strokeWidth: 2.0))
              : null,
        ),
        onChanged: state.onChange,
        onTap: state.markAsTouched,
      ),
    );
  }

  Widget _buildEmailField() {
    return JarFormField<String>(
      name: 'email',
      builder: (state) => TextField(
        //should pass value for maintain sync when value changes
        decoration: InputDecoration(
          labelText: 'Email',
          errorText: state.error,
          border: const OutlineInputBorder(),
        ),
        keyboardType: TextInputType.emailAddress,
        onChanged: state.onChange,
        onTap: state.markAsTouched,
      ),
    );
  }

  Widget _buildPasswordField() {
    return JarFormField<String>(
      name: 'password',
      builder: (state) => TextField(
        //should pass value for maintain sync when value changes
        decoration: InputDecoration(
          labelText: 'Password',
          errorText: state.error,
          border: const OutlineInputBorder(),
          helperText: 'Must be at least 8 characters with 1 uppercase letter',
        ),
        obscureText: true,
        onChanged: state.onChange,
        onTap: state.markAsTouched,
      ),
    );
  }

  Widget _buildConfirmPasswordField() {
    return JarFormField<String>(
      name: 'confirmPassword',
      builder: (state) => TextField(
        //should pass value for maintain sync when value changes
        decoration: InputDecoration(
          labelText: 'Confirm Password',
          errorText: state.error,
          border: const OutlineInputBorder(),
        ),
        obscureText: true,
        onChanged: state.onChange,
        onTap: state.markAsTouched,
      ),
    );
  }

  Widget _buildAgeField() {
    return JarFormField<num>(
      name: 'age',
      builder: (state) => TextField(
        //should pass value for maintain sync when value changes
        decoration: InputDecoration(
          labelText: 'Age',
          errorText: state.error,
          border: const OutlineInputBorder(),
          helperText: 'You must be at least 18 years old',
        ),
        keyboardType: TextInputType.number,
        onChanged: (value) {
          if (value.isEmpty) {
            state.onChange(null);
          } else {
            state.onChange(int.tryParse(value));
          }
        },
        onTap: state.markAsTouched,
      ),
    );
  }

  Widget _buildTermsField() {
    return JarFormField<bool>(
      name: 'termsAccepted',
      builder: (state) => CheckboxListTile(
        title: const Text('I accept the terms and conditions'),
        value: state.value ?? false,
        onChanged: (value) {
          state.onChange(value);
          state.markAsTouched();
        },
        controlAffinity: ListTileControlAffinity.leading,
        subtitle: state.error != null
            ? Text(
                state.error!,
                style: const TextStyle(color: Colors.red),
              )
            : null,
      ),
    );
  }

  Widget _buildSubmitButton() {
    return Builder(builder: (context) {
      return ElevatedButton(
        onPressed: _formController.isSubmitting
            ? null
            : () => _formController.submit(),
        child: _formController.isSubmitting
            ? const Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  SizedBox(
                    width: 20,
                    height: 20,
                    child: CircularProgressIndicator(
                        strokeWidth: 2.0, color: Colors.white),
                  ),
                  SizedBox(width: 8),
                  Text('Submitting...'),
                ],
              )
            : const Text('Sign Up'),
      );
    });
  }

  Widget _buildResetButton() {
    return OutlinedButton(
      onPressed: () => _formController.clearAll(),
      child: const Text('Reset Form'),
    );
  }
}
2
likes
0
points
52
downloads

Publisher

unverified uploader

Weekly Downloads

Reactive form management for Flutter with JAR validation schema support.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter, jar

More

Packages that depend on jar_form