This tutorial will teach you how to implement a persistent bottom sheet in Flutter, which is a sheet that remains visible at the bottom of the screen even when the user scrolls.
We will use the built-in BottomSheet widget in combination with the Scaffold widget to achieve this.
Material : Persistent Bottom sheet
import 'package:flutter/material.dart';
class BottomSheetFabRoute extends StatefulWidget {
  const BottomSheetFabRoute({super.key});
  @override
  BottomSheetFabRouteState createState() => BottomSheetFabRouteState();
}
class BottomSheetFabRouteState extends State<BottomSheetFabRoute> {
// This variable will hold a reference to the bottom sheet controller.
  late PersistentBottomSheetController sheetController;
// This variable will hold a reference to the scaffold context.
  late BuildContext _scaffoldCtx;
// This boolean variable will determine whether the bottom sheet is currently visible or not.
  bool showSheet = false;
  @override
  Widget build(BuildContext context) {
    // Get the current text theme and apply the surface color to it
    final textTheme = Theme.of(context)
        .textTheme
        .apply(displayColor: Theme.of(context).colorScheme.onSurface);
    // Return a scaffold with a centered text widget that displays a message and a floating action button
    return Scaffold(
      // Use the builder function to get access to the scaffold context
      body: Builder(builder: (BuildContext ctx) {
        _scaffoldCtx = ctx; // Assign the scaffold context to a variable for later use
        return Center(
          child:
          TextStyleExample(name: "Tap button \nbelow", style: textTheme.headlineMedium!.copyWith(color: Theme.of(context).colorScheme.primary)),
        );
      }),
      // Add a floating action button that toggles a bottom sheet
      floatingActionButton: FloatingActionButton(
          heroTag: "fab",
          backgroundColor:Theme.of(context).colorScheme.primary,
          elevation: 3,
          child: Icon(showSheet ? Icons.arrow_downward : Icons.arrow_upward, color: Theme.of(context).colorScheme.onPrimary),
          onPressed: () {
            setState(() {
              showSheet = !showSheet; // Toggle the sheet visibility
              if(showSheet) {
                _showSheet(); // Show the sheet if it's not already showing
              } else {
                Navigator.pop(_scaffoldCtx); // Close the sheet if it's already showing
              }
            });
          }
      ),
    );
  }
  void _showSheet() {
    // get the current text theme with updated display color
    final textTheme = Theme.of(context)
        .textTheme
        .apply(displayColor: Theme.of(context).colorScheme.onSurface);
    // show a bottom sheet and store the returned controller object
    sheetController = showBottomSheet(context: _scaffoldCtx, builder: (BuildContext bc){
      // create a card with elevation and no margin
      return Card(
        elevation: 10,
        margin: const EdgeInsets.fromLTRB(0, 0, 0, 0),
        child: Container(
          padding: const EdgeInsets.all(10),
          width: double.infinity,
          color: Theme.of(context).colorScheme.background,
            // add a container to hold the content of the sheet
            child: Column(
              mainAxisSize: MainAxisSize.min,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                Center(
                  child: Container(width: 30, height: 5, decoration:  BoxDecoration(
                    color: Theme.of(context).colorScheme.background,
                    borderRadius: const BorderRadius.all(Radius.circular(5)),
                  )),
                ),
                Container(height: 10),
                Row(
                  children: <Widget>[
                    Container(width: 50),
                    Expanded(
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: <Widget>[
                          TextStyleExample(name: "Dairy milk Chocolate", style: textTheme.titleMedium!.copyWith(color: Theme.of(context).colorScheme.primary)),
                          Container(height: 20),
                          Container(height: 5),
                          const Divider(),
                          Container(
                              padding: const EdgeInsets.symmetric(vertical: 10),
                              child:
                              TextStyleExample(name: "10 min away", style: textTheme.titleMedium!.copyWith(color: Theme.of(context).colorScheme.primary))
                          ),
                          const Divider(),
                        ],
                      ),
                    )
                  ],
                ),
                SizedBox(height: 50,
                  child: Row(
                    children: <Widget>[
                      Container(width: 10), Icon(Icons.location_on, color: Theme.of(context).colorScheme.primary), Container(width: 20),
                      TextStyleExample(name: "740 Valencia St, San Francisco, CA", style: textTheme.titleMedium!)
                    ],
                  ),
                ),
                SizedBox(height: 50,
                  child: Row(
                    children: <Widget>[
                      Container(width: 10), Icon(Icons.phone, color: Theme.of(context).colorScheme.primary), Container(width: 20),
                      TextStyleExample(name: "(415) 349-0942", style: textTheme.titleMedium!)
                    ],
                  ),
                ),
                SizedBox(height: 50,
                  child: Row(
                    children: <Widget>[
                      Container(width: 10), Icon(Icons.location_on, color: Theme.of(context).colorScheme.primary), Container(width: 20),
                      TextStyleExample(name: "741 Valencia St, San Francisco, CA", style: textTheme.titleMedium!)
                    ],
                  ),
                ),
                SizedBox(height: 50,
                  child: Row(
                    children: <Widget>[
                      Container(width: 10), Icon(Icons.phone, color: Theme.of(context).colorScheme.primary), Container(width: 20),
                      TextStyleExample(name: "(416) 349-0942", style: textTheme.titleMedium!)
                    ],
                  ),
                ),
                SizedBox(height: 50,
                  child: Row(
                    children: <Widget>[
                      Container(width: 10),  Icon(Icons.schedule, color: Theme.of(context).colorScheme.primary), Container(width: 20),
                      TextStyleExample(name: "Wed, 10 AM - 9 PM", style: textTheme.titleMedium!)
                    ],
                  ),
                ),
              ],
            )
        ),
      );
    });
    // set a callback function to be called when the bottom sheet is closed
    sheetController.closed.then((value) {
      // update state to indicate that the sheet is no longer being shown
      setState(() {
        showSheet = false;
      });
    });
  }
}
// Typography widget
class TextStyleExample extends StatelessWidget {
  // Constructor for the TextStyleExample class
  const TextStyleExample({
    super.key, // Call the constructor of the superclass
    required this.name, // Declare a required String property called name
    required this.style, // Declare a required TextStyle property called style
  });
  final String name; // Store the name property as a final String
  final TextStyle style; // Store the style property as a final TextStyle
  @override
  Widget build(BuildContext context) {
    return Padding(
      // Set the padding for the widget to 1.0 pixels on all sides
      padding: const EdgeInsets.all(1.0),
      child: Text(
        name, // Display the name property as the text content
        style: style.copyWith(letterSpacing: 1.0), // Apply the style property to the text, with an additional letterSpacing of 1.0
      ),
    );
  }
}
..
- Import the necessary packages.
- Create a stateful widget called BottomSheetFabRoute.
- Override the createState method to return a BottomSheetFabRouteState object.
- Create a state class called BottomSheetFabRouteState that extends the State class.
- Declare three variables:
- sheetController, a PersistentBottomSheetController that will hold a reference to the bottom sheet controller.
- _scaffoldCtx, a BuildContext that will hold a reference to the scaffold context.
- showSheet, a boolean that will determine whether the bottom sheet is currently visible or not.
- Override the build method to return a Scaffold widget with a centered text widget that displays a message and a floating action button.
- Use the Builder widget to get access to the scaffold context and assign it to the _scaffoldCtx variable for later use.
- Add a floating action button that toggles a bottom sheet. The button's onPressed method toggles the showSheet boolean and either shows the sheet if it's not already showing or closes the sheet if it's already showing.
floatingActionButton: FloatingActionButton(
  ...
  onPressed: () {
    setState(() {
      showSheet = !showSheet; // Toggle the sheet visibility
      if(showSheet) {
        _showSheet(); // Show the sheet if it's not already showing
      } else {
        Navigator.pop(_scaffoldCtx); // Close the sheet if it's already showing
      }
    });
  }
),
- Define the _showSheet method that displays the bottom sheet. The method uses the showBottomSheet function to display a card with information about a chocolate store.
void _showSheet() {
  // show a bottom sheet and store the returned controller object
  sheetController = showBottomSheet(context: _scaffoldCtx, builder: (BuildContext bc){
    // create a card with elevation and no margin
    return Card(
      elevation: 10,
      margin: const EdgeInsets.fromLTRB(0, 0, 0, 0),
      child: Container(
        padding: const EdgeInsets.all(10),
        width: double.infinity,
        color: Theme.of(context).colorScheme.background,
        // add a container to hold the content of the sheet
        child: Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            ...
          ],
        ),
      ),
    );
  });
}
- Create a custom widget called TextStyleExample that displays a name and a style.
..
Tags:
* Flutter UIX


