flutter_timezone_observer

A Flutter package for accessing the native device timezone (IANA) and observing changes. Includes mixins to automatically handle app lifecycle events.

Build Status pub package codecov style: very good analysis Verified Publisher


Features

  • Get Current Timezone: One-time async call to get the current IANA timezone (e.g., America/New_York).
  • Listen to Changes: Get a Stream of timezone changes reported by the platform.
  • Lifecycle-Aware Mixin: Includes AppLifecycleAwareTimezoneObserverMixin to automatically re-fetch the timezone when the app resumes.
  • Simple Mixin: Includes a basic TimezoneObserverMixin for simple use cases.
  • Minimal: No external Flutter package dependencies.

Platform Support

Platform Timezone source Change detection
Android ZoneId.systemDefault() ACTION_TIMEZONE_CHANGED broadcast
iOS TimeZone.current.identifier NSSystemTimeZoneDidChange notification
Web Intl.DateTimeFormat().resolvedOptions().timeZone Re-read on visibilitychange / focus, plus polling every 5s while visible

The public API is identical on every platform — see the notes on Web for the differences in how changes are detected there.


How to Use

1. One-time Read

Get the current timezone on app start or any time you need it.

import 'package.flutter_timezone_observer/flutter_timezone_observer.dart';

Future<void> main() async {
  final String timezone = await FlutterTimezoneObserver.currentTimezone;
  print('Current device timezone: $timezone');
  runApp(MyApp());
}

2. Listen to Stream

Listen to the native broadcast for timezone changes.

final subscription = FlutterTimezoneObserver.onTimezoneChanged.listen((zone) {
print('Timezone changed: $zone');
});

// Don't forget to cancel
subscription.cancel();

This is the most reliable way. Use this mixin on your State to automatically handle initialization, stream listening, and app-resume logic.

import 'package.flutter/material.dart';
import 'package:flutter_timezone_observer/app_lifecycle_aware_timezone_observer_mixin.dart';

class MyWidget extends StatefulWidget {
  @override
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget>
    with AppLifecycleAwareTimezoneObserverMixin {
      
  @override
  void onTimezoneChanged(String newZone) {
    // The mixin handles all the logic.
    // Just call setState to rebuild with the new value.
    setState(() {
      // `currentZone` (from the mixin) is already updated
    });
  }

  @override
  Widget build(BuildContext context) {
    // `currentZone` is provided by the mixin
    return Text(
      'Current Zone: ${currentZone ?? 'Loading...'}',
    );
  }
}

4. Usage with Simple Mixin (Basic)

If you only care about the stream and don't need the app-resume logic, use TimezoneObserverMixin.

import 'package:flutter_timezone_observer/timezone_observer_mixin.dart';

class MyWidget extends StatefulWidget {
  // ...
}

class _MyWidgetState extends State<MyWidget>
    with TimezoneObserverMixin { // <-- Note: the simple mixin
      
  @override
  void onTimezoneChanged(String newZone) {
    setState(() {});
  }
  
  // ...
}

5. Advanced: Handling Paused State

By default, AppLifecycleAwareTimezoneObserverMixin ignores stream events while the app is paused (to prevent background setState calls), as it re-fetches the timezone on resume anyway.

You can override this behavior by setting emitTimezoneWhenPaused to true.

import 'package.flutter/material.dart';
import 'package:flutter_timezone_observer/app_lifecycle_aware_timezone_observer_mixin.dart';

class MyWidget extends StatefulWidget {
  // ...
}

class _MyWidgetState extends State<MyWidget>
    with AppLifecycleAwareTimezoneObserverMixin {
      
  // Override the getter to enable background updates
  @override
  bool get emitTimezoneWhenPaused => true;
  
  @override
  void onTimezoneChanged(String newZone) {
    // This will now fire even if the app is paused.
  }
  
  @override
  Widget build(BuildContext context) {
    return Text(
      'Current Zone: ${currentZone ?? 'Loading...'}',
    );
  }
}

Web

Browsers do not broadcast a "timezone changed" event, so the change cannot be observed the way it is on Android and iOS. Instead, the web implementation re-reads Intl.DateTimeFormat().resolvedOptions().timeZone:

  • Immediately when the page becomes visible again or the window regains focus — the usual path when the user changes the timezone in the OS settings and switches back to the browser.
  • Every 5 seconds while the page is visible. Polling is suspended while the page is hidden, since browsers throttle timers in background tabs anyway.

onTimezoneChanged only emits when the value actually differs from the previously seen one, so the polling is invisible to your listeners.

Nothing has to be registered manually: the plugin is wired up by Flutter's web plugin registrant. Both dart2js and dart2wasm builds are supported.

Notes and limitations

  • Detection depends on the browser refreshing the timezone it has resolved for the page. Current versions of Chrome, Firefox and Safari do that when the OS timezone changes; a browser that only refreshes on reload cannot be observed by any polling strategy.
  • The mixins work on web, but through the stream rather than through the app lifecycle: Flutter's web engine never reports AppLifecycleState.paused (it reports hidden instead), so in AppLifecycleAwareTimezoneObserverMixin the re-fetch on resume never runs and emitTimezoneWhenPaused has no effect there. The visibilitychange handling above covers the same scenario, so returning to the tab still delivers the new timezone.

If the default cadence does not fit your app, you can install the web implementation yourself with a different one — for example, to rely on visibility and focus events only:

// web_timezone_setup.dart — import this only from web-specific code.
import 'package:flutter_timezone_observer/flutter_timezone_observer_platform_interface.dart';
import 'package:flutter_timezone_observer/flutter_timezone_observer_web.dart';

void setUpTimezoneObserver() {
  FlutterTimezoneObserverPlatform.instance = FlutterTimezoneObserverWeb(
    pollInterval: Duration.zero, // or any custom interval
  );
}

Installation

1. Add to pubspec.yaml

dart pub add flutter_timezone_observer