fixes isAlwaysShown material scrollbar.dart (#54128)

diff --git a/packages/flutter/lib/src/cupertino/scrollbar.dart b/packages/flutter/lib/src/cupertino/scrollbar.dart
index 2143dc7..4ae7b94 100644
--- a/packages/flutter/lib/src/cupertino/scrollbar.dart
+++ b/packages/flutter/lib/src/cupertino/scrollbar.dart
@@ -238,15 +238,7 @@
         ..color = CupertinoDynamicColor.resolve(_kScrollbarColor, context)
         ..padding = MediaQuery.of(context).padding;
     }
-    WidgetsBinding.instance.addPostFrameCallback((Duration duration) {
-      if (widget.isAlwaysShown) {
-        assert(widget.controller != null);
-        // Wait one frame and cause an empty scroll event.  This allows the
-        // thumb to show immediately when isAlwaysShown is true.  A scroll
-        // event is required in order to paint the thumb.
-        widget.controller.position.didUpdateScrollPositionBy(0);
-      }
-    });
+    _triggerScrollbar();
   }
 
   @override
@@ -254,7 +246,7 @@
     super.didUpdateWidget(oldWidget);
     if (widget.isAlwaysShown != oldWidget.isAlwaysShown) {
       if (widget.isAlwaysShown == true) {
-        assert(widget.controller != null);
+        _triggerScrollbar();
         _fadeoutAnimationController.animateTo(1.0);
       } else {
         _fadeoutAnimationController.reverse();
@@ -278,6 +270,19 @@
     );
   }
 
+  // Wait one frame and cause an empty scroll event.  This allows the thumb to
+  // show immediately when isAlwaysShown is true.  A scroll event is required in
+  // order to paint the thumb.
+  void _triggerScrollbar() {
+    WidgetsBinding.instance.addPostFrameCallback((Duration duration) {
+      if (widget.isAlwaysShown) {
+        assert(widget.controller != null);
+        _fadeoutTimer?.cancel();
+        widget.controller.position.didUpdateScrollPositionBy(0);
+      }
+    });
+  }
+
   // Handle a gesture that drags the scrollbar by the given amount.
   void _dragScrollbar(double primaryDelta) {
     assert(_currentController != null);
diff --git a/packages/flutter/lib/src/material/scrollbar.dart b/packages/flutter/lib/src/material/scrollbar.dart
index e7ca496..e5d0c97 100644
--- a/packages/flutter/lib/src/material/scrollbar.dart
+++ b/packages/flutter/lib/src/material/scrollbar.dart
@@ -106,15 +106,7 @@
         _textDirection = Directionality.of(context);
         _materialPainter = _buildMaterialScrollbarPainter();
         _useCupertinoScrollbar = false;
-        WidgetsBinding.instance.addPostFrameCallback((Duration duration) {
-          if (widget.isAlwaysShown) {
-            assert(widget.controller != null);
-            // Wait one frame and cause an empty scroll event.  This allows the
-            // thumb to show immediately when isAlwaysShown is true.  A scroll
-            // event is required in order to paint the thumb.
-            widget.controller.position.didUpdateScrollPositionBy(0);
-          }
-        });
+        _triggerScrollbar();
         break;
     }
     assert(_useCupertinoScrollbar != null);
@@ -124,15 +116,28 @@
   void didUpdateWidget(Scrollbar oldWidget) {
     super.didUpdateWidget(oldWidget);
     if (widget.isAlwaysShown != oldWidget.isAlwaysShown) {
-      assert(widget.controller != null);
       if (widget.isAlwaysShown == false) {
         _fadeoutAnimationController.reverse();
       } else {
+        _triggerScrollbar();
         _fadeoutAnimationController.animateTo(1.0);
       }
     }
   }
 
+  // Wait one frame and cause an empty scroll event.  This allows the thumb to
+  // show immediately when isAlwaysShown is true.  A scroll event is required in
+  // order to paint the thumb.
+  void _triggerScrollbar() {
+    WidgetsBinding.instance.addPostFrameCallback((Duration duration) {
+      if (widget.isAlwaysShown) {
+        assert(widget.controller != null);
+        _fadeoutTimer?.cancel();
+        widget.controller.position.didUpdateScrollPositionBy(0);
+      }
+    });
+  }
+
   ScrollbarPainter _buildMaterialScrollbarPainter() {
     return ScrollbarPainter(
       color: _themeColor,
diff --git a/packages/flutter/test/cupertino/scrollbar_test.dart b/packages/flutter/test/cupertino/scrollbar_test.dart
index 017fd9f..4ca2647 100644
--- a/packages/flutter/test/cupertino/scrollbar_test.dart
+++ b/packages/flutter/test/cupertino/scrollbar_test.dart
@@ -287,6 +287,59 @@
   });
 
   testWidgets(
+      'With isAlwaysShown: false, set isAlwaysShown: true. The thumb should be always shown directly',
+      (WidgetTester tester) async {
+    final ScrollController controller = ScrollController();
+    bool isAlwaysShown = false;
+    Widget viewWithScroll() {
+      return StatefulBuilder(
+        builder: (BuildContext context, StateSetter setState) {
+          return Directionality(
+            textDirection: TextDirection.ltr,
+            child: MediaQuery(
+              data: const MediaQueryData(),
+              child: Stack(
+                children: <Widget>[
+                  CupertinoScrollbar(
+                    isAlwaysShown: isAlwaysShown,
+                    controller: controller,
+                    child: SingleChildScrollView(
+                      controller: controller,
+                      child: const SizedBox(
+                        width: 4000.0,
+                        height: 4000.0,
+                      ),
+                    ),
+                  ),
+                  Positioned(
+                    bottom: 10,
+                    child: CupertinoButton(
+                      onPressed: () {
+                        setState(() {
+                          isAlwaysShown = !isAlwaysShown;
+                        });
+                      },
+                      child: const Text('change isAlwaysShown'),
+                    ),
+                  )
+                ],
+              ),
+            ),
+          );
+        },
+      );
+    }
+
+    await tester.pumpWidget(viewWithScroll());
+    await tester.pumpAndSettle();
+    expect(find.byType(CupertinoScrollbar), isNot(paints..rrect()));
+
+    await tester.tap(find.byType(CupertinoButton));
+    await tester.pumpAndSettle();
+    expect(find.byType(CupertinoScrollbar), paints..rrect());
+  });
+
+  testWidgets(
       'With isAlwaysShown: false, fling a scroll. While it is still scrolling, set isAlwaysShown: true. The thumb should not fade even after the scrolling stops',
       (WidgetTester tester) async {
     final ScrollController controller = ScrollController();
@@ -332,6 +385,7 @@
 
     await tester.pumpWidget(viewWithScroll());
     await tester.pumpAndSettle();
+    expect(find.byType(CupertinoScrollbar), isNot(paints..rrect()));
     await tester.fling(
       find.byType(SingleChildScrollView),
       const Offset(0.0, -10.0),
@@ -340,7 +394,13 @@
     expect(find.byType(CupertinoScrollbar), paints..rrect());
 
     await tester.tap(find.byType(CupertinoButton));
+    await tester.pump();
+    expect(find.byType(CupertinoScrollbar), paints..rrect());
+
+    // Wait for the timer delay to expire.
+    await tester.pump(const Duration(milliseconds: 600)); // _kScrollbarTimeToFade
     await tester.pumpAndSettle();
+    // Scrollbar thumb is showing after scroll finishes and timer ends.
     expect(find.byType(CupertinoScrollbar), paints..rrect());
   });
 
diff --git a/packages/flutter/test/material/scrollbar_test.dart b/packages/flutter/test/material/scrollbar_test.dart
index 1bbd53a..ba97a2c 100644
--- a/packages/flutter/test/material/scrollbar_test.dart
+++ b/packages/flutter/test/material/scrollbar_test.dart
@@ -309,6 +309,54 @@
   });
 
   testWidgets(
+      'With isAlwaysShown: false, set isAlwaysShown: true. The thumb should be always shown directly',
+      (WidgetTester tester) async {
+    final ScrollController controller = ScrollController();
+    bool isAlwaysShown = false;
+    Widget viewWithScroll() {
+      return _buildBoilerplate(
+        child: StatefulBuilder(
+          builder: (BuildContext context, StateSetter setState) {
+            return Theme(
+              data: ThemeData(),
+              child: Scaffold(
+                floatingActionButton: FloatingActionButton(
+                  child: const Icon(Icons.threed_rotation),
+                  onPressed: () {
+                    setState(() {
+                      isAlwaysShown = !isAlwaysShown;
+                    });
+                  },
+                ),
+                body: Scrollbar(
+                  isAlwaysShown: isAlwaysShown,
+                  controller: controller,
+                  child: SingleChildScrollView(
+                    controller: controller,
+                    child: const SizedBox(
+                      width: 4000.0,
+                      height: 4000.0,
+                    ),
+                  ),
+                ),
+              ),
+            );
+          },
+        ),
+      );
+    }
+
+    await tester.pumpWidget(viewWithScroll());
+    await tester.pumpAndSettle();
+    expect(find.byType(Scrollbar), isNot(paints..rect()));
+
+    await tester.tap(find.byType(FloatingActionButton));
+    await tester.pumpAndSettle();
+    // Scrollbar is not showing after scroll finishes
+    expect(find.byType(Scrollbar), paints..rect());
+  });
+
+  testWidgets(
       'With isAlwaysShown: false, fling a scroll. While it is still scrolling, set isAlwaysShown: true. The thumb should not fade even after the scrolling stops',
       (WidgetTester tester) async {
     final ScrollController controller = ScrollController();
@@ -348,6 +396,7 @@
 
     await tester.pumpWidget(viewWithScroll());
     await tester.pumpAndSettle();
+    expect(find.byType(Scrollbar), isNot(paints..rect()));
     await tester.fling(
       find.byType(SingleChildScrollView),
       const Offset(0.0, -10.0),
@@ -356,8 +405,13 @@
     expect(find.byType(Scrollbar), paints..rect());
 
     await tester.tap(find.byType(FloatingActionButton));
+    await tester.pump();
+    expect(find.byType(Scrollbar), paints..rect());
+
+    // Wait for the timer delay to expire.
+    await tester.pump(const Duration(milliseconds: 600)); // _kScrollbarTimeToFade
     await tester.pumpAndSettle();
-    // Scrollbar is not showing after scroll finishes
+    // Scrollbar thumb is showing after scroll finishes and timer ends.
     expect(find.byType(Scrollbar), paints..rect());
   });