image

14 Feb 2026

9K

35K

Building a Dynamic User Badge Widget with Level and XP Bar in Flutter

In modern applications, gamification elements play a crucial role in enhancing user engagement and retention. A common and effective gamification feature is the user badge, often accompanied by a level indicator and an experience (XP) bar. This visual representation motivates users by showcasing their progress and achievements within the application. This article will guide you through building a reusable and visually appealing user badge widget in Flutter, complete with a current level display and a custom-drawn XP progress bar.

Understanding the Core Components

Before diving into the implementation, let's identify the fundamental components required for our user badge widget:

  • User Data Model: A clear structure to hold the user's current level, current experience points, and the experience points required to reach the next level.
  • Level Display: A text element prominently showing the user's current level.
  • XP Progress Bar: A visual bar that fills up as the user gains XP, indicating progress towards the next level.
  • Overall Layout: A combination of these elements arranged aesthetically.

Step-by-Step Implementation

1. Defining the User Data Model

First, we'll create a simple Dart class to encapsulate the user's badge-related data. This makes our widget highly reusable and easy to integrate with various data sources.


class UserBadgeData {
  final int level;
  final double currentXp; // Current XP accumulated in the current level
  final double xpToNextLevel; // Total XP required to reach the next level from the start of the current level

  UserBadgeData({
    required this.level,
    required this.currentXp,
    required this.xpToNextLevel,
  }) : assert(currentXp >= 0 && currentXp <= xpToNextLevel, 'Current XP must be between 0 and XP to next level.');

  /// Calculates the progress as a double between 0.0 and 1.0
  double get xpProgress => xpToNextLevel > 0 ? currentXp / xpToNextLevel : 0.0;
}

2. Crafting the XP Progress Bar (CustomPainter)

For a unique and fully customizable look, we'll use Flutter's CustomPainter to draw the XP progress bar. This allows us to create rounded corners, custom gradients, and precise control over the bar's appearance.


import 'package:flutter/material.dart';

class XpProgressBarPainter extends CustomPainter {
  final double progress; // A value between 0.0 and 1.0
  final Color backgroundColor;
  final Color progressColor;
  final double borderRadius;

  XpProgressBarPainter({
    required this.progress,
    this.backgroundColor = Colors.grey,
    this.progressColor = Colors.blueAccent,
    this.borderRadius = 8.0,
  }) : assert(progress >= 0.0 && progress <= 1.0, 'Progress must be between 0.0 and 1.0');

  @override
  void paint(Canvas canvas, Size size) {
    final RRect backgroundRRect = RRect.fromRectAndRadius(
      Rect.fromLTWH(0, 0, size.width, size.height),
      Radius.circular(borderRadius),
    );

    // Draw background
    final Paint backgroundPaint = Paint()
      ..color = backgroundColor
      ..style = PaintingStyle.fill;
    canvas.drawRRect(backgroundRRect, backgroundPaint);

    // Draw progress
    if (progress > 0) {
      final double progressWidth = size.width * progress;
      final RRect progressRRect = RRect.fromRectAndRadius(
        Rect.fromLTWH(0, 0, progressWidth, size.height),
        Radius.circular(borderRadius),
      );

      final Paint progressPaint = Paint()
        ..color = progressColor
        ..style = PaintingStyle.fill;
      canvas.drawRRect(progressRRect, progressPaint);
    }
  }

  @override
  bool shouldRepaint(covariant XpProgressBarPainter oldDelegate) {
    return oldDelegate.progress != progress ||
           oldDelegate.backgroundColor != backgroundColor ||
           oldDelegate.progressColor != progressColor ||
           oldDelegate.borderRadius != borderRadius;
  }
}

3. Assembling the User Badge Widget

Now, let's combine our data model, the custom progress bar, and other UI elements into a single, reusable UserBadgeWidget. We'll use a Column and Row widgets to arrange the level and XP bar vertically.


import 'package:flutter/material.dart';
// Assuming user_badge_data.dart and xp_progress_bar_painter.dart are in the same project
// import 'user_badge_data.dart'; // Adjust path as necessary
// import 'xp_progress_bar_painter.dart'; // Adjust path as necessary

class UserBadgeWidget extends StatelessWidget {
  final UserBadgeData badgeData;
  final Color levelTextColor;
  final Color xpBarBackgroundColor;
  final Color xpBarProgressColor;
  final double xpBarHeight;
  final double containerBorderRadius;
  final double padding;
  final Color containerColor;
  final List<BoxShadow>? boxShadow;

  const UserBadgeWidget({
    Key? key,
    required this.badgeData,
    this.levelTextColor = Colors.white,
    this.xpBarBackgroundColor = const Color(0xFF424242), // Dark grey
    this.xpBarProgressColor = Colors.lightBlueAccent,
    this.xpBarHeight = 10.0,
    this.containerBorderRadius = 10.0,
    this.padding = 16.0,
    this.containerColor = const Color(0xFF212121), // Dark background for the badge
    this.boxShadow,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.all(padding),
      decoration: BoxDecoration(
        color: containerColor,
        borderRadius: BorderRadius.circular(containerBorderRadius),
        boxShadow: boxShadow ?? [
          BoxShadow(
            color: Colors.black.withOpacity(0.3),
            blurRadius: 8,
            offset: const Offset(0, 4),
          ),
        ],
      ),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // Level Display
          Text(
            'Level ${badgeData.level}',
            style: TextStyle(
              color: levelTextColor,
              fontSize: 22,
              fontWeight: FontWeight.bold,
              letterSpacing: 1.2,
            ),
          ),
          const SizedBox(height: 8),
          // XP Text (e.g., "150/300 XP")
          Text(
            '${badgeData.currentXp.toInt()}/${badgeData.xpToNextLevel.toInt()} XP',
            style: TextStyle(
              color: levelTextColor.withOpacity(0.7),
              fontSize: 14,
            ),
          ),
          const SizedBox(height: 8),
          // XP Progress Bar
          SizedBox(
            height: xpBarHeight,
            width: double.infinity, // Occupy full available width
            child: CustomPaint(
              painter: XpProgressBarPainter(
                progress: badgeData.xpProgress,
                backgroundColor: xpBarBackgroundColor,
                progressColor: xpBarProgressColor,
                borderRadius: containerBorderRadius / 2, // Slightly smaller radius for the bar
              ),
            ),
          ),
        ],
      ),
    );
  }
}

4. Integrating into Your Application

To use the UserBadgeWidget, simply provide it with an instance of UserBadgeData. You can place it anywhere in your widget tree, for example, within a Scaffold's body.


import 'package:flutter/material.dart';
// Assuming UserBadgeData and UserBadgeWidget are defined in your project
// import 'user_badge_data.dart'; // Adjust path as necessary
// import 'user_badge_widget.dart'; // Adjust path as necessary

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'User Badge Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const HomePage(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    // Example UserBadgeData
    final UserBadgeData userData = UserBadgeData(
      level: 15,
      currentXp: 180,
      xpToNextLevel: 300,
    );

    return Scaffold(
      appBar: AppBar(
        title: const Text('User Profile'),
        backgroundColor: const Color(0xFF1A1A1A),
      ),
      backgroundColor: const Color(0xFF333333), // Dark background for the page
      body: Center(
        child: Padding(
          padding: const EdgeInsets.all(20.0),
          child: UserBadgeWidget(
            badgeData: userData,
            xpBarProgressColor: Colors.deepOrangeAccent,
            levelTextColor: Colors.amberAccent,
          ),
        ),
      ),
    );
  }
}

Further Enhancements and Considerations:

  • Animations: For a more dynamic feel, you can animate the XP bar's progress using TweenAnimationBuilder or AnimatedBuilder when the currentXp changes. This provides a satisfying visual feedback as users gain experience.
  • Theming and Customization: Extend the widget with more properties to allow customization of font styles, colors, padding, and even different badge shapes or icons. Consider using a `Theme` or `InheritedWidget` for app-wide styling.
  • Backend Integration: In a real-world application, UserBadgeData would be fetched from a backend service, often through a state management solution like Provider, BLoC, or Riverpod, to keep the UI synchronized with the user's actual progress.
  • Responsiveness: Ensure the widget scales well across different screen sizes and orientations by using layout widgets effectively and considering relative sizing where appropriate.
  • Avatar/Profile Picture: You could easily integrate a user's avatar or a specific badge icon into the widget's layout to make it more personalized and visually rich.
  • Tap Gestures: Add `GestureDetector` to make the badge tappable, potentially showing a detailed progress screen or achievements.

Conclusion:

Building a custom user badge widget with a level and XP bar in Flutter is an excellent way to enhance your application's user experience through gamification. By leveraging Flutter's powerful rendering capabilities with CustomPainter and combining it with thoughtful UI design, you can create engaging and visually appealing components that motivate users and foster a sense of progression. This modular approach ensures your widget is reusable, maintainable, and easily extensible for future features, significantly boosting user engagement in your application.

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