Timer Widget

✨ Features

  • ⏱️ CountUp Timer - Simple countUp with pause/resume (infinity).
  • ⏱️ Countdown Timer - Simple countdown with pause/resume
  • ❄️ Cooldown Button - OTP resend, rate-limited actions
  • 🛡️ Debounce Button - Prevent rapid clicks, form submission
  • 🔄 Async Loader - API calls with auto-retry support
  • 🎮 Global Controller - Control ANY timer from ANYWHERE by ID
  • 🎨 Customizable - Multiple button types, custom styles
  • 📦 Zero Dependencies - No Provider, no external packages

A powerful, all-in-one Flutter timer widget for countdown timers, cooldown buttons, debounce buttons, and async loading. Perfect for OTP resend, rate limiting, form submission, and API calls with retry.

Zero external dependencies - all state management is handled internally!

demo

pub package likes popularity pub points

📦 Installation

dependencies:
  timer_widget: ^1.0.0
import 'package:timer_widget/timer_widget.dart';

🎯 One Widget, Four Behaviors

Select the behavior using timerType:

TimerWidget(
  timerType: TimerType.countdown,  // countdown | cooldown | debounce | asyncLoader
  // ...
)
TimerType Use Case Example
countdown Simple countdown timer Timeouts, delays
cooldown Button with cooldown period OTP resend, refresh
debounce Prevent rapid clicks Form submission
asyncLoader Async operations with retry API calls, data loading

🎮 Global Controller - Control from ANYWHERE!

Just give widget an id and control from any widget, class, or file!

Step 1: Give widgets unique IDs

// OTP Button
TimerWidget(
  id: "otp_button",  // Unique ID
  timerType: TimerType.cooldown,
  timeOutInSeconds: 30,
  builder: (context, state) {
    if (state.isCounting) {
      return Text("Resend in ${state.remainingSeconds}s");
    }
    return Text("Send OTP");
  },
)

// Submit Button
TimerWidget(
  id: "submit_button",  // Different ID
  timerType: TimerType.debounce,
  asyncOperation: () async => await submitForm(),
  builder: (context, state) {
    return Text(state.isLoading ? "Submitting..." : "Submit");
  },
)

Step 2: Control from ANYWHERE!

// From any widget, any page, any class!
TimerWidgetController.start("otp_button");
TimerWidgetController.stop("otp_button");
TimerWidgetController.pause("otp_button");
TimerWidgetController.resume("otp_button");

Step 3: Check state from ANYWHERE!

// Get state
TimerWidgetController.isCounting("otp_button");      // bool
TimerWidgetController.isPaused("otp_button");        // bool
TimerWidgetController.remainingSeconds("otp_button"); // int
TimerWidgetController.isLoading("submit_button");    // bool
TimerWidgetController.isSuccess("submit_button");    // bool
TimerWidgetController.isError("submit_button");      // bool
TimerWidgetController.getData("loader");             // dynamic
TimerWidgetController.getError("loader");            // Object?

Bulk Control All Timers

TimerWidgetController.startAll();
TimerWidgetController.stopAll();
TimerWidgetController.pauseAll();
TimerWidgetController.resumeAll();
TimerWidgetController.resetAll();

Utility Methods

TimerWidgetController.allIds;        // List<String> - all registered IDs
TimerWidgetController.hasId("x");    // bool - check if exists
TimerWidgetController.count;         // int - number of registered timers

📱 Complete Example

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

class MyPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // OTP Resend Button
        TimerWidget(
          id: "otp",
          timerType: TimerType.cooldown,
          timeOutInSeconds: 30,
          buttonType: ButtonType.outline,
          onPressed: () => sendOTP(),
          builder: (context, state) {
            if (state.isCounting) {
              return Text("Resend in ${state.remainingSeconds}s");
            }
            return Text("Send OTP");
          },
        ),

        // Form Submit with Debounce
        TimerWidget<void>(
          id: "submit",
          timerType: TimerType.debounce,
          debounceMs: 500,
          buttonType: ButtonType.elevated,
          asyncOperation: () async {
            await submitForm();
          },
          builder: (context, state) {
            if (state.isLoading) {
              return CircularProgressIndicator();
            }
            return Text("Submit");
          },
        ),

        // External Control Buttons
        ElevatedButton(
          onPressed: () => TimerWidgetController.start("otp"),
          child: Text("Force Send OTP"),
        ),
        ElevatedButton(
          onPressed: () => TimerWidgetController.stopAll(),
          child: Text("Stop All Timers"),
        ),
      ],
    );
  }
}

// Control from ANY class - like a service/provider!
class AuthService {
  static void onLogout() {
    // Stop all timers when user logs out
    TimerWidgetController.stopAll();
  }
  
  static void refreshOTP() {
    // Control specific timer
    TimerWidgetController.start("otp");
  }
}

🚀 Quick Examples

1. Countdown Timer

TimerWidget(
  id: "countdown",
  timerType: TimerType.countdown,
  timeOutInSeconds: 5,
  buttonType: ButtonType.elevated,
  onPressed: () => print("Started!"),
  onComplete: () => print("Done!"),
  builder: (context, state) {
    if (state.isCounting) {
      return Text("Wait ${state.remainingSeconds}s");
    }
    return Text("Click Me");
  },
)

2. OTP Resend / Cooldown Button

TimerWidget(
  id: "otp_resend",
  timerType: TimerType.cooldown,
  timeOutInSeconds: 30,
  buttonType: ButtonType.outline,
  onPressed: () => sendOTP(),
  builder: (context, state) {
    if (state.isCounting) {
      return Text("Resend in ${state.remainingSeconds}s");
    }
    return Text("Send OTP");
  },
)

3. Debounce Button (Form Submission)

TimerWidget<void>(
  id: "form_submit",
  timerType: TimerType.debounce,
  debounceMs: 500,
  buttonType: ButtonType.elevated,
  asyncOperation: () async {
    await submitForm();
  },
  builder: (context, state) {
    if (state.isLoading) {
      return CircularProgressIndicator();
    }
    return Text("Submit");
  },
)

4. Async Loader with Auto-Retry

TimerWidget<UserData>(
  id: "user_loader",
  timerType: TimerType.asyncLoader,
  asyncOperation: () => fetchUserData(),
  retryCount: 3,
  retryDelay: Duration(seconds: 1),
  autoStart: true,
  onSuccess: (data) => print("Loaded!"),
  onError: (error) => print("Failed!"),
  builder: (context, state) {
    if (state.isLoading) return CircularProgressIndicator();
    if (state.isError) return Text("Error: ${state.error}");
    if (state.isSuccess) return Text("Data: ${state.data}");
    return Text("Tap to load");
  },
)

// Control from anywhere
TimerWidgetController.execute("user_loader");  // Load
TimerWidgetController.retry("user_loader");    // Retry
TimerWidgetController.reset("user_loader");    // Reset

5. Stopwatch (Count-up Timer)

Excellent for showing elapsed time during long operations or API calls. Starts at 0 and increments every second. If an asyncOperation is provided, it automatically stops when the operation finishes.

TimerWidget<String>(
  id: "api_sync",
  timerType: TimerType.stopwatch,
  asyncOperation: () => longRunningTask(),
  autoStart: false,
  builder: (context, state) {
    if (state.isLoading) {
      return Text("Task in progress... Elapsed: ${state.remainingSeconds}s");
    }
    if (state.isSuccess) {
      return Text("Completed in ${state.remainingSeconds}s!");
    }
    return Text("Start Task");
  },
)

📖 State Object

The builder receives a TimerWidgetState with all information:

builder: (context, state) {
  state.remainingSeconds  // Countdown seconds remaining
  state.isCounting        // Is timer active?
  state.isPaused          // Is timer paused?
  state.isLoading         // Is async operation loading?
  state.isSuccess         // Did async operation succeed?
  state.isError           // Did async operation fail?
  state.data              // Data from successful operation
  state.error             // Error from failed operation
}

⚙️ All Properties

Property Type Default Description
id String? null Unique ID for global control
timerType TimerType countdown Widget behavior mode
builder Function required Build the UI
timeOutInSeconds int 5 Countdown/cooldown duration
buttonType ButtonType none Button wrapper type
buttonStyle ButtonStyle? null Custom button style
disableDuringCounting bool true Disable during countdown
autoStart bool false Auto-start on mount
onPressed VoidCallback? null Press callback
onComplete VoidCallback? null Complete callback
asyncOperation Future<T> Function()? null Async function
debounceMs int 300 Debounce delay (ms)
retryCount int 0 Auto-retry count
retryDelay Duration 1s Retry delay
onSuccess Function(T)? null Success callback
onError Function(Object)? null Error callback

🎨 Button Types

ButtonType.none      // Raw widget (GestureDetector)
ButtonType.elevated  // ElevatedButton
ButtonType.outline   // OutlinedButton
ButtonType.icon      // IconButton

📱 Example App

Check the /example folder for a complete demo app with all features.

cd example
flutter run

🔑 Keywords

flutter timer, countdown widget, cooldown button, otp resend flutter, debounce button, async loader, loading button, rate limit, flutter button timer, countdown button, timer controller, global controller


📄 License

MIT License - see LICENSE file for details.

🤝 Contributing

Contributions are welcome! Please open an issue or PR on GitHub.


Our Other Packages

  • LiveDB: livedb - A powerful local database with reactive streams.
  • Media Link Generator: media_link_generator - Generate direct download links for various cloud storages.
  • MediaGetter: mediagetter - Complete solution for picking and managing media files.
  • Contacts Getter: contacts_getter - Access and manage device contacts and messages.

Libraries

timer_widget
Timer Widget - All-in-one countdown, cooldown, debounce, and async loader widget