I tried to build an Animated List in Flutter and I have been getting RangeError (index): Invalid value: Valid value range is empty: 0 when I run my code. Here is my code. I followed a tutorial and I used a similar process but I am getting errors while there is no error in the tutorial I followed. I have tried to add shrinkwrap to the animated list yet the problem persists.
import 'package:devopsnotepad/core/models/server/dummy_model.dart';
import 'package:devopsnotepad/core/models/server/server_model.dart';
import 'package:devopsnotepad/core/services/api_service.dart';
import 'package:devopsnotepad/ui/shared/colors.dart';
import 'package:devopsnotepad/ui/shared/edge_insets.dart';
import 'package:devopsnotepad/ui/shared/spacing.dart';
import 'package:devopsnotepad/ui/shared/text_styles.dart';
import 'package:devopsnotepad/ui/widgets/app_button.dart';
import 'package:devopsnotepad/ui/widgets/app_spinner.dart';
import 'package:devopsnotepad/ui/widgets/custom_app_bar.dart';
import 'package:devopsnotepad/ui/widgets/server_card_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:stacked/stacked.dart';
import 'server_list_viewmodel.dart';
class ServerListView extends StatefulWidget {
const ServerListView({Key? key}) : super(key: key);
@override
State<ServerListView> createState() => _ServerListViewState();
}
class _ServerListViewState extends State<ServerListView> {
//final List<Server> serverList;
final List<ServerList> serverList = List.from(servers);
List<Widget> animatedTiles = [];
final GlobalKey<AnimatedListState> _listkey = GlobalKey<AnimatedListState>();
final Tween<Offset> _offset =
Tween(begin: const Offset(1, 0), end: const Offset(0, 0));
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
addServer();
});
}
void addServer() {
Future loading = Future(() {});
for (var server in serverList) {
loading = loading.then((_) {
return Future.delayed(const Duration(milliseconds: 100), () {
animatedTiles.add(
ServerCard(serverItem: server, onTap: () {}),
);
_listkey.currentState!.insertItem(animatedTiles.length - 1);
});
});
}
}
@override
void dispose() {
super.dispose();
_controller.dispose();
}
@override
Widget build(BuildContext context) {
return ViewModelBuilder<ServerListViewModel>.reactive(
builder: (context, model, child) => Scaffold(
appBar: CustomAppBar(
title: "Servers",
actions: [
IconButton(
onPressed: () async {
showSearch(context: context, delegate: ServerSearch());
},
icon: const Icon(Icons.search),
),
IconButton(
onPressed: model.goToSettingsView,
icon: const Icon(Icons.settings),
),
],
),
body: serverList.isEmpty
//TODO: model.servers.isEmpty
? const _EmptyServer()
: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
verticalSpaceMini,
Expanded(
child: AnimatedList(
shrinkWrap: true,
padding: kEdgeInsetsAllMedium,
key: _listkey,
initialItemCount: serverList.length,
itemBuilder: (context, index, animation) {
return SlideTransition(
position: animation.drive(_offset),
child: Slidable(
endActionPane: ActionPane(
motion: const StretchMotion(),
children: [
SlidableAction(
onPressed: ((context) {}),
icon: Icons.delete,
backgroundColor: AppColor.kErrorColor,
label: "Delete",
),
]),
child: animatedTiles[index]),
);
},
),
),
],
),
floatingActionButton: AppButton(
title: 'Add new server',
backgroundColor: AppColor.kPrimaryColor.shade900,
width: 170,
height: 40,
trailingIcon: Icons.add_circle_outline,
onTap: model.goToAddServerView,
iconColor: Colors.white,
iconSize: 15,
),
),
viewModelBuilder: () => ServerListViewModel(),
);
}
}
class ServerSearch extends SearchDelegate {
@override
List<Widget> buildActions(BuildContext context) {
return [
IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
query = '';
},
),
];
}
@override
Widget buildLeading(BuildContext context) {
return IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
close(context, '');
},
);
}
@override
Widget buildResults(BuildContext context) => FutureBuilder(
future: APIService()
.get(route: '/server', queryParameters: {"device": "80988579"}),
builder: ((context, snapshot) {
if (snapshot.hasData) {
var test = DataResponse.fromJson(snapshot.data);
var serverList = test.servers;
if (query.isNotEmpty) {
var searchResult = serverList
.where((element) => element.name.toLowerCase().contains(query))
.toList();
if (searchResult.isEmpty) {
return const Center(child: Text('No result found'));
} else {
return ListView.builder(
itemCount: searchResult.length,
itemBuilder: (context, index) {
return ServerCard(
// endPoint: searchResult[index].name,
// ipAddress: searchResult[index].ipAddress,
// serverHealth: 'Excellent',
// serverName: searchResult[index].name,
onTap: () {});
},
);
}
} else {
return const SizedBox.shrink();
}
} else {
return const Center(
child: AppSpinner(),
);
}
}));
@override
Widget buildSuggestions(BuildContext context) {
return const SizedBox.shrink();
}
}
class _EmptyServer extends StatelessWidget {
const _EmptyServer();
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
verticalSpaceMassive,
const Image(
image: AssetImage('assets/images/add-notes.png'),
height: 160,
),
verticalSpaceMini,
Text(
'Empty Server List',
style: kHeading1TextStyle,
),
verticalSpaceMicro,
Center(
child: Text(
'You do not have any servers yet',
style: kBodyRegularTextStyle,
),
)
],
);
}
}
@Joel, actually in the very beginning, the animatedTiles is empty as it is being initialize after postFrame along with some dealy & you have only serverList isEmpty check that's why it is throwing out of range exception. So just add isEmpty check of both animatedTiles & serverList and accordingly handle the state it will work smooth.