image

15 Feb 2026

9K

35K

Building a Task Progress Tracker Widget with a Percentage Bar in Flutter

In modern applications, providing clear visual feedback on task progression is crucial for user experience. A task progress tracker with a percentage bar not only informs users about the current status but also enhances engagement and reduces perceived wait times. This article will guide you through creating a reusable and customizable task progress tracker widget in Flutter, complete with a dynamic percentage bar.

Why a Task Progress Tracker?

  • Enhanced User Experience: Users stay informed about long-running operations.
  • Reduced Anxiety: Eliminates guesswork regarding task completion.
  • Improved Engagement: Visual progress can make an application feel more responsive.
  • Clarity: Instantly conveys the state of multiple tasks at a glance.

Prerequisites

Before we begin, ensure you have Flutter installed and set up. A basic understanding of Flutter widgets, state management, and layout principles will be beneficial.

Project Setup

You can start with a new Flutter project or integrate this widget into an existing one.


flutter create task_progress_app
cd task_progress_app
    

Open lib/main.dart and clear its contents, replacing it with a basic MaterialApp and Scaffold setup.

Designing the TaskProgressTracker Widget

Our TaskProgressTracker will be a StatelessWidget. Since we want to display dynamic progress, it's best to handle the progress value as a property passed from a parent widget. The parent will manage the state of the progress, making our tracker itself a simple and reusable presentational widget.

Widget Structure

The widget will display the task name, the current progress as a percentage, and a visual progress bar. We'll use a Column to stack these elements vertically, and a combination of Container and Stack for the percentage bar itself to allow for text overlay.

Step 1: Create the task_progress_tracker.dart File

Create a new file named lib/widgets/task_progress_tracker.dart (or directly in lib/main.dart for a simple example).


import 'package:flutter/material.dart';

class TaskProgressTracker extends StatelessWidget {
  final String taskName;
  final double currentProgress; // e.g., 0.5 for 50%
  final double totalProgress; // e.g., 1.0 for 100%

  final Color backgroundColor;
  final Color progressColor;
  final Color textColor;
  final double height;
  final double borderRadius;

  const TaskProgressTracker({
    Key? key,
    required this.taskName,
    required this.currentProgress,
    this.totalProgress = 1.0, // Default to 1.0 for percentage calculation
    this.backgroundColor = Colors.grey,
    this.progressColor = Colors.blue,
    this.textColor = Colors.white,
    this.height = 20.0,
    this.borderRadius = 10.0,
  }) : assert(currentProgress >= 0 && currentProgress <= totalProgress,
            'currentProgress must be between 0 and totalProgress'),
       super(key: key);

  @override
  Widget build(BuildContext context) {
    final double percentage = currentProgress / totalProgress;

    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            taskName,
            style: TextStyle(
              fontSize: 16.0,
              fontWeight: FontWeight.bold,
              color: Colors.black87,
            ),
          ),
          SizedBox(height: 8.0),
          LayoutBuilder(
            builder: (context, constraints) {
              return Container(
                width: constraints.maxWidth,
                height: height,
                decoration: BoxDecoration(
                  color: backgroundColor.withOpacity(0.3), // Lighter background
                  borderRadius: BorderRadius.circular(borderRadius),
                ),
                child: Stack(
                  children: [
                    // Progress Bar Fill
                    Positioned.fill(
                      child: FractionallySizedBox(
                        alignment: Alignment.centerLeft,
                        widthFactor: percentage,
                        child: Container(
                          decoration: BoxDecoration(
                            color: progressColor,
                            borderRadius: BorderRadius.circular(borderRadius),
                          ),
                        ),
                      ),
                    ),
                    // Percentage Text Overlay
                    Center(
                      child: Text(
                        '${(percentage * 100).toStringAsFixed(0)}%',
                        style: TextStyle(
                          color: textColor,
                          fontWeight: FontWeight.bold,
                          fontSize: height * 0.7, // Scale font size with height
                        ),
                      ),
                    ),
                  ],
                ),
              );
            },
          ),
        ],
      ),
    );
  }
}
    

Code Explanation:

  • Properties: We define taskName, currentProgress, and totalProgress as required parameters. Customization options like backgroundColor, progressColor, textColor, height, and borderRadius are also provided. An assert ensures valid progress values.
  • LayoutBuilder: This widget is crucial for obtaining the maximum available width for our progress bar, allowing it to span the full width of its parent.
  • Background Container: The outer Container sets the overall shape and background of the progress bar track.
  • Stack: We use a Stack to overlay the progress fill and the percentage text.
  • Progress Bar Fill:
    • Positioned.fill ensures the child fills the parent Stack completely, then FractionallySizedBox controls its width.
    • FractionallySizedBox with widthFactor: percentage dynamically adjusts the width of the progress bar based on the calculated percentage. alignment: Alignment.centerLeft makes it grow from left to right.
    • The inner Container with progressColor defines the filled portion's appearance.
  • Percentage Text: A Center widget is used to place the formatted percentage text (e.g., "75%") directly on top of the progress bar, with its color and size customizable. The font size is scaled relative to the bar's height for better responsiveness.

Step 2: Using the TaskProgressTracker in main.dart

Now, let's integrate our new widget into the main application. We'll use a StatefulWidget to manage the progress state dynamically, simulating updates.


import 'package:flutter/material.dart';
import 'package:task_progress_app/widgets/task_progress_tracker.dart'; // Adjust path if needed

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Task Progress Tracker Demo',
      theme: ThemeData(
        primarySwatch: Colors.deepPurple,
      ),
      home: const HomeScreen(),
    );
  }
}

class HomeScreen extends StatefulWidget {
  const HomeScreen({Key? key}) : super(key: key);

  @override
  State createState() => _HomeScreenState();
}

class _HomeScreenState extends State {
  double _uploadProgress = 0.0;
  double _downloadProgress = 0.0;
  double _processingProgress = 0.0;

  @override
  void initState() {
    super.initState();
    // Simulate progress updates
    _startProgressSimulation();
  }

  void _startProgressSimulation() {
    Future.delayed(const Duration(milliseconds: 100), () {
      if (!mounted) return; // Check if the widget is still mounted
      setState(() {
        _uploadProgress += 0.05;
        _downloadProgress += 0.03;
        _processingProgress += 0.01;

        if (_uploadProgress > 1.0) _uploadProgress = 1.0;
        if (_downloadProgress > 1.0) _downloadProgress = 1.0;
        if (_processingProgress > 1.0) _processingProgress = 1.0;
      });
      if (_uploadProgress < 1.0 || _downloadProgress < 1.0 || _processingProgress < 1.0) {
        _startProgressSimulation(); // Continue simulation
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Task Progress Tracker'),
      ),
      body: SingleChildScrollView(
        child: Column(
          children: [
            TaskProgressTracker(
              taskName: 'File Upload',
              currentProgress: _uploadProgress,
              progressColor: Colors.green,
            ),
            TaskProgressTracker(
              taskName: 'Data Download',
              currentProgress: _downloadProgress,
              progressColor: Colors.orange,
              backgroundColor: Colors.orangeAccent.withOpacity(0.2),
              textColor: Colors.black, // Example of changing text color
            ),
            TaskProgressTracker(
              taskName: 'Image Processing',
              currentProgress: _processingProgress,
              progressColor: Colors.deepPurple,
              height: 30.0, // Example of changing height
              borderRadius: 15.0,
            ),
            // Example with a different totalProgress (e.g., 3 out of 5 items)
            TaskProgressTracker(
              taskName: 'Batch Operations (3/5 completed)',
              currentProgress: 3,
              totalProgress: 5,
              progressColor: Colors.red,
              textColor: Colors.white,
            ),
            // Reset button
            Padding(
              padding: const EdgeInsets.all(16.0),
              child: ElevatedButton(
                onPressed: () {
                  setState(() {
                    _uploadProgress = 0.0;
                    _downloadProgress = 0.0;
                    _processingProgress = 0.0;
                    _startProgressSimulation(); // Restart simulation
                  });
                },
                child: const Text('Reset Progress'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
    

Enhancements and Customization

  • Animation: Use AnimatedContainer for the progress fill or an AnimationController to smoothly animate progress updates, providing a more fluid user experience.
  • Custom Painters: For more complex designs (e.g., circular progress bars, unique shapes, multi-segment bars), consider using CustomPainter.
  • Gestures: Add tap detection to the widget for additional interactions, such as viewing detailed task information.
  • Error States: Implement visual cues (e.g., changing colors to red) for tasks that fail or encounter errors.
  • Accessibility: Ensure proper semantic labels and hints for screen readers to assist users with disabilities.
  • Theming: Integrate the widget with Flutter's theming system for consistent design across your application.

Conclusion

You have successfully built a flexible and reusable TaskProgressTracker widget in Flutter, capable of displaying task status with a clear percentage bar. This widget can be easily integrated into various parts of your application, significantly improving the user experience by providing immediate and understandable feedback on ongoing processes. Experiment with the customization options and enhancements to tailor it to your specific application needs.

Related Articles

May 14, 2026

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter Countdown timers are essential in many applic

May 11, 2026

Unleashing Dynamic UIs: Flutter's Animation Prowess

Unleashing Dynamic UIs: Flutter's Animation Prowess for Slide & Scale Effects Flutter's declarative UI framework, combined with its powerful animation capabilit

May 11, 2026

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy A well-designed Product Detail Page (PDP) is