Back

Shimmer Loading Effects in Flutter

Shimmer Loading Effects in Flutter

Shimmer loading effects are a way to provide visual feedback to the user while content is being loaded. They are often used as placeholders while data is fetched from a remote server, or a local database is being queried. Shimmer loading effects create the illusion of content being present by displaying a shimmering animation in the place where the content will eventually appear. This helps improve the user experience by providing a sense of progress and reducing the perceived waiting time. This article will show you how to implement the shimmer effect in a Flutter application.

Let’s start with creating our Flutter application. Open your terminal and run the command below.

flutter create shimmer_demo

The command above creates a Flutter project and installs the necessary dependencies. Once the command above is done, change the directory to the project directory using the command below.

cd shimmer_demo

Next, using the command below, let’s install the Flutter Shimmer package.

flutter pub add shimmer

After installing the Shimmer package, open the project in your preferred code editor.

Exploring the Shimmer package

With all our installations completed, let’s create some items. Add the code below to create an item.dart file in the lib\ folder.

import 'package:flutter/material.dart';

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

 @override
 Widget build(BuildContext context) {
   return Column(
     crossAxisAlignment: CrossAxisAlignment.start,
     mainAxisAlignment: MainAxisAlignment.start,
     children: [
       ClipRRect(
         borderRadius: BorderRadius.circular(10),
         child: Container(
           height: 190,
           width: 200,
           decoration: BoxDecoration(
             borderRadius: BorderRadius.circular(10),
             color: Colors.teal,
           ),
         ),
       ),
       const SizedBox(height: 10),
       const Row(
         mainAxisAlignment: MainAxisAlignment.spaceBetween,
         children: <Widget>[
           Text(
             'Product',
             style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
           ),
           SizedBox(width: 5),
           Icon(Icons.bookmark_add),
         ],
       ),
       const SizedBox(height: 8),
       const Text(
         'Cost',
         style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
       ),
     ],
   );
 }
}

In the code block above, we’ve created our product item. This extends a Stateless widget that returns a Column that contains a Container, Product Name (Text widget), BookMark Icon, and Price (Text widget).

Next, create a new file in the lib\ folder named home.dart, and paste the code below into it.

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

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

 @override
 Widget build(BuildContext context) {
   return Scaffold(
     appBar: AppBar(
       title: const Text('Shimmer Effect'),
     ),
     body: Padding(
       padding: const EdgeInsets.symmetric(horizontal: 16.0),
       child: SingleChildScrollView(
         child: Column(
           children: [
             _buildProduct(),
           ],
         ),
       ),
     ),
   );
 }

 Widget _buildProduct() => GridView.builder(
       shrinkWrap: true,
       physics: const NeverScrollableScrollPhysics(),
       itemCount: 6,
       itemBuilder: (context, index) => const Items(),
       gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
         crossAxisCount: 2,
         crossAxisSpacing: 10,
         childAspectRatio: 1 / 1.7,
       ),
     );
}

In the code block above, we display our product items created earlier in a Grid view display in our newly created Home widget. The Home widget contains an appbar and a scrollable column that contains our product items in a grid view.

Finally, head over to the main.dart file in the lib\ folder and update the entire code with the one below.

import 'package:flutter/material.dart';
import 'package:shimmer_demo/home.dart';

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

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

 // This widget is the root of your application.
 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     title: 'Shimmer Demo App',
     debugShowCheckedModeBanner: false,
     theme: ThemeData(
       colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
       useMaterial3: true,
     ),
     home: const HomeScreen(),
   );
 }
}

The code above is a clean-up version of the former main.dart code. In this version, we’ve removed all the comments, imported and used the HomeScreeen widget as our home route. Finally, we’ve removed the debug mode banner by setting the debugShowCheckedModeBanner to false.

With all these changes done correctly, run the command below to see a preview of the application on a simulator, emulator, or physical device.

flutter run

-

Experimenting a bit

Now that we’ve set up our User Interface set up, let’s go ahead and experiment with the Shimmer effect on it. Head over to the home.dart file and import the Shimmer package at the top of the file.

import 'package:shimmer/shimmer.dart';

Next, update the _buildProduct() widget function with the one below.

Widget _buildProduct() => GridView.builder(
     shrinkWrap: true,
     physics: const NeverScrollableScrollPhysics(),
     itemCount: 6,
     itemBuilder: (context, index) => Shimmer.fromColors(
         baseColor: Colors.black54,
         highlightColor: Colors.black12,
         child: const Items()),
     gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
       crossAxisCount: 2,
       crossAxisSpacing: 10,
       childAspectRatio: 1 / 1.7,
     ),
   );

In the code above, we wrapped the Items() with the shimmer widget as a parent widget. We also provided the shimmer widget with a base and highlight color in different shades of black.

Reloading the application gives us the result below.

-

The Shimmer Package provides some customizable features for the Shimmer widget. In this section, we’ll be looking into other features that the Shimmer package has to offer.

Shimmer direction From the example above, the moving shimmering effect goes from left to right. But this can be changed by your choice of direction ranging from left-to-right (ltr), right-to-left (rtl), top-to-bottom (ttb), and bottom-to-top (btt). To see this in action, update the _buildProduct() widget function with the code below.

Widget _buildProduct() => GridView.builder(
     shrinkWrap: true,
     physics: const NeverScrollableScrollPhysics(),
     itemCount: 6,
     itemBuilder: (context, index) => Shimmer.fromColors(
         baseColor: Colors.black54,
         highlightColor: Colors.black12,
         direction: ShimmerDirection.ttb, // added direction
         child: const Items()),
     gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
       crossAxisCount: 2,
       crossAxisSpacing: 10,
       childAspectRatio: 1 / 1.7,
     ),
   );

In the code block above, we’ve updated the animation direction to start from the top to the bottom.

-

Shimmer Loop Not only can the direction be changed, but the number of animating movements can be controlled. This can be controlled using integers ranging from 0 being the infinity looping to any specific integer. Update the _buildProduct() widget function by adding the loop property.

Widget _buildProduct() => GridView.builder(
     shrinkWrap: true,
     physics: const NeverScrollableScrollPhysics(),
     itemCount: 6,
     itemBuilder: (context, index) => Shimmer.fromColors(
         baseColor: Colors.black54,
         highlightColor: Colors.black12,
         direction: ShimmerDirection.ttb,
         loop: 2, //added loop
         child: const Items()),
     gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
       crossAxisCount: 2,
       crossAxisSpacing: 10,
       childAspectRatio: 1 / 1.7,
     ),
   );

In the code above, we’ve restricted the animation movement to 2.

-

Shimmer Duration The duration of the shimmering animation can be controlled, thus controlling the speed at which it moves. Update the _buildProduct() widget function by adding the period property.

Widget _buildProduct() => GridView.builder(
     shrinkWrap: true,
     physics: const NeverScrollableScrollPhysics(),
     itemCount: 6,
     itemBuilder: (context, index) => Shimmer.fromColors(
         baseColor: Colors.black54,
         highlightColor: Colors.black12,
         direction: ShimmerDirection.ttb,
         period: const Duration(milliseconds: 900), // added period
         child: const Items()),
     gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
       crossAxisCount: 2,
       crossAxisSpacing: 10,
       childAspectRatio: 1 / 1.7,
     ),
   );

In the code block above, we’ve updated the shimmer animation loops’ duration. This can be seen in the example below.

-

Adding Shimmer Effects to Asynchronous Content

Shimmer Effects are mainly used in asynchronous content because they act as the perfect placeholders to fill in when the content is retrieved from the API. Another amazing fact about the Shimmer package is how it paints its child widget with the Shimmer effect, thus mimicking the original content to be displayed. In this section, we’ll improve our asynchronous content user experience by adding the Shimmer effect when the items are being fetched.

To begin, clone the starter project from GitHub, and run the command below in the project directory.

flutter pub get && flutter run

The command above will download the necessary dependencies and run the project in a simulator, emulator, or physical device connected to the code editor.

Below is a preview of the clone project.

-

In the gif above, we perform an asynchronous action to fetch content from an API. A CircularProgressIndicator widget is used as a placeholder to await fetched content.

Let’s improve the user experience of this application by using a shimmer loading effect to mimic the contents of the application when it is loading.

First, install the Shimmer package using the below command.

flutter pub add shimmer

Next, create a file in the lib\ folder named product_shimmer.dart and paste the code below.

import 'package:flutter/material.dart';

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

 @override
 Widget build(BuildContext context) {
   return Column(
     crossAxisAlignment: CrossAxisAlignment.start,
     mainAxisAlignment: MainAxisAlignment.start,
     children: [
       Container(
         height: 190,
         width: 200,
         decoration: BoxDecoration(
           borderRadius: BorderRadius.circular(10),
           color: Colors.teal,
         ),
       ),
       const SizedBox(height: 10),
       const Row(
         mainAxisAlignment: MainAxisAlignment.spaceBetween,
         children: <Widget>[
           Expanded(
             child: Text(
               'Contents here Contents here Contents here',
               style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
             ),
           ),
           SizedBox(width: 5),
           Icon(Icons.bookmark_add),
         ],
       ),
       const SizedBox(height: 8),
       const Text(
         '0',
         style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
       ),
     ],
   );
 }
}

The code above is a mimicry of the individual content of the Home screen.

Next, head to the home.dart file and paste the code below into the _HomeScreenState.

Widget _buildProductShimmer() => GridView.builder(
     shrinkWrap: true,
     physics: const NeverScrollableScrollPhysics(),
     itemCount: 6,
     itemBuilder: (context, index) {
       return Shimmer.fromColors(
           baseColor: Colors.black54,
           highlightColor: Colors.black12,
           direction: ShimmerDirection.ttb,
           period: const Duration(milliseconds: 800),
           child: const ProductShimmer());
     },
     gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
       crossAxisCount: 2,
       crossAxisSpacing: 10,
       childAspectRatio: 1 / 2,
     ),
   );

In the code block above, we’re populating the screen with the ProductShimmer, wrapped with the Shimmer widget from the Shimmer package. We’ve also customized the Shimmer widget by defining our duration, period, and direction. The code above mimics the grid view with the populated contents.

Finally, update the ternary statement by displaying the _buildProductShimmer() widget in place of the Circular progress indicator.

Column(
 mainAxisAlignment: MainAxisAlignment.center,
 children: [
   isLoading ? _buildProductShimmer() : _buildProduct(),
 ],
),

With all the steps followed accurately, we should have an end product like the gif below.

-

Conclusion

Based on the demo app above, the Shimmer package has proven to be effective, intuitive, and a beautiful placeholder when performing asynchronous calls. It has also created a significant difference between itself and the default Circular progress indicator when boosting the user’s experience of an application.

Here is the Link to the complete Asynchronous code source on GitHub.

Truly understand users experience

See every user interaction, feel every frustration and track all hesitations with OpenReplay — the open-source digital experience platform. It can be self-hosted in minutes, giving you complete control over your customer data. . Check our GitHub repo and join the thousands of developers in our community..

OpenReplay