Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Being able to copyWith a ReactiveTextField OR expose the fields of ReactiveTextField #126

Closed
naamapps opened this issue Mar 25, 2021 · 17 comments

Comments

@naamapps
Copy link

Hi,
I implemented a widget called AutoDirectionTextField which uses ReactiveValueListenableBuilder to update the text direction when the text of the field updates according to the language the user typed.
The problem is that it is not manageable because with each update there are some breaking changes and new parameters to the ReactiveTextField.
There are two options to tackle this:

  1. Extend ReactiveTextField - but the fields are not exposed because they are passed directly to the constructor.
  2. Pass ReactiveTextField to my widget - but there is no option to change the text direction also because the fields are not exposed.

The solution would be to expose the fields or add a copyWith method to allow us to copy an existing ReactiveTextField and override them if needed.

Please help,
Thanks

@joanpablo
Copy link
Owner

Hi @naamapps,

Thanks for using Reactive Forms and for the issue.

I'm not sure I understand your issue. I know that you have created a custom Widget that listens for changes of control using a ReactiveValueListenableBuilder. But I don't understand the problem with ReactiveTextField. Could you please argue more about the issue or put some code examples?

Thanks

@naamapps
Copy link
Author

@joanpablo

Here is the code:

Click to expand!
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:reactive_forms/reactive_forms.dart';
import 'package:screwdriver/screwdriver.dart';
import 'package:intl/intl.dart' as intl;
import 'dart:ui' as ui;

class AutoDirectionTextField extends StatelessWidget {
  final String formControlName;
  final FormControl formControl;
  final ValidationMessagesFunction validationMessages;
  final ControlValueAccessor valueAccessor;
  final ShowErrorsFunction showErrors;
  final InputDecoration decoration;
  final TextInputType keyboardType;
  final TextCapitalization textCapitalization;
  final TextInputAction textInputAction;
  final TextStyle style;
  final StrutStyle strutStyle;
  final TextDirection textDirection;
  final TextAlign textAlign;
  final TextAlignVertical textAlignVertical;
  final bool autofocus;
  final bool readOnly;
  final ToolbarOptions toolbarOptions;
  final bool showCursor;
  final bool obscureText;
  final bool autocorrect;
  final SmartDashesType smartDashesType;
  final SmartQuotesType smartQuotesType;
  final bool enableSuggestions;
  final int maxLines;
  final int minLines;
  final bool expands;
  final int maxLength;
  final GestureTapCallback onTap;
  final List<TextInputFormatter> inputFormatters;
  final double cursorWidth;
  final Radius cursorRadius;
  final Color cursorColor;
  final Brightness keyboardAppearance;
  final EdgeInsets scrollPadding;
  final bool enableInteractiveSelection;
  final InputCounterWidgetBuilder buildCounter;
  final ScrollPhysics scrollPhysics;
  final VoidCallback onSubmitted;
  final FocusNode focusNode;
  final Iterable<String> autofillHints;
  final MouseCursor mouseCursor;
  final DragStartBehavior dragStartBehavior;
  final AppPrivateCommandCallback onAppPrivateCommand;
  final String restorationId;
  final ScrollController scrollController;
  final TextSelectionControls selectionControls;
  final ui.BoxHeightStyle selectionHeightStyle;
  final ui.BoxWidthStyle selectionWidthStyle;

  const AutoDirectionTextField({
    Key key,
    this.formControlName,
    this.formControl,
    this.validationMessages,
    this.valueAccessor,
    this.showErrors,
    this.keyboardType,
    this.textInputAction,
    this.style,
    this.strutStyle,
    this.textDirection,
    this.textAlignVertical,
    this.toolbarOptions,
    this.showCursor,
    this.smartDashesType,
    this.smartQuotesType,
    this.maxLines = 1,
    this.minLines,
    this.maxLength,
    this.onTap,
    this.inputFormatters,
    this.cursorRadius,
    this.cursorColor,
    this.keyboardAppearance,
    this.buildCounter,
    this.scrollPhysics,
    this.onSubmitted,
    this.cursorWidth = 2.0,
    this.scrollPadding = const EdgeInsets.all(20.0),
    this.enableInteractiveSelection = true,
    this.decoration = const InputDecoration(),
    this.textCapitalization = TextCapitalization.none,
    this.autofocus = false,
    this.readOnly = false,
    this.obscureText = false,
    this.autocorrect = true,
    this.enableSuggestions = true,
    this.expands = false,
    this.textAlign = TextAlign.start,
    this.focusNode,
    this.autofillHints,
    this.mouseCursor,
    this.dragStartBehavior,
    this.onAppPrivateCommand,
    this.restorationId,
    this.scrollController,
    this.selectionControls,
    this.selectionHeightStyle,
    this.selectionWidthStyle,
  })  : assert(
            (formControlName != null && formControl == null) ||
                (formControlName == null && formControl != null),
            'Must provide a formControlName or a formControl, but not both at the same time.'),
        super(key: key);

  bool isRTL(String text) {
    String firstChar = '';
    if (text.isNotNullOrBlank) {
      firstChar = text[0];
    }
    return intl.Bidi.detectRtlDirectionality(firstChar);
  }

  @override
  Widget build(BuildContext context) {
    return ReactiveValueListenableBuilder(
      formControlName: formControlName,
      formControl: formControl,
      builder: (context, control, child) {
        bool isRTLResult = Directionality.of(context) == TextDirection.rtl;
        if (control.value != null && control.value.toString().isNotBlank) {
          isRTLResult = isRTL(control.value.toString());
        }
        return ReactiveTextField(
          formControl: control,
          validationMessages: validationMessages,
          autocorrect: autocorrect,
          autofocus: autofocus,
          buildCounter: buildCounter,
          cursorColor: cursorColor,
          cursorRadius: cursorRadius,
          cursorWidth: cursorWidth,
          enableInteractiveSelection: enableInteractiveSelection,
          enableSuggestions: enableSuggestions,
          decoration: decoration,
          expands: expands,
          inputFormatters: inputFormatters,
          keyboardAppearance: keyboardAppearance,
          keyboardType: keyboardType,
          maxLength: maxLength,
          maxLines: maxLines,
          minLines: minLines,
          obscureText: obscureText,
          onSubmitted: onSubmitted,
          key: key,
          onTap: onTap,
          readOnly: readOnly,
          scrollPadding: scrollPadding,
          scrollPhysics: scrollPhysics,
          showCursor: showCursor,
          showErrors: showErrors,
          smartDashesType: smartDashesType,
          smartQuotesType: smartQuotesType,
          strutStyle: strutStyle,
          style: style,
          textAlign: textAlign,
          textAlignVertical: textAlignVertical,
          textCapitalization: textCapitalization,
          textInputAction: textInputAction,
          toolbarOptions: toolbarOptions,
          valueAccessor: valueAccessor,
          textDirection: isRTLResult ? TextDirection.rtl : TextDirection.ltr,
          focusNode: focusNode,
        );
      },
    );
  }
}

As you can see, I manually type every field you wrote in the ReactiveTextField contractor and pass it to ReactiveTextField.
I want to remove this and always depend on your ReactiveTextField widget.
So to do this I either need to extend ReactiveTextField or do somthing like this:

Pass the ReactiveTextField instead of all the fields above,
And:

 return ReactiveValueListenableBuilder(
      formControlName: reactiveTextField.formControlName,
      formControl: reactiveTextField.formControl,
      builder: (context, control, child) {
        bool isRTLResult = Directionality.of(context) == TextDirection.rtl;
        if (control.value != null && control.value.toString().isNotBlank) {
          isRTLResult = isRTL(control.value.toString());
        }
        // ** I need this **
        var newField = reactiveTextField.copyWith(textDirection: isRTLResult ? TextDirection.rtl : TextDirection.ltr)
        return newField;
      }
);

Hope it's more understandable now.
Thanks.

@naamapps
Copy link
Author

naamapps commented Apr 4, 2021

Hey @joanpablo any updates on this?

@joanpablo
Copy link
Owner

Hi @naamapps,

Sorry not yet. Next weekend I will let you know.

Thanks

@kuhnroyal
Copy link
Contributor

ReactiveTextField is a widget. You don't copy widgets.

@joanpablo
Copy link
Owner

joanpablo commented Apr 11, 2021

Hi @naamapps,

Yeap @kuhnroyal is right, we will need to find a way to solve your issue, but is not adding a method copyWith to the ReactiveTextField. We need to rethink your issue and find a better solution.

@naamapps
Copy link
Author

@joanpablo
Yes you are right just did a quick search..
Also extending a widget is a no go.
You have an idea maybe?
The only problem is that I need to manually add every single property, it is not maintainable.

@kuhnroyal
Copy link
Contributor

Well in Flutter you just wrap it in another widget. Use smart defaults that fit your needs. Maybe a couple different factories and you are done.

@joanpablo
Copy link
Owner

I'm agree with @kuhnroyal

@naamapps
Copy link
Author

@joanpablo @kuhnroyal
Could you please give me an example?
I don't understand what you mean by factory.
A code sample would be great.
Thanks

@kuhnroyal
Copy link
Contributor

Tbh, I didn't fully read you problem until now.
When your text-direction changes, you have to build a new ReactiveTextField widget. Basically you can use your sample from #126 (comment) and just don't pass it in but construct it there instead.

Alternatively you can create a custom builder typedef that you can pass which has a textDirection.

The problem is that it is not manageable because with each update there are some breaking changes and new parameters to the ReactiveTextField.

This is mainly caused by changes in Flutter and there is not much anyone can do about it.

@joanpablo
Copy link
Owner

joanpablo commented Apr 13, 2021

Hi @naamapps,

The main difficulty for implementing the copyWIth in the ReactiveTextField is that I don't hold all the properties as fields in the class. All properties that are passed by the constructor, like textDirection, decoration, etc, are directly passed to the constructor of the inner TextField, and I never hold them (never define them as properties of the ReactiveTextField class). This is the main reason I can't implement this feature.

But if you create your own custom widget and want to define all those properties as properties of the class, then you can do it in your own widget. This is the only solution I can give you right now.

You have shown me in previous examples that you actually do this, you define each property of the ReactiveTextField as a property of the AutoDirectionTextField widget, so you can actually implement a copyWith method in your own widget.

@naamapps
Copy link
Author

Hi @joanpablo,
My issue is that I am defining every property.
This is what makes it unmaintainable.
I do not want to define every single property of ReactiveTextFIeld.
This is why I asked a copyWith method - so I could only pass the text direction property to a reactive text field I pass as a property.

I'm not sure I understand why you pass the properties in the constructor directly and you don't save them.

The other solution is to make a builder function that accepts text direction and returns reactive text field.
But it's more boilerplate and it is not automatically assigning the text direction like I would like it to.

If you have any other suggestion I would be glad to hear.
Thanks

@kuhnroyal
Copy link
Contributor

I'm not sure I understand why you pass the properties in the constructor directly and you don't save them.

Because in this case ReactiveTextField extends ReactiveFormField which holds all the properties.

For your use-case, to change the text-direction, why not just wrap everything in a new Directionality widget.
Setting things like textDirection on every single small widget is mostly not the ideal pattern in Flutter.

 return ReactiveValueListenableBuilder(
      formControlName: reactiveTextField.formControlName,
      formControl: reactiveTextField.formControl,
      builder: (context, control, child) {
        bool isRTLResult = Directionality.of(context) == TextDirection.rtl;
        if (control.value != null && control.value.toString().isNotBlank) {
          isRTLResult = isRTL(control.value.toString());
        }
        return Directionality(
          textDirection: isRTLResult ? TextDirection.rtl : TextDirection.ltr,
          child: reactiveTextField,
        );
      }
);

@naamapps
Copy link
Author

@kuhnroyal,
This is what I did when I first worked on this feature.
However, the directionality widget also set the direction of the placeholder and error.
I just want to change the direction of the written text.

@kuhnroyal
Copy link
Contributor

I see, well then you need to construct it in the builder. Don't see any other way.

@naamapps
Copy link
Author

I have settled with a builder:

return ReactiveValueListenableBuilder(
      formControlName: formControlName,
      formControl: formControl,
      builder: (context, control, child) {
        bool isRTLResult = Directionality.of(context) == TextDirection.rtl;
        if (control.value != null && control.value.toString().isNotBlank) {
          isRTLResult = isRTL(control.value.toString());
        }
        return builder(
          control,
          isRTLResult ? TextDirection.rtl : TextDirection.ltr,
        );
      },
);

Thank you guys for your help!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants