TextFormField selection handle is overlapping when scrolled offscreen

Viewed 160

The problem I'm facing is that when my ListView is scrolled off view, the text selection handle can still be seen. Here's a photo for reference, it will explain it much better: https://imgur.com/a/Lg93cV9

I've only tested it on Android, I don't have an iOS device to test it on.

Here's my main widget

  @override
  Widget body() {
    return StoreConnector<AppState, Step1IntroViewModel>(
      converter: (store) => Step1IntroViewModel.fromStore(store),
      builder: (context, viewModel) {
        return StepMainTemplate.build(
          this.context,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.center,
            mainAxisAlignment: MainAxisAlignment.start,
            children: <Widget>[
              _buildImageWidget(),
              Text(
                'Upload image',
                style: TextStyle(
                  color: Colors.red,
                  fontWeight: FontWeight.bold,
                ),
              ),
              Expanded(
                child: ListView(
                  children: <Widget>[
                    StepMainTemplate.title(
                      subtitle: 'Step 1 of 2',
                      title: 'Introduce yourself',
                    ),
                    ProfileForm(
                      key: _profileFormKey,
                      initProfile: viewModel.profileState?.data,
                    ),
                    Container(
                      padding: const EdgeInsets.fromLTRB(0, 40, 0, 20),
                      width: double.infinity,
                      child: RedOneButton.text(
                          text: "Next",
                          function: () {
                            var profile = _profileFormKey.currentState.save();
                            if (profile != null) {
                              viewModel.saveProfile(profile);
                              ExtendedNavigator.ofRouter<Router>()
                                  .pushNamed(Routes.step2Route);
                            }
                          }),
                    ),
                  ],
                ),
              ),
            ],
          ),
        );
      },
    );
  }

And here's the ProfileForm

class ProfileForm extends StatefulWidget {
  final Profile initProfile;

  const ProfileForm({Key key, this.initProfile}) : super(key: key);

  @override
  ProfileFormState createState() => ProfileFormState();
}

class ProfileFormState extends State<ProfileForm> {
  final _formKey = GlobalKey<FormState>();

  List<String> genders = ['Male', 'Female'];
  List<String> positions = ['SA', 'PSA', 'PS', 'VPS'];
  String _fullName;
  String _position;
  String _gender;
  String _number;
  String _email;
  String _urlName;

  @override
  void initState() {
    if (widget.initProfile != null) {
      _fullName = widget.initProfile.fullName;
      _position = widget.initProfile.position;
      _gender = widget.initProfile.gender;
      _number = widget.initProfile.phone;
      _email = widget.initProfile.email;
      _urlName = widget.initProfile.urlName;
    }
    super.initState();
  }

  Profile save() {
    if (_formKey.currentState.validate()) {
      _formKey.currentState.save();
      var profile = Profile(
          fullName: _fullName,
          position: _position,
          gender: _gender,
          phone: _number,
          email: _email,
          urlName: _urlName);
      return profile;
    }
    return null;
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: EdgeInsets.all(12.0),
      child: Form(
          key: _formKey,
          child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: <Widget>[
                ProfileTextFormField(
                  initialValue: _fullName,
                  labelText: 'Full name',
                  hintText: 'Please enter your full name',
                  validator: (value) => Validator.validateFullName(value),
                  textInputAction: TextInputAction.next,
                  textCapitalization: TextCapitalization.words,
                  onSaved: (value) => _fullName = value,
                ),
                ProfileDropDownField(
                    initialValue: _position,
                    labelText: 'Position',
                    hintText: 'Please enter your position',
                    items: positions,
                    onSelected: (value) => _position = value),
                ProfileTextFormField(
                  initialValue: _number,
                  labelText: 'Phone number',
                  hintText: 'Please enter your phone number',
                  validator: (value) => Validator.validatePhone(value),
                  keyboardType: TextInputType.phone,
                  textInputAction: TextInputAction.next,
                  onSaved: (value) => _number = value,
                ),
                ProfileTextFormField(
                  initialValue: _email,
                  labelText: 'Email',
                  hintText: 'Please enter your email',
                  validator: (value) => Validator.validateEmail(value),
                  keyboardType: TextInputType.emailAddress,
                  textInputAction: TextInputAction.next,
                  onSaved: (value) => _email = value,
                  textCapitalization: TextCapitalization.none,
                ),
                ProfileRadioField(
                  initialValue: _gender,
                  labelText: 'Gender',
                  items: genders,
                  onSelected: (value) => _gender = value,
                ),
                ProfileTextFormField(
                  initialValue: _urlName,
                  labelText: 'URL name',
                  hintText: 'Your preferred URL name',
                  textInputAction: TextInputAction.done,
                  onSaved: (value) => _urlName = value,
                  textCapitalization: TextCapitalization.none,
                ),
              ])),
    );
  }
}

Also related to https://github.com/flutter/flutter/issues/13182

1 Answers

I have managed to fix this problem by directly putting the TextFormField directly inside a ListView. This is the only way I could get it to work properly.

  @override
  Widget build(BuildContext context) {
    return Form(
        key: _formKey,
        child: ListView(
          padding: const EdgeInsets.all(8.0),
          children: <Widget>[
            widget.header ?? SizedBox.shrink(),
            ..._buildContentList(),
            widget.footer ?? SizedBox.shrink(),
          ],
        ));
  }

  List<Widget> _buildContentList() {
    var list = <Widget>[];
    if (widget.hasProfile) list.addAll(_buildProfileFormList());
    if (widget.hasStory) list.addAll(_buildStoryFormList());
    return list;
  }

  List<Widget> _buildProfileFormList() {
    return <Widget>[
      ProfileTextFormField(
        initialValue: _profile.fullName,
        labelText: 'Full name',
        hintText: 'Please enter your full name',
        validator: (value) => Validator.validateFullName(value),
        textInputAction: TextInputAction.next,
        textCapitalization: TextCapitalization.words,
        onSaved: (value) => _profile.fullName = value,
      ),
      ProfileDropDownField(
          initialValue: _profile.position,
          labelText: 'Position',
          hintText: 'Please enter your position',
          items: positions,
          onSelected: (value) => _profile.position = value),
      ProfileTextFormField(
        initialValue: _profile.phone,
        labelText: 'Phone number',
        hintText: 'Please enter your phone number',
        validator: (value) => Validator.validatePhone(value),
        keyboardType: TextInputType.phone,
        textInputAction: TextInputAction.next,
        onSaved: (value) => _profile.phone = value,
      ),
      ProfileTextFormField(
        initialValue: _profile.email,
        labelText: 'Email',
        hintText: 'Please enter your email',
        validator: (value) => Validator.validateEmail(value),
        keyboardType: TextInputType.emailAddress,
        textInputAction: TextInputAction.next,
        onSaved: (value) => _profile.email = value,
        textCapitalization: TextCapitalization.none,
      ),
      ProfileRadioField(
        initialValue: _profile.gender,
        labelText: 'Gender',
        items: genders,
        onSelected: (value) => _profile.gender = value,
      ),
      ProfileTextFormField(
        initialValue: _profile.urlName,
        labelText: 'URL name',
        hintText: 'Your preferred URL name',
        textInputAction: TextInputAction.done,
        onSaved: (value) => _profile.urlName = value,
        textCapitalization: TextCapitalization.none,
      ),
    ];
  }

  List<Widget> _buildStoryFormList() {
    return <Widget>[
      ProfileTextFormField(
        initialValue: _profile.aboutYourself,
        labelText: '',
        hintText:
            '',
        textInputAction: TextInputAction.next,
        textCapitalization: TextCapitalization.sentences,
        keyboardType: TextInputType.multiline,
        onSaved: (value) => _profile.aboutYourself = value,
        maxLines: 3,
      ),
      ProfileTextFormField(
        initialValue: _profile.sharingMessage,
        labelText: '',
        hintText: '',
        keyboardType: TextInputType.multiline,
        textInputAction: TextInputAction.done,
        maxLength: 300,
        onSaved: (value) => _profile.sharingMessage = value,
        maxLines: 5,
      ),
    ];
  }

Related