title: How to check whether the custom painter has rendered the things implemented in its paint method in the device in flutter?
body: <p>I have tried to render a custom path in custom painter and tried to get the performance metrics (i.e., rendering time to draw the path for the specified data points) by using <code>widgetBinding.addPostFrameCallback()</code> in the build method to calculate the rendering time after the build method is executed. I was able to get the rendering time to draw the path soon after it gets rendered in the device.</p>
  <pre><code>@override
    Widget build(BuildContext context) {
      WidgetsBinding.instance.addPostFrameCallback(
        (_) {
          final DateTime endTime = DateTime.now();
          final Duration duration = endTime.difference(startTime!); // start time is initialized at the initState.
          print('After build : ${duration.inMilliseconds}');
        },
      );
      // other configurations
  
  }
  </code></pre>
  <p>However, when there are 500k of data points specified to render the path then, the statements in the <code>addPostFrameCallback</code> get executed before the path has rendered in the device. Kindly suggest a way to find the exact rendering time of the specified path once it gets rendered in the device.</p>
  <p>Note: I have used a physical device to test this case.</p>
  <p>Screen recording to depict this case</p>
  <p><img src="https://i.stack.imgur.com/ZKGMB.gif" alt="1" /></p>
  <p>full source code:</p>
  <pre><code>import 'dart:math';
  
  import 'package:flutter/material.dart';
  
  void main() {
    runApp(const MyApp());
  }
  
  DateTime? startTime;
  
  class MyApp extends StatelessWidget {
    const MyApp({Key? key}) : super(key: key);
  
    // This widget is the root of your application.
    @override
    Widget build(BuildContext context) {
      return MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: const MyHomePage(),
      );
    }
  }
  
  class MyHomePage extends StatefulWidget {
    const MyHomePage({Key? key}) : super(key: key);
  
    @override
    State&lt;MyHomePage&gt; createState() =&gt; _MyHomePageState();
  }
  
  class _MyHomePageState extends State&lt;MyHomePage&gt; {
    List&lt;ChartData&gt; dataList = [];
    var rng = Random();
  
    @override
    void initState() {
      dataList = List&lt;ChartData&gt;.generate(
          500000,
          (index) =&gt; ChartData(
              x: index.toDouble(), y: 10 + (rng.nextDouble() * (40 - 10))));
      super.initState();
    }
  
    @override
    Widget build(BuildContext context) {
      return Scaffold(
        backgroundColor: Colors.black,
        appBar: AppBar(),
        body: Container(
          color: Colors.white,
          child: SizedBox(
            height: 400,
            child: CustomWidget&lt;ChartData&gt;(
              dataSource: dataList,
              xValueMapper: (ChartData data, _) =&gt; data.x,
              yValueMapper: (ChartData data, _) =&gt; data.y,
            ),
          ),
        ),
      );
    }
  }
  
  /// Maps the index value.
  typedef ChartIndexedValueMapper&lt;R&gt; = R? Function(int index);
  
  /// Maps the data from data source.
  typedef ChartValueMapper&lt;T, R&gt; = R? Function(T datum, int index);
  
  class CustomWidget&lt;T&gt; extends StatefulWidget {
    const CustomWidget(
        {Key? key,
        required List&lt;T&gt; dataSource,
        required this.xValueMapper,
        required this.yValueMapper})
        : datasource = dataSource,
          super(key: key);
  
    final List&lt;T&gt; datasource;
    final ChartValueMapper&lt;T, num&gt;? xValueMapper;
    final ChartValueMapper&lt;T, num&gt;? yValueMapper;
  
    @override
    _CustomWidgetState createState() {
      // ignore: no_logic_in_create_state
      return _CustomWidgetState(
        datasource,
        xValueMapper != null
            ? (int index) =&gt; xValueMapper!(datasource[index], index)
            : null,
        yValueMapper != null
            ? (int index) =&gt; yValueMapper!(datasource[index], index)
            : null,
      );
    }
  }
  
  class _CustomWidgetState extends State&lt;CustomWidget&gt; {
    _CustomWidgetState(this.dataSource, this.xValueMap, this.yValueMap);
  
    final dynamic dataSource;
    final ChartIndexedValueMapper&lt;dynamic&gt;? xValueMap;
    final ChartIndexedValueMapper&lt;dynamic&gt;? yValueMap;
  
    late double _min, _max;
  
    List&lt;double&gt; xValues = [];
    List&lt;double&gt; yValues = [];
  
    @override
    void initState() {
      startTime = DateTime.now();
      super.initState();
    }
  
    void initChartPoints() {
      processData(dataSource);
      num min = double.maxFinite;
      num max = -double.maxFinite;
      for (var y in yValues) {
        min = min &gt; y ? y : min;
        max = max &lt; y ? y : max;
      }
  
      _min = min.toDouble();
      _max = max.toDouble();
    }
  
    @override
    Widget build(BuildContext context) {
      WidgetsBinding.instance.addPostFrameCallback(
        (_) {
          final DateTime endTime = DateTime.now();
          final Duration duration = endTime.difference(startTime!);
          print('After build : ${duration.inMilliseconds}');
        },
      );
      initChartPoints();
      return Container(
        child: CustomPaint(
          child: Container(),
          painter: LinePainter(xValues, yValues, _min, _max),
        ),
      );
    }
  
    void processData(dynamic data) {
      for (int i = 0; i &lt; data.length; i++) {
        xValues.add(xValueMap!(i));
        yValues.add(yValueMap!(i));
      }
    }
  }
  
  class LinePainter extends CustomPainter {
    final List&lt;double&gt; xValues, yValues;
    final double min, max;
    LinePainter(this.xValues, this.yValues, this.min, this.max);
  
    static double border = 10;
    final linePaint = Paint()
      ..color = Colors.blue
      ..strokeWidth = 1.0
      ..style = PaintingStyle.stroke;
  
    @override
    void paint(Canvas canvas, Size size) {
      if (xValues.length == yValues.length) {
        final clipRect = Rect.fromLTWH(0, 0, size.width, size.height);
        canvas.clipRect(clipRect);
        final drawHeight = size.height - 2.0 * border;
        final drawWidth = size.width - 2.0 * border;
        final hd = drawHeight / 5.0;
        final wd = drawWidth / xValues.length.toDouble();
        final height = hd * 3.0;
  
        final hr = height / (max - min);
        final left = border;
        final top = border;
        final c = Offset(left + wd / 2.0, top + height / 2.0);
        final points = computePoints(c, wd, height, hr);
        final path = computePath(points);
        canvas.drawPath(path, linePaint);
      }
    }
  
    List&lt;Offset&gt; computePoints(Offset c, double width, double height, double hr) {
      List&lt;Offset&gt; points = [];
      for (var yVal in yValues) {
        final yy = height - (yVal - min) * hr;
        final dp = Offset(c.dx, c.dy - height / 2.0 + yy);
        points.add(dp);
        c += Offset(width, 0);
      }
  
      return points;
    }
  
    Path computePath(List&lt;Offset&gt; points) {
      final Path path = Path();
  
      for (int i = 0; i &lt; points.length; i++) {
        final p = points[i];
        if (i == 0) {
          path.moveTo(p.dx, p.dy);
        } else {
          path.lineTo(p.dx, p.dy);
        }
      }
      return path;
    }
  
    @override
    bool shouldRepaint(covariant CustomPainter oldDelegate) {
      return true;
    }
  }
  
  class PointInfo {
    PointInfo({this.x, this.y});
  
    final dynamic x;
    final dynamic y;
  }
  
  class ChartData {
    ChartData({this.x, this.y});
  
    final double? x;
    final double? y;
  }
  
  </code></pre>