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

Entered text disappears on scrolling, in FormBuilderTextField #292

Closed
droidzone opened this issue May 27, 2020 · 8 comments
Closed

Entered text disappears on scrolling, in FormBuilderTextField #292

droidzone opened this issue May 27, 2020 · 8 comments
Labels
bug Something isn't working

Comments

@droidzone
Copy link

I am using flutter_form_builder 3.10.1. The issue is that after entering text input in a FormBuilderTextField, the text disappears when I scroll the ListView.

See the short video: https://vimeo.com/user116509358/review/423244392/0e9689694b

My code:
`
import 'dart:convert';

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:intl/intl.dart';
import 'package:myopipclinic/helpers/network.dart';

import 'login_screen.dart';

class RegisterAndAddAppointment extends StatefulWidget {
static const String id = 'RegisterAndAddAppointment';
String urlsuffix;
int clinicId;
int docID;
RegisterAndAddAppointment({this.urlsuffix, this.clinicId, this.docID});

@OverRide
_RegisterAndAddAppointmentState createState() =>
_RegisterAndAddAppointmentState();
}

class _RegisterAndAddAppointmentState extends State {
final GlobalKey _fbKey = GlobalKey();
Network connection = Network();
String selectedSlot;
List slots;
List slotList;
int clinicId;
int docID;
var selectedDate;
var urlsuffix;
bool showBookButton;

@OverRide
void initState() {
slots = [];
slotList = [];
connection = Network();
clinicId = widget.clinicId;
docID = widget.docID;
urlsuffix = widget.urlsuffix;
showBookButton = false;
var now = DateTime.now();
selectedDate = DateFormat("dd-MM-yy").format(DateTime.now());

print("Got clinicID:$clinicId docID:$docID");

super.initState();

}

Future getSlots() async {
print("In BookAppointment::getSlots");
slotList = [];
selectedSlot = null;
bool resp = await connection.getUserNamePwd();
if (resp == false) {
Navigator.pushNamed(context, LoginScreen.id);
}

String urlSuffix = 'getslots/clinic/$clinicId/doctor/$docID';
var response = await connection.getSlots(
  urlsuffix: urlSuffix,
  clinicID: clinicId,
  docID: docID,
  chosenDate: selectedDate,
);

setState(() {
  slots = jsonDecode(response);
  print("slots: $slots ${slots.runtimeType}");
  print("response is $response");
});

}

Future saveRegistration(
{var enteredData,
BuildContext context,
bool mustCheckin,
bool addAppointment}) async {
print('urlsuffix is ${urlsuffix}');
if (mustCheckin != null && mustCheckin) {
print("Also checking in");
} else {
print("Not checking in");
mustCheckin = false;
}
if (addAppointment != null && addAppointment) {
print("Also adding appointment");
} else {
print("Not adding appointment");
addAppointment = false;
}
print('Entered data');
print(enteredData['phone']);
if (enteredData['dob'] == null) {
enteredData['dob'] = '';
} else {
enteredData['dob'] = enteredData['dob'].toIso8601String();
}
bool resp = await connection.getUserNamePwd();
print("While trying to get username and password, resp: ${resp}");
if (resp == false) {
Navigator.pushNamed(context, LoginScreen.id);
}
await connection.isAuthenticated();
var name = enteredData['name'];
var insurance_number = enteredData['insurance_number'];
var age_yrs = enteredData['age_yrs'];
var age_mnths = enteredData['age_mnths'];
var dob = enteredData['dob'];
var gender = enteredData['gender'];
var phone = enteredData['phone'];
var email = enteredData['email'];
var altphone = enteredData['altphone'];
var address = enteredData['address'];
var marital = enteredData['marital'];
print(
'Sending parameters: name:$name, insurance_number:$insurance_number age_yrs:$age_yrs age_mnths:$age_mnths dob:$dob gender:$gender phone:$phone email:$email altphone:$altphone address:$address marital:$marital mustCheckin:$mustCheckin addAppointment:$addAppointment');
var response = await connection.sendRegistration(
urlsuffix: widget.urlsuffix,
name: name,
insurance_number: insurance_number,
age_yrs: age_yrs,
age_mnths: age_mnths,
dob: dob,
gender: gender,
phone: phone,
email: email,
altphone: altphone,
address: address,
maritalStatus: marital,
mustCheckin: mustCheckin,
addAppointment: addAppointment,
);

print('response is $response');
response = response.substring(1, response.length - 1);
Scaffold.of(context).showSnackBar(SnackBar(content: Text('$response')));
if (response.contains('"Successfully saved')) {
  showBookButton = true;
}

}

@OverRide
Widget build(BuildContext context) {
var now = DateTime.now();
var today = new DateTime(now.year, now.month, now.day);

List<Widget> chipList = List();
slots.forEach((slot) {
  chipList.add(ChoiceChip(
    selectedColor: Colors.red,
    backgroundColor: Colors.green,
    label: Text(
      slot,
      style: TextStyle(color: Colors.white),
    ),
    selected: selectedSlot == slot,
    onSelected: (bool selected) {
      print("$slot was selected");
      setState(() {
        selectedSlot = slot;
        print(selectedSlot == slot);
        print("selected:$selected slot:$slot selectedSlot:$selectedSlot ");
      });
    },
  ));
});

return Scaffold(
  appBar: AppBar(
    title: Text('Register and Book'),
  ),
  body: Builder(builder: (BuildContext context) {
    return FormBuilder(
      key: _fbKey,
      child: ListView(
        shrinkWrap: true,
        children: [
          Row(
            children: [
              Expanded(
                child: Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: FormBuilderDateTimePicker(
                    attribute: "appointment_date",
                    inputType: InputType.date,
                    format: DateFormat("dd-MM-yyyy"),
                    initialDatePickerMode: DatePickerMode.day,
                    initialDate: DateTime(1980, 6, 15),
                    initialValue: DateTime(now.year, now.month, now.day),
                    lastDate: DateTime(now.year, now.month + 1, now.day),
                    firstDate: DateTime(now.year - 115, now.month, now.day),
                    decoration: InputDecoration(
                      labelText: "Appointment Date",
                      labelStyle: TextStyle(fontSize: 18),
                    ),
                    onChanged: (newDate) {
                      try {
                        selectedDate =
                            DateFormat("dd-MM-yy").format(newDate);
                        print("New date is $selectedDate");
                        getSlots();
                      } catch (e) {

// Do nothing
}
},
validators: [
// ignore: missing_return
(val) {},
],
),
),
),
RaisedButton(
onPressed: () {
getSlots();
},
child: Text('Get Slots'),
),
],
),
Wrap(
children: chipList,
),
Padding(
padding: const EdgeInsets.all(8.0),
child: FormBuilderTextField(
attribute: "name",
decoration: InputDecoration(labelText: "Name"),
validators: [
FormBuilderValidators.required(
errorText: 'You have to enter patient's name'),
],
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: FormBuilderTextField(
attribute: "insurance_number",
decoration: InputDecoration(labelText: "Insurance No:"),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: FormBuilderTextField(
attribute: "age_yrs",
decoration: InputDecoration(
labelText: "Age(in years)",
labelStyle: TextStyle(fontSize: 18),
),
validators: [
FormBuilderValidators.numeric(),
FormBuilderValidators.max(110),
],
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: FormBuilderTextField(
attribute: "age_mnths",
decoration: InputDecoration(
labelText: "Age(in months)",
labelStyle: TextStyle(fontSize: 18),
),
validators: [
FormBuilderValidators.numeric(),
FormBuilderValidators.max(12),
],
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: FormBuilderDateTimePicker(
attribute: "dob",
inputType: InputType.date,
format: DateFormat("dd-MM-yyyy"),
initialDatePickerMode: DatePickerMode.day,
initialDate: DateTime(1980, 6, 15),
lastDate: DateTime(now.year, now.month, now.day),
firstDate: DateTime(now.year - 115, now.month, now.day),
decoration: InputDecoration(
labelText: "Date of Birth",
labelStyle: TextStyle(fontSize: 18),
),
validators: [
(val) {},
],
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: FormBuilderDropdown(
attribute: "gender",
decoration: InputDecoration(
labelText: "Gender",
labelStyle: TextStyle(fontSize: 22),
),
hint: Text(
'Select Gender',
// style: TextStyle(fontSize: 22),
),
validators: [FormBuilderValidators.required()],
initialValue: 'Male',
items: ['Male', 'Female', 'Other']
.map((gender) => DropdownMenuItem(
value: gender, child: Text("$gender")))
.toList(),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: FormBuilderTextField(
attribute: "phone",
decoration: InputDecoration(
labelText: "Phone Number",
labelStyle: TextStyle(fontSize: 18),
),
validators: [
FormBuilderValidators.numeric(),
FormBuilderValidators.required(
errorText: 'Phone number is required.')
],
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: FormBuilderTextField(
// controller: _emailController,
keyboardType: TextInputType.emailAddress,
attribute: "email",
decoration: InputDecoration(
labelText: "Email",
labelStyle: TextStyle(fontSize: 18),
),
validators: [
FormBuilderValidators.email(),
],
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: FormBuilderTextField(
// controller: _phoneController,
attribute: "altphone",
decoration: InputDecoration(
labelText: "Alternate Phone",
labelStyle: TextStyle(fontSize: 18),
),
validators: [],
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: FormBuilderTextField(
attribute: "address",
decoration: InputDecoration(labelText: "Address"),
validators: [],
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: FormBuilderDropdown(
attribute: "marital",
decoration: InputDecoration(
labelText: "Marital Status",
labelStyle: TextStyle(fontSize: 22),
),
// initialValue: 'Male',
hint: Text(
'Select',
),
initialValue: 'Unmarried',
validators: [FormBuilderValidators.required()],
items: ['Married', 'Unmarried', 'Other']
.map((gender) => DropdownMenuItem(
value: gender, child: Text("$gender")))
.toList(),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
showBookButton
? Container()
: Padding(
padding: EdgeInsets.symmetric(horizontal: 5),
child: RaisedButton(
color: Colors.blue,
onPressed: () {
if (_fbKey.currentState.saveAndValidate()) {
print(_fbKey.currentState.value);
saveRegistration(
enteredData: _fbKey.currentState.value,
context: context,
addAppointment: true,
);
}
},
child: Text('Register'),
),
),
!showBookButton
? Container()
: Padding(
padding: EdgeInsets.symmetric(horizontal: 5),
child: RaisedButton(
color: Colors.blue,
onPressed: () {
if (_fbKey.currentState.saveAndValidate()) {
print(_fbKey.currentState.value);
saveRegistration(
enteredData: _fbKey.currentState.value,
context: context,
addAppointment: true,
);
}
},
child: Text('Book appointment'),
),
),
],
),
SizedBox(
height: 20,
)
],
),
);
}),
);
}
}

`

@danvick
Copy link
Collaborator

danvick commented May 29, 2020

Hi @droidzone,
First I don't think the video link you provided seems to work.

I think (and I'm speculating here) that this may be caused by the ListView recycling its child Widgets as you scroll because this is its behavior in order to optimize computing resources.

If this is the case, kindly consider either:

  1. Using Keys to uniquely identify items within your ListView or,
  2. Using a Column instead of a ListView

@danvick danvick added the bug Something isn't working label May 29, 2020
@droidzone
Copy link
Author

Hello Dan, Thank you for responding.

The updated video link is https://www.youtube.com/watch?v=xkkr92pnPmY
I tried using keys, but still the text that I enter keeps disappearing.
Column doesnt work for me, since I have several text fields, and I need the user to scroll down and fill them.

@droidzone
Copy link
Author

droidzone commented Jun 12, 2020

    child: ListView(
                children: [
                Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: FormBuilderTextField(
                    key: UniqueKey(),
                    attribute: "name",
                    decoration: InputDecoration(labelText: "Name"),
                    validators: [
                        FormBuilderValidators.required(
                            errorText: 'You have to enter patient\'s name'),
                    ],
                    ),
                ),
                Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: FormBuilderTextField(
                    key: UniqueKey(),
                    attribute: "insurance_number",
                    decoration: InputDecoration(labelText: "Insurance No:"),
                    ),
                ),

@SpencerRiddering
Copy link
Contributor

SpencerRiddering commented Jun 14, 2020

Hi @droidzone ,

I've run into the same issue (I think). It's unexpected (to me) that FormBuilder doesn't restore the FormBuilderTextField's state when scrolling back to it. FormBuilder utilizes the initialValue paramter to initialize the value of FormBuilderTextField. Scrolling back to the field seems like a similar operation where FormBuilder provides the last know state of the field rather than the initialValue.

I've found a thing that makes it work for me. I use the onChanged parameter to captuer the latest value and replace the value in initialValues.

FormBuilder(
    key: _formKey,
    initialValue: initialValues,
    child: ...

        FormBuilderTextField(
            onChanged: (value) {
                initialValues["name"] = value;
            },
            ...
        )

@dannycortesv
Copy link

@SpencerRiddering Thanks for your tip, it solved me the issue too ;)

@danvick danvick closed this as completed Nov 29, 2020
@danvick danvick reopened this Nov 29, 2020
@venmouze
Copy link

venmouze commented Dec 1, 2020

Hi,

I'm having the same issue using the FormBuilderDateTimePicker, the workaround mentioned by @SpencerRiddering works for me as well.

For my case, my form has two steps. After selected the date and navigating to the next step and backing, the field back to initialState.

I/flutter (13785): XXXXX FBDTP initState CALLED.
GO to NEXT screen and BACK, the widget will be rebuilt here
I/flutter (13785): XXXXX FBDTP initState CALLED. <- at this moment the value is replaced by initState using the InitialValue and ignoring the state stored by the user on _textFieldController

Maybe it should check on initState if the value was changed by the user if it was it should ignore the initialValue.

Editing: Forgot to mention that if I opt to not use a initialValue, it is replacing with an empty string.

Thanks!

@deandreamatias
Copy link
Collaborator

Can be close this issue?

@deandreamatias deandreamatias added the awaiting author response Waiting for author of issue to respond with more info label Jun 13, 2022
@deandreamatias
Copy link
Collaborator

Due to lack of response and an old error, I will close this issue.
If the bug still exists, feel free to open a new issue

@deandreamatias deandreamatias removed the awaiting author response Waiting for author of issue to respond with more info label Jun 24, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

6 participants