I got a problem working with TextFormField inside a StreamBuilder in a scrollable ListView.
When a ListView is scrolled up and down, it destroys and rebuilds some children. When StreamBuilder widget rebuilds itself due to asynchronous nature of streams, it initiates two calls of StreamBuilder build function. First a no-data build with connection.state = waiting and then second build with stream last value.
When a TextFormFields inside a StreamBuilder rebuilds itself, it uses first call to set InitialValue for current value and it ignores next frame. This happens apparently because in between these two calls TextFormFields is not being destroyed and rebuild but rather updated.
Problem is that i use InitialValue field to set updated stream value to the TextFormField. So all above results in reseting any user input in the TextFormFields back to InitialValue set in StreamBuilder.
My code:
Widget StreamInputName({required BuildContext context,required Stream<String> inputStream, required ValueChanged<String> onChanged,
String? nameText,String? helperText,String? hintText, int maxLines = 1,String? initialText}) {
final profilecubit = context.read<ProfileEditCubit>();
return StreamBuilder<String>(
stream: inputStream,//profilecubit.userNameStream,
initialData: initialText,
builder: (context,snapshot) {
return DecoratedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(width: 1,color: Colors.grey.shade300),),
child: Container(
padding: EdgeInsets.only(top: 3,right: 25,bottom: 14,left: 15),
child:
TextFormField(
initialValue: snapshot.data,
style: TextStyle(fontSize: 16,fontWeight: FontWeight.normal),
decoration: InputDecoration(
labelText: nameText,
labelStyle: TextStyle(fontSize: 16,fontWeight: FontWeight.normal,color: Colors.grey),
focusedBorder: InputBorder.none,
enabledBorder: InputBorder.none,
disabledBorder: InputBorder.none,
errorBorder: InputBorder.none,
border: InputBorder.none,
contentPadding: EdgeInsets.only(top: 12, left: 0, right: 0, bottom: 0,),
errorStyle: TextStyle(color: Theme.of(context).errorColor, fontSize: 14, height: 0.56,),
hintText: hintText,
errorText: (snapshot.error != null) ? snapshot.error.toString() : null,
floatingLabelBehavior: FloatingLabelBehavior.always,
helperText: helperText,
),
maxLines: maxLines,
onChanged: onChanged,
),
)
);
}
);
}
Some responds to similar problems in stackoverflow, suggest to keepAlive all widgets inside scrollable ListView which seems rather wrong to me.
Any ideas ?
CodePudding user response:
Create a TextEditingController
outside the StreamInputName
method but in the class, then set it as the controller of the TextFormField
like so:
final TextEditingController controller = TextEditingController();
Widget StreamInputName({required BuildContext context,required Stream<String> inputStream, required ValueChanged<String> onChanged,
String? nameText,String? helperText,String? hintText, int maxLines = 1,String? initialText}) {
final profilecubit = context.read<ProfileEditCubit>();
return StreamBuilder<String>(
stream: inputStream,//profilecubit.userNameStream,
initialData: initialText,
builder: (context,snapshot) {
return DecoratedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(width: 1,color: Colors.grey.shade300),),
child: Container(
padding: EdgeInsets.only(top: 3,right: 25,bottom: 14,left: 15),
child:
TextFormField(
controller: controller
...