How to use the shimmer loading effect in Flutter?: The easy, straightforward way.

User experience is more important in app development. API calls and Firebase calls are taking time to load data. That time if our app UI is empty, it will affect the user experience.

Some people are showing the loaders instead. But it's old style. Here I come up with a solution. It definitely gives the best visual user experience with the new current trend.

In this tutorial, I will teach you the shimmer effect in Flutter for CLEAN architecture step by step. Here, I will share how to use the shimmer class in a reusable manner along with folder structure.

At the end of the blog, I enclosed my GitHub repo link.

Ok, without any delay, let's jump into it.

Step 1: Import the package

  • Add the flutter package (latest one) in your flutter project.

  • And run the below command in your terminal

  • Flutter pub get

Step 2: Create reusable global shimmer widget

  • Create a folder structure like this
lib/
├── core/
│   └── utils/
│       └── colors.dart
│
├── data/
│   ├── models/
│   │   ├── friends_model.dart
│   │   ├── post_model.dart
│   │   └── user_model.dart
│   │
│   ├── provider/
│   │   └── user_provider.dart
│   │
│   └── repositories/
│       └── user_repo_imp.dart
│
├── domain/
│   ├── entities/
│   │   ├── friends_entity.dart
│   │   ├── post_entity.dart
│   │   └── user_entity.dart
│   │
│   └── repositories/
│       └── user_repo.dart
│
├── presentation/
│   ├── screen/
│   │   ├── home_screen.dart
│   │   └── home_shimmer_template.dart
│   │
│   ├── widgets/
│   │   └── shimmer_widget.dart // reusabel shimmer widget
│   │
│   └── bloc/
│       ├── user_bloc.dart
│       ├── user_event.dart
│       └── user_state.dart
│
└── main.dart
  • Now, add the shimmer_widget code.
class ShimmerWidget extends StatelessWidget {
  final double width;
  final double height;
  final BorderRadius borderRadius;
  const ShimmerWidget({
    super.key,
    this.width = double.infinity,
    this.height = 200,
    this.borderRadius = BorderRadius.zero,
  });

  @override
  Widget build(BuildContext context) {
    return Shimmer.fromColors(
      baseColor: shimmerBaseColor, // Color(0xFFD7CBFF); // Slightly darker
      highlightColor: shimmerHighlightColor, // Color(0xFFF0E9FF); // Slightly lighter
      child: Container(
        width: width,
        height: height,
        decoration: BoxDecoration(
          color: Colors.grey.shade300,
          borderRadius: borderRadius,
        ),
      ),
    );
  }
}

Step 3: Create your loading state layout by using shimmer widget

  • Now, first create your main screen layout. once designed, again create a high-level layout for shimmer
class HomeShimmerTemplate extends StatelessWidget {
  const HomeShimmerTemplate({super.key});

  @override
  Widget build(BuildContext context) {
    double width = MediaQuery.of(context).size.width;
    return SingleChildScrollView(
      child: Column(
        children: [
          Padding(
            padding: const EdgeInsets.symmetric(vertical: 50, horizontal: 10),
            child: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.center,
                spacing: 20,
                children: [
                  ShimmerWidget(
                    width: width * 0.5,
                    height: 50,
                    borderRadius: const BorderRadius.all(Radius.circular(16)),
                  ),

                  ShimmerWidget(
                    width: width * 0.8,
                    height: 20,
                    borderRadius: const BorderRadius.all(Radius.circular(16)),
                  ),
                ],
              ),
            ),
          ),
          //* Post Sectoin
          Container(
            width: double.infinity,
            padding: const EdgeInsets.symmetric(vertical: 20),
            decoration: BoxDecoration(
              color: Colors.white,
              borderRadius: BorderRadius.only(
                topLeft: Radius.circular(20),
                topRight: Radius.circular(20),
              ),
              border: Border.all(color: borderColor),
              boxShadow: [
                BoxShadow(
                  color: Colors.black.withAlpha(100),
                  blurRadius: 5,
                  spreadRadius: -15,
                  offset: const Offset(0, -5),
                ),
              ],
            ),
            child: Padding(
              padding: const EdgeInsets.symmetric(horizontal: 20),
              child: Column(
                spacing: 20,
                children: [
                  //* My Friends section
                  Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    spacing: 15,
                    children: [
                      //* Title
                      ShimmerWidget(
                        width: width * 0.3,
                        height: 20,
                        borderRadius: const BorderRadius.all(
                          Radius.circular(16),
                        ),
                      ),

                      //* Friends List
                      SizedBox(
                        height: 80,
                        child: ListView.builder(
                          shrinkWrap: true,
                          itemCount: 10,
                          scrollDirection: Axis.horizontal,
                          itemBuilder: (context, index) {
                            return Padding(
                              padding: const EdgeInsets.only(right: 10),
                              child: ShimmerWidget(
                                width: 80,
                                borderRadius: const BorderRadius.all(
                                  Radius.circular(50),
                                ),
                              ),
                            );
                          },
                        ),
                      ),
                    ],
                  ),

                  //* Post section
                  ListView.builder(
                    shrinkWrap: true,
                    itemCount: 7,
                    primary: false,
                    scrollDirection: Axis.vertical,
                    itemBuilder: (context, index) {
                      return Padding(
                        padding: const EdgeInsets.only(bottom: 8.0),
                        child: ShimmerWidget(
                          width: width * 0.8,
                          height: 350,
                          borderRadius: const BorderRadius.all(
                            Radius.circular(16),
                          ),
                        ),
                      );
                    },
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}
  • If you are using bloc state management, use the shimmer layout on the loading state. Once data is fetched, then load the original layout in the loaded state.
body: BlocListener<UserBloc, UserState>(
        listener: (context, state) {
          if (state is UserLoadedState) {
            userEntity = state.user;
          }
        },
        child: BlocBuilder<UserBloc, UserState>(
          builder: (context, state) {
            if (state is UserLoadingState) {
              //* Loading state
              return const HomeShimmerTemplate();
            } else if (state is UserLoadedState) {
              //* Loaded state
              return bodyWidget(); // Actual original layout
            } else {
              //* err state
              return SizedBox();
            }
          },
        ),
      ),

Step 4: Configure everything.

This is so simple and easy. I will meet you again in the next blog. Happy coding…

0
Subscribe to my newsletter

Read articles from Renga Praveen Kumar directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Renga Praveen Kumar
Renga Praveen Kumar

I'm Renga, a Flutter developer passionate about building efficient and scalable apps. I share whatever I learn with the world every weekend, covering insights, best practices, and real-world solutions to help developers master Flutter. Let’s learn and build together!