I have a ListView.builder
inside a page that contains multiple tabs.
After a list item is removed (by swiping left or right), going to another tab and then going back to the first tab results in a range error.
Going to another page and coming back does not result in an error. Perhaps going to another page causes the builder to re-build again, and tab doesn't?
Am I doing something wrong here or is this a bug with TabView
? Fully reproducible code below.
To reproduce the issue swipe an item to delete it, then go to second tab and then back to first tab.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'List Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
List<int> list = [1, 2, 3, 4, 5];
return DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
bottom: const TabBar(
tabs: [
Tab(icon: Icon(Icons.recycling_outlined)),
Tab(icon: Icon(Icons.directions_transit)),
Tab(icon: Icon(Icons.directions_bike)),
],
),
),
body: TabBarView(
children: [
ListView.builder(
padding: const EdgeInsets.all(4.0),
itemCount: list.length,
itemBuilder: (context, index) {
var item = list[index];
return Dismissible(
key: Key(item.toString()),
onDismissed: (direction) {
list.removeAt(index);
},
child: ListTile(
title: Text(item.toString()),
),
);
},
),
const Icon(Icons.directions_transit),
const Icon(Icons.directions_bike),
],
),
),
);
}
}
CodePudding user response:
Separating the ListView
solves tree the issue. To save the state of list, I am using AutomaticKeepAliveClientMixin
, you can use others state-management property.
body: TabBarView(
children: [
FirstChild(),
const Icon(Icons.directions_transit),
const Icon(Icons.directions_bike),
],
),
ListView
class FirstChild extends StatefulWidget {
FirstChild({Key? key}) : super(key: key);
@override
State<FirstChild> createState() => _FirstChildState();
}
class _FirstChildState extends State<FirstChild>
with AutomaticKeepAliveClientMixin {
List<int> list = [1, 2, 3, 4, 5];
@override
Widget build(BuildContext context) {
super.build(context);
return ListView.builder(
padding: const EdgeInsets.all(4.0),
itemCount: list.length,
itemBuilder: (context, index) {
var item = list[index];
return Dismissible(
key: Key(list[index].toString()),
onDismissed: (direction) {
setState(() {
list.removeAt(index);
});
},
child: ListTile(
title: Text(item.toString()),
),
);
},
);
}
@override
bool get wantKeepAlive => true;
}
CodePudding user response:
Use setState to update list and also remove List declaration from build method
class _MyHomePageState extends State<MyHomePage> {
List<int> list = [1, 2, 3, 4, 5];
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
bottom: const TabBar(
tabs: [
Tab(icon: Icon(Icons.recycling_outlined)),
Tab(icon: Icon(Icons.directions_transit)),
Tab(icon: Icon(Icons.directions_bike)),
],
),
),
body: TabBarView(
children: [
ListView.builder(
padding: const EdgeInsets.all(4.0),
itemCount: list.length,
itemBuilder: (context, index) {
var item = list[index];
return Dismissible(
key: Key(item.toString()),
onDismissed: (direction) {
setState(() {
list.removeAt(index);
});
},
child: ListTile(
title: Text(item.toString()),
),
);
},
),
const Icon(Icons.directions_transit),
const Icon(Icons.directions_bike),
],
),
),
);
}
}