Skip to content

[Sheet] SheetController.(relative) animateTo is broken #412

@vishna

Description

@vishna

What's wrong

Calling SheetController.relativeAnimateTo does nothing in conditions where it should expand

Steps to repro

Here's the "minimal" code I managed to put together to illustrate the issue

import 'package:flutter/material.dart';
import 'package:sheet/sheet.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Flutter Demo',
      home: HomeAndSheet(),
    );
  }
}

class HomeAndSheet extends StatefulWidget {
  const HomeAndSheet({super.key});

  @override
  State<HomeAndSheet> createState() => _HomeAndSheetState();
}

class _HomeAndSheetState extends State<HomeAndSheet> {
  Widget emptyPage(BuildContext context) => const Placeholder();
  Widget pageWithList(BuildContext context) => const PageWithList();
  List<WidgetBuilder> pages() => [emptyPage, pageWithList]; // BAD CASE
  // List<WidgetBuilder> pages() => [pageWithList, emptyPage]; // GOOD CASE

  late WidgetBuilder pageBuilder;
  final sheetController = SheetController();

  @override
  void initState() {
    super.initState();
    setPage(0);
  }

  void setPage(int pageNumber) {
    setState(() {
      pageBuilder = pages()[pageNumber];
    });
  }

  @override
  Widget build(BuildContext context) {
    final sheetHeight = MediaQuery.sizeOf(context).height - 100;

    return Stack(
      children: [
        HomePage(
          title: 'Flutter Demo Home Page',
          onTap: () async {
            debugPrint("SHOW");
            setPage(1);
            await sheetController.relativeAnimateTo(
              1,
              duration: const Duration(milliseconds: 400),
              curve: Curves.easeOutQuart,
            );
            setPage(0);
          },
        ),
        Sheet(
          controller: sheetController,
          fit: SheetFit.loose,
          initialExtent: 0,
          child: SizedBox(
            height: sheetHeight,
            child: Builder(
              builder: pageBuilder,
            ),
          ),
        )
      ],
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({super.key, required this.title, required this.onTap});

  final String title;
  final VoidCallback onTap;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(title),
      ),
      body: const Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Press the button to reveal the sheet',
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: onTap,
        child: const Icon(Icons.border_bottom),
      ),
    );
  }
}

class PageWithList extends StatelessWidget {
  const PageWithList({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.amber,
      appBar: AppBar(
        backgroundColor: Colors.amberAccent,
        title: const Text("Oh Sheet!"),
      ),
      body: ListView(
        children: ListTile.divideTiles(
          context: context,
          tiles: List<Widget>.generate(
              100,
              (int index) => ListTile(
                    title: Text('Item $index'),
                  )),
        ).toList(),
      ),
    );
  }
}

Launch simulator, press FAB button and watch the sheet not appear. If you switch in code from // BAD to // GOOD, hot restart, tap the button again the sheet will appear 😐

What's happening

BAD CASE

SHOW
relativeAnimateTo(1.0, 0:00:00.400000, Cubic(0.17, 0.84, 0.44, 1.00))
pixelsFromRelativeOffset (offset = 1.0, minScrollExtent = 0.0, maxScrollExtent = 832.0)
beginActivity(DrivenScrollActivity#cc5e8(AnimationController#f7883(▶ 0.000; for DrivenScrollActivity)))
setPixels(0.0)
updateRenderObject <--- We put PageWithList widget inside the sheet
performLayout START
beginActivity(IdleScrollActivity#84f3a)
applyContentDimensions(0.0, 832.0)
performLayout END
updateRenderObject <--- We put Placeholder widget inside the sheet
performLayout START
applyContentDimensions(0.0, 832.0)
performLayout END

GOOD

SHOW
relativeAnimateTo(1.0, 0:00:00.400000, Cubic(0.17, 0.84, 0.44, 1.00))
pixelsFromRelativeOffset (offset = 1.0, minScrollExtent = 0.0, maxScrollExtent = 832.0)
beginActivity(DrivenScrollActivity#bc7fc(AnimationController#596a8(▶ 0.000; for DrivenScrollActivity)))
setPixels(0.0)
updateRenderObject <--- We put Placeholder widget inside the sheet
performLayout START
applyContentDimensions(0.0, 832.0)
performLayout END
setPixels(381.951171875)
setPixels(460.16555404663086)
setPixels(527.0387233543396)
setPixels(582.0008573913574)
setPixels(625.2629356384277)
setPixels(694.4747106933594)
setPixels(742.2553861427307)
setPixels(761.226918964386)
setPixels(776.0444121360779)
setPixels(788.8407738685609)
setPixels(799.1720542907715)
setPixels(807.4257688903808)
setPixels(814.3350498962402)
setPixels(819.6619298744201)
setPixels(823.9420788574218)
setPixels(827.145937626362)
setPixels(829.4243977189063)
setPixels(830.9135014343261)
setPixels(831.7354572105407)
setPixels(832.0)
beginActivity(IdleScrollActivity#2b86b)
updateRenderObject <--- We put PageWithList widget inside the sheet
performLayout START
beginActivity(IdleScrollActivity#b08ce)
applyContentDimensions(0.0, 832.0)
performLayout END

What I think is happening

Whenever animateTo is called and a new widget is put at the same time, a performLayout inside RenderSheetViewport will have to be called. In GOOD case this manages to happen before beginActivity(IdleScrollActivity#2b86b) so the sheet "knows" how to expand. In BAD CASE out widget seems to be more complex and the dimensions are not delivered on time and the animation doesn't execute properly.

I managed to workaround this by putting a delay of 150ms before animateTo. This is obviously a non ideal HACK. Other possible hack is to use "Placeholder" widget and only replace the widget once the animation is ongoing. Related -> #360

Investigating a proper fix, if anyone has any pointers - let me know.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions