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.
- Shimmer: https://pub.dev/packages/shimmer
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.
It’s time for hands-on. Clone my Git repo and try this and alter it as you want.
This is so simple and easy. I will meet you again in the next blog. Happy coding…
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!