Reputation: 1339
I have a situation where I need to maintain the state of the listview of custom widget on scrolling.
Following is the functionality.
I've a listview in flutter with each containing a TextField. In each ListView item, there's a nested list associated with each item which is another TextField.
The ListView and nested ListView are created dynamically. But on scrolling to the end, the text in the textfield (both parent and child listview widgets) is cleared and not maintaining the state.
Following is my code.
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(debugShowCheckedModeBanner: false, home: NewCourse()));
}
class NewCourse extends StatefulWidget {
@override
_NewCourseState createState() => _NewCourseState();
}
class _NewCourseState extends State<NewCourse> {
bool isTagSelected = false;
bool isTopicCreationEnabled = false;
List<NewTopic> newTopicList = [];
addNewTopic() {
newTopicList.add(new NewTopic());
setState(() {});
}
enableTopicCreation(String txtTopicName) {
setState(() {
if (txtTopicName.length > 0) {
isTopicCreationEnabled = true;
} else {
isTopicCreationEnabled = false;
}
});
}
@override
Widget build(BuildContext context) {
var _createNewTopic;
if (isTopicCreationEnabled) {
_createNewTopic = () {
addNewTopic();
};
} else {
_createNewTopic = null;
}
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.blueGrey,
title: Text('ALL COURSES'),
centerTitle: true,
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Container(
color: Colors.blueGrey,
child: Center(
child: Padding(
padding: EdgeInsets.all(20),
child: Text(
"NEW COURSE",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
fontFamily: 'CodeFont',
color: Colors.white,
),
),
),
),
),
Container(
padding: EdgeInsets.all(5),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.grey[400],
blurRadius: 20.0,
offset: Offset(0, 10),
),
],
),
child: Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Expanded(
flex: 9,
child: Container(
padding: EdgeInsets.all(8),
margin: EdgeInsets.all(8),
child: TextField(
onChanged: (text) {
enableTopicCreation(text);
},
decoration: InputDecoration(
border: InputBorder.none,
hintText: "Course Name",
hintStyle: TextStyle(color: Colors.grey[400]),
),
),
),
),
Expanded(
flex: 3,
child: FlatButton(
onPressed: _createNewTopic,
child: Container(
padding: EdgeInsets.all(18),
margin: EdgeInsets.all(8),
child: Icon(
Icons.add_box,
color: isTopicCreationEnabled
? Colors.green
: Colors.blueGrey,
),
),
),
),
],
),
],
),
),
Container(
child: Expanded(
child: getAllTopicsListView(),
),
),
],
),
);
}
Widget getAllTopicsListView() {
ListView topicList = new ListView.builder(
shrinkWrap: true,
itemCount: newTopicList.length,
itemBuilder: (context, index) {
return new ListTile(
title: new NewTopic(),
);
});
return topicList;
}
}
class NewTopic extends StatefulWidget {
@override
_NewTopicState createState() => _NewTopicState();
}
class _NewTopicState extends State<NewTopic> {
TextEditingController _topicController;
@override
void initState() {
super.initState();
_topicController = new TextEditingController();
}
@override
void dispose() {
super.dispose();
_topicController.dispose();
}
bool isSubTopicCreationEnabled = false;
List<NewSubTopic> newSubTopicList = [];
addNewSubTopic() {
setState(() {
newSubTopicList.add(new NewSubTopic());
});
}
enableSubTopicCreation(String txtTopicName) {
setState(
() {
if (txtTopicName.length > 0) {
isSubTopicCreationEnabled = true;
} else {
isSubTopicCreationEnabled = false;
}
},
);
}
@override
Widget build(BuildContext context) {
var _createNewSubTopic;
if (isSubTopicCreationEnabled) {
_createNewSubTopic = () {
addNewSubTopic();
};
} else {
_createNewSubTopic = null;
}
return Column(
children: [
Container(
margin: EdgeInsets.only(top: 20, bottom: 20, left: 10, right: 50),
padding: EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.grey[400],
blurRadius: 20.0,
offset: Offset(0, 10),
),
],
),
child: Center(
child: Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Expanded(
flex: 9,
child: Container(
child: TextField(
controller: _topicController,
onChanged: (text) {
enableSubTopicCreation(text);
},
decoration: InputDecoration(
border: InputBorder.none,
hintText: "Enter the topic",
hintStyle: TextStyle(color: Colors.grey[400]),
),
),
),
),
Expanded(
flex: 3,
child: FlatButton(
onPressed: _createNewSubTopic,
child: Container(
child: Icon(
Icons.add_box,
color: isSubTopicCreationEnabled
? Colors.green
: Colors.blueGrey,
),
),
),
),
],
),
Row(
children: <Widget>[
Container(
child: Expanded(
//child: Text("Hi There!"),
child: getAllSubTopicsListView(),
),
),
],
),
],
),
),
),
],
);
}
Widget getAllSubTopicsListView() {
ListView subTopicList = new ListView.builder(
shrinkWrap: true,
itemCount: newSubTopicList.length,
itemBuilder: (context, index) {
return new ListTile(
title: new NewSubTopic(),
);
},
);
return subTopicList;
}
}
class NewSubTopic extends StatefulWidget {
@override
_NewSubTopicState createState() => _NewSubTopicState();
}
class _NewSubTopicState extends State<NewSubTopic> {
TextEditingController _subtopicController;
@override
void initState() {
super.initState();
_subtopicController = new TextEditingController();
}
@override
void dispose() {
super.dispose();
_subtopicController.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Container(
margin: EdgeInsets.only(top: 20, bottom: 20, left: 50, right: 10),
padding: EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.grey[400],
blurRadius: 20.0,
offset: Offset(0, 10),
),
],
),
child: Center(
child: Container(
child: TextField(
controller: _subtopicController,
decoration: InputDecoration(
border: InputBorder.none,
hintText: "Enter the sub topic",
hintStyle: TextStyle(color: Colors.grey[400]),
),
),
),
),
),
],
);
}
}
I have used TextEditingController to maintain the state, but did not solve the issue.
Issue: Could not maintain the state once I am scrolling down and scrolling back to the top of the screen. The entered text in TextField widget in the parent and child are cleared. Any suggestion would be appreciated, thanks.
Upvotes: 3
Views: 3175
Reputation: 101
That happens because the ListView widget dynamically removes and re-adds widgets as they scroll out of and back into view.
For short lists/ portrait-only apps, where only minimal scrolling might be needed, a ListView should be fine, since items won't scroll that far out of view (ListView has a certain threshold until which it will keep items in memory).
But for longer lists or apps that should work in landscape mode as well - or maybe just to be safe - you might want to use a Column (combined with SingleChildScrollView) instead. Since SingleChildScrollView doesn't clear widgets that scroll out of view, you are not in danger of losing user input in that case.
For example:
Form(
child: ListView(
children: [ ... ],
),
),
simply becomes
Form(
child: SingleChildScrollView(
child: Column(
children: [ ... ],
),
),
),
Upvotes: 6
Reputation: 147
I'd the same issue while developing one of my apps.
I fixed this issue by replacing ListView
into Column
wrapped with SingleChildScrollView
.
This happens because the ListView
only renders visible items on the screen.
So, whenever you scroll up, the children widget and the state(data) inside it gets destroyed, and have to re-run the app to retrieve the state.
Upvotes: 2
Reputation: 1151
add this
class _HomeHomeMadeListState extends State<HomeHomeMadeList> with AutomaticKeepAliveClientMixin { class _HomeHomeMadeListState extends State<HomeHomeMadeList> {
@override
bool get wantKeepAlive => true;
Upvotes: 6