Flutter unable to set state of list<object> - dart

Here is how the main TodoApp widget looks like:
class TodoApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(title: 'Todo List', home: new TodoList());
}
}
I have created a class for a list item which is called Task:
class Task {
String name;
int number;
Task(this.name, this.number);
}
The list widget looks like this:
class TodoList extends StatefulWidget {
#override
createState() => new TodoListState();
}
The TodoListState class has a method which adds new items to the list:
class TodoListState extends State<TodoList> {
List<Task> _todoItems = [];
List<bool> checkedItems = []; // this was created for testing purposes
void _addTodoItem(Task task) {
if (task.name.length > 0) {
setState(() => checkedItems.add(false)); // works fine
setState(() => _todoItems.add(new Task('abc', 12))); // does not work
}
}
...
}
Why when _addTodoItem method is called, one item is added to the checkedItems list but _todoItems list is left unchanged? What am I missing?

It's working as it should, setState is only called once, hence the first method is executed. You should add both methods in the setState method, since it's called once per run or state, you can write it like this:
....
setState(() => {
checkedItems.add(false);
_todoItems.add(new Task('abc', 12);
});
....

Related

Does buildSuggestions of showSearch method run continuously?

I'm new in flutter and I have a function to search working with API,and i have created a function called _fetchData() inside StatefulWidget:
_fetchData() async {
print("fetchData Run");
print(widget.query);
this.setState((){
isLoading=true;
});
final response = await http.get(URL+'search/keyword?api_key='+API_KEY+'&query='+widget.query);
if (response.statusCode == 200) {
print(response.body);
var list = json.decode(response.body)["results"] as List;
keywordList = list.map<Keyword>((json)=>Keyword.fromJson(json)).toList();
this.setState((){
isLoading=false;
});
} else {
throw Exception('Failed to load photos');
}
}
and i put my StatefulWidget in buildSuggestions like:
class SearchHome extends SearchDelegate {
#override
List<Widget> buildActions(BuildContext context) {
//widget
}
#override
Widget buildLeading(BuildContext context) {
//widget
}
#override
Widget buildResults(BuildContext context) {
//widget
}
#override
Widget buildSuggestions(BuildContext context) {
//SuggestKeyword is my statefullwidget with _fetchData() function inside
return SuggestKeyword(query: query);
}
}
but i'm confuse why _fetchData() keep running continuously, that's mean my apps keep hit an API, i think it's not good so i want to avoid it
I know i can put SuggestKeyword inside buildResults, but these method need user to submit/enter the keyboard to run, i want to search while the user is still typing, but buildSuggestions keep running even when user is not typing,
am i doing something wrong? any suggestion would appreciated!

Reset State of children widget

Here is the summary of the code I'm having a problem with:
Parent widget
class HomePage extends StatefulWidget {
#override
State<HomePage> createState() => HomePageState();
}
class HomePageState extends State<HomePage> {
final GlobalKey<AsyncLoaderState> _asyncLoaderState = GlobalKey<AsyncLoaderState>();
List<DateTime> rounds;
List<PickupModel> pickups;
DateTime currentDate;
Widget build(BuildContext context) {
var _asyncLoader = AsyncLoader(
key: _asyncLoaderState,
initState: () async => await _getData(),
renderLoad: () => Scaffold(body: Center(child: CircularProgressIndicator())),
renderError: ([error]) => Text('Sorry, there was an error loading'),
renderSuccess: ({data}) => _buildScreen(context),
);
return _asyncLoader;
}
Widget _buildScreen(context) {
return Scaffold(
body: PickupList(pickups),
);
}
Future<Null> _selectDate(BuildContext context) async {
final DateTime picked = await showDatePicker(
context: context,
);
if (picked != null && picked != currentDate) {
currentDate = picked;
pickups = await api.fetchPickupList(currentDate);
setState(() {
});
}
}
_getData() async {
rounds = await api.fetchRoundsList();
currentDate = _getNextRound(rounds);
pickups = await api.fetchPickupList(currentDate);
}
}
Children Widget
(Listview builds tiles)
class PickupTile extends StatefulWidget{
final PickupModel pickup;
PickupTile(this.pickup);
#override
State<StatefulWidget> createState() {
return PickupTileState();
}
}
class PickupTileState extends State<PickupTile> {
Duration duration;
Timer timer;
bool _isUnavailable;
bool _isRunning = false;
bool _isDone = false;
#override
void initState() {
duration = widget.pickup.duration;
_isUnavailable = widget.pickup.customerUnavailable;
super.initState();
}
#override
Widget build(BuildContext context) {
return Row(
children: [
// UI widgets
]
}
So I have a parent widget an initial list of pickups which are displayed in the children PickupTile. One can change the date of the pickups displayed using _selectDate. This fetches a new list of Pickups which are stored in the parent State, and the children are rebuilt with their correct attributes. However, the State of the children widget (duration, isRunning, isDone...) is not reset so they stay on screen when changing the date.
If feel like I'm missing something obvious but I can't figure out how to reset the State of the children Widget or create new PickupTiles so that when changing the date I get new separate States.

Refresh a listview outside of a widget in Flutter?

I have a Flutter application which comprises of a scaffold with a slider and a tabview.
The tabview comprises of a List which is show on each tab as seen in the picture below.
List<Widget> widgetList = <Widget>[
Post(),
Feed(),
Location(),
HomePage(),
Feed(),
];
Now I would like to refresh the current tab on screen when the slider is moved. However, since the classes are private i.e. _HomePageState, I do not know how to access the refreshList() method as shown in the snippet below.
homepage.dart:
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => new _HomePageState();
}
class _HomePageState extends State<HomePage> {
var list;
var random;
var refreshKey = GlobalKey<RefreshIndicatorState>();
#override
void initState() {
super.initState();
random = Random();
refreshList();
}
Future<Null> refreshList() async {
refreshKey.currentState?.show(atTop: false);
await Future.delayed(Duration(seconds: 2));
setState(() {
list = List.generate(random.nextInt(10), (i) => "Item $i");
});
return null;
}
}
The slider is not baked into each listview Widget i.e. homepage.dart as the slider values is applicable to each individual tab. How can I refresh the inner listview widget when the outer widget with the slider is moved?
There are different ways to deal with this:
You could pass down the PageController and the page index to your page widgets. In your page widgets can then listen to the changes (pageController.addListener(...)) and compare the currently centered page with their page index.
Create one ChangeNotifier for every page that you want to refresh:
final postRefresh = ChangeNotifier();
final feedRefresh = ChangeNotifier();
...
// build method of parent:
List<Widget> widgetList = <Widget>[
Post(refresh: postRefresh),
Feed(refresh: feedRefresh),
...
];
// initState method of parent:
pageController.addListener(() {
final roundedPage = pageController.page.round();
if(roundedPage == 0) {
postRefresh.notifyListeners();
}
else if(roundedPage == 1) {
feedRefresh.notifyListeners();
}
// ...
})
You could also give your page widgets a global key (for that, their State classes must be public):
// class body of parent:
final postPageKey = GlobalKey<PostPageState>();
final feedPageKey = GlobalKey<FeedPageState>();
// build method of parent:
List<Widget> widgetList = <Widget>[
Post(key: postPageKey),
Feed(key: feedPageKey),
...
];
// initState method of parent:
pageController.addListener(() {
final roundedPage = pageController.page.round();
if(roundedPage == 0) {
postPageKey.currentState?.refresh();
}
else if(roundedPage == 1) {
feedPageKey.currentState?.refresh();
}
// ...
})

Flutter - generic function typedef errror

I am writing a base loadmore listview in flutter following the tutorial building-a-social-network-with-flutter:
Define a typedef function as a listview adapter, it return a widget for each item:
typedef Widget WidgetAdapter<T>(T t);
My base listview widget:
class LoadingListView<T> extends StatefulWidget{
...
final WidgetAdapter<T> widgetAdapter;
...
#override createState() => new _LoadingListViewState();
}
My base listview state:
class _LoadingListViewState<T> extends State<LoadingListView<T>> {
...
List<T> objects = [];
...
Widget itemBuilder(BuildContext context, int index) {
return widget.widgetAdapter != null ? widget.widgetAdapter(objects[index]);
: new Container();
}
}
In my UserList widget:
#override
Widget build(BuildContext context) {
Widget w;
w = new LoadingListView<User>(
request, widgetAdapter: adapt, pageSize: widget.pageSize, pageThreshold: widget.pageThreshold);
return w;
}
Widget adapt(User user){
return new UserItem(user); //return widget UserItem
}
And i am getting the error:
type '(User) => Widget' is not a subtype of type '(dynamic) => Widget'
How to fix it. Please help me. Thanks you very much.
There is a problem with createState() method in your StatefullWidget class.
Update your createState() method like this.
#override
State createState() => new _LoadingListViewState<T>()

Am not rebuilding objects on build() call

Despite flutter calling build (and printing the correct information as below), it doesn't seem to build new TaskWidgets (the print in TaskWidgetState's constructor is not called). This is creating some unusual behaviour in my application (for example, the persistence of deleted ListView items).
I have the following code:
class TaskWidget extends StatefulWidget {
TaskWidget({this.task, this.callToSave, this.callToDelete});
final Task task;
final Function callToSave;
final Function callToDelete;
#override
State<StatefulWidget> createState() {
return new TaskWidgetState(task, callToSave, callToDelete);
}
}
class TaskWidgetState extends State<TaskWidget>{
Task task;
Function toCallOnChange;
Function callToDelete;
TaskWidgetState(Task task, Function callToSave, Function callToDelete){
print("I'm a task widget for " + task.serialise().toString());
this.task = task;
toCallOnChange = callToSave;
this.callToDelete = callToDelete;
}
}
and
class ToDoListWidget extends State<ToDoList>{
List<Task> _toDo = new List<Task>();
...
#override
Widget build(BuildContext context) {
print("building");
return new Scaffold(
body: new ListView(
children: <Widget> [
generateCard(),
...
]
),
);
}
Widget generateCard() {
return new Card(
child: new Column (
children: generateWidgets()
),
...
);
}
List<Widget> generateWidgets() {
print("generating Widgets");
List<Task> tasks = getTasks();
List<Widget> widgets = new List<Widget>();
print("I have " + tasks.length.toString() + " widgets to build");
for(Task t in tasks) {
print(t.title);
TaskWidget widget = new TaskWidget(task: t, callToSave: saveList, callToDelete: deleteTask,);
widgets.add(widget);
}
return widgets;
}
}
Prints out:
building
I/flutter (28783): Returning for Daily
I/flutter (28783): // correct, undeleted task
but onscreen state doesn't reflect this
You're not using State and Stateful Widget properly.
How it works in flutter is that the Widget can be created many times, but there will most likely only be one instance of a State to go along with it.
It's a bit of an anti-pattern to have a constructor for a state.
Instead you should be doing something like this:
class TaskWidget extends StatefulWidget {
TaskWidget({this.task, this.callToSave, this.callToDelete});
final Task task;
final Function callToSave;
final Function callToDelete;
#override
State<StatefulWidget> createState() => new TaskWidgetState();
}
class TaskWidgetState extends State<TaskWidget>{
Widget build(Context context) {
// you can just use the widget.task, this is to illustrate.
var task = widget.task;
var callToSave = widget.callToSave;
var callToDelete = widget.calltoDelete;
}
}
This way, when the widget changes, your state will be re-built and will use whatever the updated values are that were passed into the widget.

Resources