Today we’re going to learn how to test your Flutter apps, specifically the performance of the apps. When it comes to mobile apps, user experience is everything. Users want apps to have smooth scrolling and meaningful animations that are devoid of jank, or stuttering, or skipped frames.
There are two ways. The first is to personally test the app on various devices. While such a strategy may work for a small app, it becomes increasingly difficult as the program develops in size. Run an integration test that executes a specific operation and records a performance timeline instead. Then, review the data to see if a certain section of the app needs to be enhanced. Thus, we’re going to learn how to construct a test that stores a summary of the results to a local file and records a performance timeline while doing a certain activity.
Running Test in Your Flutter Apps
Suppose you have a Flutter app that scrolls through a list. You may need The scrollUntilVisible() method of the WidgetTester class scrolls through a list until a specific widget is visible. This is useful because the height of the list items varies based on the device. The scrollUntilVisible() method continually scrolls through a list of things until it discovers what it’s looking for, rather than assuming that you know the height of all the items in a list or that a certain widget is rendered on all devices.
The code below demonstrates how to utilize the scrollUntilVisible() method to search the list for a specific item. This code is located in the file test/widget test.dart.
// Your App Sample
import 'package:flutter/material.dart';
void main() {
runApp(MyApp(
items: List<String>.generate(10000, (i) => 'Item $i'),
));
}
class MyApp extends StatelessWidget {
final List<String> items;
const MyApp({super.key, required this.items});
@override
Widget build(BuildContext context) {
const title = 'Long List';
return MaterialApp(
title: title,
home: Scaffold(
appBar: AppBar(
title: const Text(title),
),
body: ListView.builder(
key: const Key('long_list'),
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(
items[index],
key: Key('item_${index}_text'),
),
);
},
),
),
);
}
}
// widget_tester
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:scrolling/main.dart';
void main() {
testWidgets('Counter increments smoke test', (tester) async {
await tester.pumpWidget(MyApp(
items: List<String>.generate(10000, (i) => 'Item $i'),
));
final listFinder = find.byType(Scrollable);
final itemFinder = find.byKey(const ValueKey('item_50_text'));
await tester.scrollUntilVisible(
itemFinder,
500.0,
scrollable: listFinder,
);
expect(itemFinder, findsOneWidget);
});
}
You will be able to run it with the command below.
flutter test test/widget_test.dart
Record & Save the Test Result
Additionally, record the app’s performance as it scrolls through the list next. Use the IntegrationTestWidgetsFlutterBinding class’s traceAction() function to complete this operation.
Besides, this method executes the specified function and creates a Timeline with detailed information on the app’s performance. This example shows a function that scrolls through a list of things and displays a specific item. When the function is finished, the traceAction() function generates a report data Map containing the Timeline.
Consequently, when performing several traceActions, specify the reportKey. All Timelines are kept with the key timeline by default; in this case, the reportKey has been modified to scroll.
Lastly, don’t forget to specify the reportKey. All timelines are kept with the key timeline by default; in this case, the reportKey has been modified to scroll.
await binding.traceAction(
() async {
await tester.scrollUntilVisible(
itemFinder,
500.0,
scrollable: listFinder,
);
},
reportKey: 'scrolling_timeline',
);
Finally, create a file called perf driver.dart in the test driver folder and add the following code to it to record the results.
import 'package:flutter_driver/flutter_driver.dart' as driver;
import 'package:integration_test/integration_test_driver.dart';
Future<void> main() {
return integrationDriver(
responseDataCallback: (data) async {
if (data != null) {
final timeline = driver.Timeline.fromJson(data['scrolling_timeline']);
final summary = driver.TimelineSummary.summarize(timeline);
await summary.writeTimelineToFile(
'scrolling_timeline',
pretty: true,
includeSummary: true,
);
}
},
);
}
Run & Review The Test In Flutter
More specifically, the —profile option instructs the compiler to compile the app for “profile mode” rather than “debug mode,” ensuring that the benchmark result is more representative of what end users will see.
Additionally, when using a mobile device or emulator, use the —no-dds option. This option disables the Dart Development Service (DDS), preventing it from being accessed from your computer.
flutter drive \
--driver=test_driver/perf_driver.dart \
--target=integration_test/scrolling_test.dart \
--profile
--no-dds
Following the successful completion of the test, the build directory at the root of the project contains two files. which is the scrolling_summary.timeline_summary.json
contains the summary and the scrolling_timeline.timeline.json
contains the complete timeline data.
Conclusion
To conclude, the focal point of this blog is why we need to test our apps is that the users want apps to have smooth scrolling and meaningful animations that are devoid of jank, or stuttering, or skipped frames. Then we run an integration test that executes a specific operation and records a performance timeline. We also review the data to see if a certain section of the app needs to be enhanced. To find a similar app that does execute the test, you may check it here. Additionally, you may also check the official documentation. Thank you for reading. Subscribe to my weekday blogs here.