// Copyright 2021 The Flutter team. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // ignore_for_file: avoid_print import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:web_startup_analyzer/web_startup_analyzer.dart'; main() async { var analyzer = WebStartupAnalyzer(additionalFrameCount: 10); print(json.encode(analyzer.startupTiming)); analyzer.onFirstFrame.addListener(() { print(json.encode({'firstFrame': analyzer.onFirstFrame.value})); }); analyzer.onFirstPaint.addListener(() { print(json.encode({ 'firstPaint': analyzer.onFirstPaint.value?.$1, 'firstContentfulPaint': analyzer.onFirstPaint.value?.$2, })); }); analyzer.onAdditionalFrames.addListener(() { print(json.encode({ 'additionalFrames': analyzer.onAdditionalFrames.value, })); }); runApp( WebStartupAnalyzerSample( analyzer: analyzer, ), ); } class WebStartupAnalyzerSample extends StatelessWidget { final WebStartupAnalyzer analyzer; const WebStartupAnalyzerSample({super.key, required this.analyzer}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter web app timing', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.green.shade100), ), home: WebStartupAnalyzerScreen(analyzer: analyzer), ); } } class WebStartupAnalyzerScreen extends StatefulWidget { final WebStartupAnalyzer analyzer; const WebStartupAnalyzerScreen({super.key, required this.analyzer}); @override State createState() => _WebStartupAnalyzerScreenState(); } class _WebStartupAnalyzerScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.amber.shade50, body: Align( alignment: Alignment.topCenter, child: Container( margin: const EdgeInsets.all(8.0), constraints: const BoxConstraints(maxWidth: 400), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8.0), ), child: ListenableBuilder( listenable: widget.analyzer.onChange, builder: (BuildContext context, child) { return ListView( shrinkWrap: true, children: [ TimingWidget( name: 'DCL', timingMs: widget.analyzer.domContentLoaded, ), TimingWidget( name: 'Load entrypoint', timingMs: widget.analyzer.loadEntrypoint, ), TimingWidget( name: 'Initialize engine', timingMs: widget.analyzer.initializeEngine, ), TimingWidget( name: 'Run app', timingMs: widget.analyzer.appRunnerRunApp, ), if (widget.analyzer.firstFrame != null) TimingWidget( name: 'First frame', timingMs: widget.analyzer.firstFrame!, ), if (widget.analyzer.firstPaint != null) TimingWidget( name: 'First paint', timingMs: widget.analyzer.firstPaint!), if (widget.analyzer.firstContentfulPaint != null) TimingWidget( name: 'First contentful paint', timingMs: widget.analyzer.firstContentfulPaint!), if (widget.analyzer.additionalFrames != null) ...[ for (var i in widget.analyzer.additionalFrames!) TimingWidget(name: 'Frame', timingMs: i.toDouble()), ] else TextButton( child: const Text('Trigger frames'), onPressed: () {}, ), ], ); }, ), ), ), ); } } class TimingWidget extends StatelessWidget { final String name; final double timingMs; const TimingWidget({ super.key, required this.name, required this.timingMs, }); @override Widget build(BuildContext context) { return ListTile( title: Text( name, style: const TextStyle(fontSize: 18), overflow: TextOverflow.ellipsis, ), trailing: Text( '${timingMs.truncate()}ms', style: const TextStyle(fontSize: 18), ), ); } }