Cupertino DateTime picker interfering with scroll behaviour

297

In case anyone comes looking for the same issue, this turned out to be anctual bug addressed in the current master channel

https://github.com/flutter/flutter/issues/106737

Share:
297
its_broke_again
Author by

its_broke_again

Updated on December 01, 2022

Comments

  • its_broke_again
    its_broke_again 6 months

    I have an app that includes a series of questionnaires. One of which is 5 pages long, and each page is essentially a SingleChildScrollView with a column containing 5-10 items of the same custom widget, apart from the first page which also includes 2 cupertino time pickers. The custom widget itself is a column with 5 Rows - The top row is a Text widget (the question) and rows 2-5 are rows each of which contains a possible answer (text widget) and a radio button.

    However, when going through the questionnaire when the first page initially loads, I can scroll up and down fine, but after around 5 scrolls from top to bottom, the scrolling becomes increasingly janky. The performance profile shows the scrolling goes from 120fps in the first few scrolls to 10fps or lower. Now I spent a while assuming this was a scrolling issue, but I've also noticed that if I press 'back' on the AppBar the page transition animation is also very janky and slow.

    On testing, it seems that it is the Cupertino pickers. I have made a sample app (below) to mock up the text presentation of the questionnaire but with no dependencies. If the app is run with the pickers commented out and you scroll up and down repeatedly, the frame rate is fine. If the pickers are not commented out and show up in the app, then repeated scrolling causes a progressive fall in the fps which eventually is very janky. This is on a real iOS device (if anything the effect is much less on the simulator) and doesn't require any interaction with the picker (no need to pick a time).

    Anyone have any idea why this is happening?

    import 'package:flutter/cupertino.dart';
    import 'package:flutter/material.dart';
    
    void main() {
      runApp(const MyApp());
    }
    
    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(title: 'Flutter Demo Home Page'),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      const MyHomePage({Key? key, required this.title}) : super(key: key);
      final String title;
    
      @override
      State<MyHomePage> createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      @override
      Widget build(BuildContext context) {
    
    
        return Scaffold(
          appBar: AppBar(
            title: const  Text('Scroll Test'),
          ),
          body: SafeArea(
            child: SingleChildScrollView(
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  const Padding(
                    padding: EdgeInsets.only(top: 8.0),
                    child: Text( 'This is an instruction to make sense of the rest of the questions'
                    ),
                  ),
                  SizedBox(
                    width: MediaQuery.of(context).size.width * .9,
                    height: MediaQuery.of(context).size.height * .2,
                    child: CupertinoDatePicker(
                        key: UniqueKey(),
                        use24hFormat: true,
                        onDateTimeChanged: (DateTime newtime) {
    
                        },
                        initialDateTime: DateTime(2020, 3, 6, 8),
                        maximumDate: DateTime(2020, 3, 6, 20),
                        mode: CupertinoDatePickerMode.time),
                  ),
                  TestWidget(),
                  TestWidget(),
                  TestWidget(),
                  TestWidget(),
                  SizedBox(
                    width: MediaQuery.of(context).size.width * .9,
                    height: MediaQuery.of(context).size.height * .2,
                    child: CupertinoDatePicker(
                        key: UniqueKey(),
                        use24hFormat: true,
                        onDateTimeChanged: (DateTime newtime) {
    
                        },
                        initialDateTime: DateTime(2020, 3, 6, 8),
                        maximumDate: DateTime(2020, 3, 6, 20),
                        mode: CupertinoDatePickerMode.time),
                  ),
                  TestWidget(),
                  TestWidget(),
                  TestWidget(),
    
                  TextButton(
                      onPressed: () {
                      },
                      child: const Text('Next'))
                ],
              ),
            ),
          ),
        );
      }
    }
    
    
    
    class TestWidget extends StatelessWidget {
    
      final String quest;
      final String opt1;
      final String opt2;
      final String opt3;
      final String opt4;
    
    
      const TestWidget({Key? key,
        this.quest = 'Here  is a question with some quite long text.  And even a second sentence',
        this.opt1 = "option 1",
        this.opt2 = 'option 2',
        this.opt3 = 'Option 3',
        this.opt4 = 'Option 4'
      }) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
    
        return SizedBox(
          height: 300,
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              Text(quest),
              Row(
                children: [
                  Expanded(
                      flex: 8,
                      child: Text(opt1)),
                  Expanded(
                    flex: 2,
                    child: Text('x'),
                  )
                ],
              ),
              Row(
                children: [
                  Expanded(
                      flex: 8,
                      child: Text(opt2)),
                  Expanded(
                    flex: 2,
                    child: Text('x'),
                  )
                ],
    
              ),
    
              Row(
                children: [
                  Expanded(
                      flex: 8,
                      child: Text(opt3)),
                  Expanded(
                    flex: 2,
                    child: Text('x'),
                  )
                ],
    
              ),
              Row(
                children: [
                  Expanded(
                      flex: 8,
                      child: Text(opt4)),
                  Expanded(
                    flex: 2,
                    child: Text('x'),
                  )
    
                ],
              ),
    
              const Padding(
                padding: EdgeInsets.only(top:8.0),
                child: Divider(),
              )
            ],
          ),
        );
      }
    }