Skip to content

Commit 89b2102

Browse files
Merge pull request #1 from SyncfusionExamples/933157
How to load data asynchronously with Bloc for paging in Flutter DataTable
2 parents 1362901 + 9c5c03b commit 89b2102

File tree

125 files changed

+5128
-2
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

125 files changed

+5128
-2
lines changed

.gitignore

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Miscellaneous
2+
*.class
3+
*.log
4+
*.pyc
5+
*.swp
6+
.DS_Store
7+
.atom/
8+
.build/
9+
.buildlog/
10+
.history
11+
.svn/
12+
.swiftpm/
13+
migrate_working_dir/
14+
15+
# IntelliJ related
16+
*.iml
17+
*.ipr
18+
*.iws
19+
.idea/
20+
21+
# The .vscode folder contains launch configuration and tasks you configure in
22+
# VS Code which you may wish to be included in version control, so this line
23+
# is commented out by default.
24+
#.vscode/
25+
26+
# Flutter/Dart/Pub related
27+
**/doc/api/
28+
**/ios/Flutter/.last_build_id
29+
.dart_tool/
30+
.flutter-plugins
31+
.flutter-plugins-dependencies
32+
.pub-cache/
33+
.pub/
34+
/build/
35+
36+
# Symbolication related
37+
app.*.symbols
38+
39+
# Obfuscation related
40+
app.*.map.json
41+
42+
# Android Studio will place build artifacts here
43+
/android/app/debug
44+
/android/app/profile
45+
/android/app/release

README.md

Lines changed: 302 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,302 @@
1-
# How-to-load-data-asynchronously-with-Bloc-for-paging-in-Flutter-DataTable
2-
This demo shows how to load data asynchronously with Bloc for paging in Flutter DataTable?
1+
# How to load data asynchronously with Bloc for paging in Flutter DataTable?
2+
3+
In this article, we will show you how to load data asynchronously with Bloc for paging in [Flutter DataTable](https://www.syncfusion.com/flutter-widgets/flutter-datagrid).
4+
5+
## Steps to asynchronous data loading using Bloc:
6+
7+
### Step 1: Creating the Bloc for Data Fetching
8+
9+
The Bloc (Business Logic Component) is responsible for managing state and handling asynchronous data loading. Bloc uses events to trigger data fetching. Here, we define a FetchEmployees event that specifies the range of data to fetch. This event takes startIndex and endIndex as parameters to determine the required records. This Bloc fetches a batch of employees asynchronously when a new page is requested.
10+
11+
```dart
12+
abstract class EmployeeEvent {}
13+
14+
class FetchEmployees extends EmployeeEvent {
15+
final int startIndex;
16+
final int endIndex;
17+
18+
FetchEmployees({required this.startIndex, required this.endIndex});
19+
}
20+
21+
abstract class EmployeeState {}
22+
23+
class EmployeeInitial extends EmployeeState {
24+
final int totalCount = 60;
25+
}
26+
27+
class EmployeeLoaded extends EmployeeState {
28+
final List<Employee> employees;
29+
30+
EmployeeLoaded({required this.employees});
31+
}
32+
33+
class EmployeeError extends EmployeeState {
34+
final String error;
35+
36+
EmployeeError({required this.error});
37+
}
38+
39+
class EmployeeBloc extends Bloc<EmployeeEvent, EmployeeState> {
40+
static const int totalCount = 60;
41+
final List<String> names = [
42+
'Alice Johnson',
43+
'Bob Smith',
44+
'Charlie Brown',
45+
'David Wilson',
46+
'Emma Davis',
47+
'Frank Miller',
48+
'Grace Lee',
49+
'Hannah White',
50+
'Isaac Clark',
51+
'Jack Turner',
52+
'Katherine Hall',
53+
'Liam Scott',
54+
'Mia Young',
55+
'Nathan Adams',
56+
'Olivia Baker',
57+
'Paul Carter',
58+
'Quinn Murphy',
59+
'Rachel Evans',
60+
'Samuel Collins',
61+
'Taylor Martin'
62+
];
63+
64+
final List<String> designations = [
65+
'Software Engineer',
66+
'Senior Developer',
67+
'Project Manager',
68+
'Business Analyst',
69+
'QA Engineer',
70+
'UI/UX Designer',
71+
'Database Administrator',
72+
'System Architect',
73+
'HR Manager',
74+
'Technical Lead'
75+
];
76+
77+
EmployeeBloc() : super(EmployeeInitial()) {
78+
on<FetchEmployees>((event, emit) async {
79+
try {
80+
// Simulate network delay.
81+
await Future.delayed(const Duration(seconds: 2));
82+
83+
// Ensure endIndex does not exceed total count.
84+
int adjustedEndIndex =
85+
event.endIndex > totalCount ? totalCount : event.endIndex;
86+
87+
final random = Random();
88+
89+
// Mock employee data generation.
90+
final employees = List.generate(
91+
adjustedEndIndex - event.startIndex,
92+
(index) => Employee(
93+
event.startIndex + index + 1,
94+
names[random.nextInt(names.length)],
95+
designations[random.nextInt(designations.length)],
96+
random.nextInt(5000) + 3000,
97+
),
98+
);
99+
100+
// Emit the loaded state with employees and the total count.
101+
emit(EmployeeLoaded(employees: employees));
102+
} catch (e) {
103+
emit(EmployeeError(error: e.toString()));
104+
}
105+
});
106+
}
107+
}
108+
```
109+
110+
### Step 2: Implementing Data Source
111+
112+
The [DataGridSource](https://pub.dev/documentation/syncfusion_flutter_datagrid/latest/datagrid/DataGridSource-class.html) class is responsible for fetching data and managing the data grid's rows. It uses a [StreamSubscription](https://api.flutter.dev/flutter/dart-async/StreamSubscription-class.html) to listen for state changes from the EmployeeBloc. When a page change is requested, it fetches the required data asynchronously and updates the data grid's rows. The [handlePageChange](https://pub.dev/documentation/syncfusion_flutter_datagrid/latest/datagrid/DataGridSource/handlePageChange.html) method ensures that data is fetched only when necessary, preventing duplicate fetch calls.
113+
114+
```dart
115+
class EmployeeDataSource extends DataGridSource {
116+
final BuildContext context;
117+
List<Employee> _employees = [];
118+
List<DataGridRow> _dataGridRows = [];
119+
bool _isLoading = false; // Flag to track loading state
120+
StreamSubscription<EmployeeState>? _streamSubscription;
121+
122+
EmployeeDataSource(this.context);
123+
124+
@override
125+
List<DataGridRow> get rows => _dataGridRows;
126+
127+
Future<void> _fetchData(int startIndex, int endIndex) async {
128+
// If a fetch is already in progress, prevent another fetch.
129+
if (_isLoading) return;
130+
131+
_isLoading = true;
132+
loadingController.add(_isLoading);
133+
134+
final completer = Completer<void>();
135+
136+
// Dispatch the fetch event to the bloc using BuildContext.
137+
BlocProvider.of<EmployeeBloc>(context)
138+
.add(FetchEmployees(startIndex: startIndex, endIndex: endIndex));
139+
140+
// Cancel the previous stream subscription before creating a new one.
141+
await _streamSubscription?.cancel();
142+
143+
// Create a new stream subscription for the current page request.
144+
_streamSubscription =
145+
BlocProvider.of<EmployeeBloc>(context).stream.listen((state) {
146+
if (!context.mounted) return;
147+
if (state is EmployeeLoaded) {
148+
_employees = state.employees;
149+
_buildRows();
150+
completer.complete();
151+
_isLoading = false;
152+
loadingController.add(_isLoading);
153+
} else if (state is EmployeeError) {
154+
completer.completeError(state.error);
155+
_isLoading = false;
156+
}
157+
});
158+
159+
return completer.future;
160+
}
161+
162+
void _buildRows() {
163+
_dataGridRows = _employees.map<DataGridRow>((e) {
164+
return DataGridRow(cells: [
165+
DataGridCell<int>(columnName: 'id', value: e.id),
166+
DataGridCell<String>(columnName: 'name', value: e.name),
167+
DataGridCell<String>(columnName: 'designation', value: e.designation),
168+
DataGridCell<int>(columnName: 'salary', value: e.salary),
169+
]);
170+
}).toList();
171+
}
172+
173+
@override
174+
Future<bool> handlePageChange(int oldPageIndex, int newPageIndex) async {
175+
// Prevent duplicate fetch calls.
176+
if (_isLoading) return false;
177+
178+
int startIndex = newPageIndex * _rowsPerPage;
179+
int endIndex = (startIndex + _rowsPerPage).clamp(0, totalCount);
180+
181+
// Ensure startIndex does not exceed totalCount.
182+
if (startIndex >= totalCount) {
183+
startIndex = max(0, totalCount - _rowsPerPage);
184+
}
185+
186+
await _fetchData(startIndex, endIndex);
187+
notifyListeners();
188+
189+
return true;
190+
}
191+
192+
@override
193+
DataGridRowAdapter buildRow(DataGridRow row) {
194+
return DataGridRowAdapter(
195+
cells: row.getCells().map<Widget>((e) {
196+
return Container(
197+
alignment: Alignment.center,
198+
padding: EdgeInsets.all(8.0),
199+
child: Text(e.value.toString()),
200+
);
201+
}).toList(),
202+
);
203+
}
204+
205+
void updateDataGriDataSource() {
206+
notifyListeners();
207+
}
208+
209+
// Dispose the stream subscription when the data source is disposed.
210+
@override
211+
void dispose() {
212+
_streamSubscription?.cancel();
213+
super.dispose();
214+
}
215+
}
216+
```
217+
218+
### Step 3: Creating SfDataGrid and SfDataPager
219+
220+
Initialize the [SfDataGrid](https://pub.dev/documentation/syncfusion_flutter_datagrid/latest/datagrid/SfDataGrid-class.html) and [SfDataPager](https://pub.dev/documentation/syncfusion_flutter_datagrid/latest/datagrid/SfDataPager-class.html) widget with all the necessary properties. The [StreamBuilder](https://api.flutter.dev/flutter/widgets/StreamBuilder-class.html) listens to the loading state and displays a loading indicator when data is being fetched.
221+
222+
```dart
223+
@override
224+
Widget build(BuildContext context) {
225+
return Scaffold(
226+
appBar: AppBar(
227+
title: const Text('Syncfusion DataGrid with Bloc'),
228+
),
229+
body: Column(
230+
children: [
231+
Expanded(
232+
child: StreamBuilder(
233+
stream: loadingController.stream,
234+
builder: (context, snapshot) {
235+
return Stack(children: [
236+
SfDataGrid(
237+
source: _employeeDataSource,
238+
columnWidthMode: ColumnWidthMode.fill,
239+
columns: <GridColumn>[
240+
GridColumn(
241+
columnName: 'id',
242+
label: Container(
243+
padding: EdgeInsets.all(8.0),
244+
alignment: Alignment.center,
245+
child: Text(
246+
'ID',
247+
)),
248+
),
249+
GridColumn(
250+
columnName: 'name',
251+
label: Container(
252+
padding: EdgeInsets.all(8.0),
253+
alignment: Alignment.center,
254+
child: Text(
255+
'Name',
256+
)),
257+
),
258+
GridColumn(
259+
columnName: 'designation',
260+
label: Container(
261+
padding: EdgeInsets.all(8.0),
262+
alignment: Alignment.center,
263+
child: Text(
264+
'Designation',
265+
)),
266+
),
267+
GridColumn(
268+
columnName: 'salary',
269+
label: Container(
270+
padding: EdgeInsets.all(8.0),
271+
alignment: Alignment.center,
272+
child: Text(
273+
'Salary',
274+
)),
275+
),
276+
],
277+
),
278+
if (snapshot.data == true)
279+
const Center(
280+
child: CircularProgressIndicator(),
281+
),
282+
]);
283+
}),
284+
),
285+
SfDataPager(
286+
delegate: _employeeDataSource,
287+
pageCount: (totalCount / _rowsPerPage).ceilToDouble(),
288+
availableRowsPerPage: [10, 20, 30],
289+
onRowsPerPageChanged: (int? rowsPerPage) {
290+
setState(() {
291+
_rowsPerPage = rowsPerPage!;
292+
_employeeDataSource.updateDataGriDataSource();
293+
});
294+
},
295+
),
296+
],
297+
),
298+
);
299+
}
300+
```
301+
302+
You can download this example on [GitHub](https://github.com/SyncfusionExamples/How-to-load-data-asynchronously-with-Bloc-for-paging-in-Flutter-DataTable).

android/.gitignore

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
gradle-wrapper.jar
2+
/.gradle
3+
/captures/
4+
/gradlew
5+
/gradlew.bat
6+
/local.properties
7+
GeneratedPluginRegistrant.java
8+
9+
# Remember to never publicly share your keystore.
10+
# See https://flutter.dev/to/reference-keystore
11+
key.properties
12+
**/*.keystore
13+
**/*.jks

0 commit comments

Comments
 (0)