joanpablo / reactive_forms

This is a model-driven approach to handling form inputs and validations, heavily inspired in Angular's Reactive Forms
MIT License
472 stars 90 forks source link

ReactiveTextField and DateTime #456

Open dmarotta77 opened 6 months ago

dmarotta77 commented 6 months ago

I would like to create a ReactiveTextField for the manual entry of a date, without using ReactiveDateTimePicker. If I define a FormControl and associate it with a ReactiveTextField, when I type in the date, I get a conversion exception from String to DateTime

dmarotta77 commented 6 months ago

After some testing, I solved the problem by performing date format validation in the ValueAccessor, although from an architectural standpoint, it does not fully satisfy me.

I am providing an example of the adopted solution in case someone else has the same need.

Do you have any suggestions for a better implementation?

main.dart


import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:intl/intl.dart';
import 'package:new_flutter/custom_date_time_value_accessor.dart';
import 'package:reactive_forms/reactive_forms.dart';

void main() {
  runApp(
    MaterialApp(
      localizationsDelegates: const [
        ...GlobalMaterialLocalizations.delegates,
      ],
      supportedLocales: const [
        Locale('it'),
      ],
      localeListResolutionCallback: (_, __) {
        Intl.defaultLocale = 'it';
        return const Locale('it');
      },
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Date'),
        ),
        body: Column(
          children: [
            Spacer(
              flex: 1,
            ),
            MyDateInputApp(),
            Spacer(
              flex: 1,
            ),
          ],
        ),
      ),
    ),
  );
}

class MyDateInputApp extends StatelessWidget {
  const MyDateInputApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyDateInputForm(),
    );
  }
}

class MyDateInputForm extends StatelessWidget {
  MyDateInputForm({super.key});

  final form = FormGroup(
    {'date': FormControl<DateTime>(
      validators: [
        Validators.required,
      ]
    )},
  );

  @override
  Widget build(BuildContext context) {
    return ReactiveForm(
      formGroup: form,
      child: ReactiveTextField<DateTime>(
        valueAccessor: CustomDateTimeValueAccessor(),
        validationMessages: {
          'date': (_) => 'Invalid date format',
          ValidationMessage.required: (_) => 'Required',
        },
        formControlName: 'date',
        decoration: const InputDecoration(
          labelText: 'Date',
          hintText: 'dd/mm/yyyy',
        ),
      ),
    );
  }
}

custom_date_time_value_accessor.dart

import 'package:intl/intl.dart';
import 'package:reactive_forms/reactive_forms.dart';

class CustomDateTimeValueAccessor
    extends ControlValueAccessor<DateTime, String> {
  @override
  String modelToViewValue(DateTime? modelValue) {
    return modelValue == null
        ? ''
        : DateFormat.yMd(Intl.defaultLocale).format(modelValue);
  }

  @override
  DateTime? viewToModelValue(String? viewValue) {
    if (viewValue != null && !_isDate(viewValue)) {
      control!.setErrors({'date': true});
      control!.markAsTouched();
      return null;
    } else {
      return DateFormat.yMd(Intl.defaultLocale).parseStrict(viewValue!);
    }
  }

  bool _isDate(String? value) {
    if (value == null || value.trim().isEmpty) {
      return false;
    }
    try {
      DateFormat.yMd(Intl.defaultLocale).parseStrict(value);
      return true;
    } catch (e) {
      return false;
    }
  }
}