Flutter Route Refresh

pub package License: MIT

Automatically refresh stale data when users navigate back to a screen.

Flutter Route Refresh solves a common problem in Flutter apps: keeping screen data fresh after navigation. When users navigate away and return, this package automatically refreshes only the data that changed — no manual refresh logic needed.


The Problem

Screen A (Product List)  →  Screen B (Edit Product)  →  Back to Screen A
        ↓                                                      ↓
   Shows products                                    Still shows OLD data! 😞

Typically, you'd need to manually track state changes, pass callbacks, or use complex state management just to refresh data when returning to a screen.

The Solution

Screen A (Product List)  →  Screen B (Edit Product)  →  Back to Screen A
        ↓                         ↓                           ↓
   Shows products          Posts "updateProducts"      Auto-refreshes! ✨
                               event

Flutter Route Refresh handles this automatically with a simple event-based system.


Features

  • Automatic Refresh — Data refreshes automatically when returning to a screen
  • Event-Driven — Mark data as stale via events from anywhere in your app
  • Selective Updates — Only refresh the specific data that changed
  • Type-Safe — Generic types for events and keys prevent runtime errors
  • Lightweight — No external dependencies, minimal footprint
  • Easy Integration — Just add a mixin to your existing StatefulWidgets

Installation

Add to your pubspec.yaml:

dependencies:
  flutter_route_refresh: ^1.0.0

Then run:

flutter pub get

Quick Start

1. Initialize the Package

In your main.dart, create a RouteObserver and initialize the package:

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

final RouteObserver<ModalRoute<void>> routeObserver = RouteObserver<ModalRoute<void>>();

void main() {
  FlutterRouteRefresh.init(routeObserver: routeObserver);
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      navigatorObservers: [routeObserver], // Don't forget this!
      home: const HomeScreen(),
    );
  }
}

2. Define Your Events and Keys

Create enums for your events and refresh keys:

// Events that can trigger data refresh
enum AppEvent {
  productUpdated,
  cartModified,
  userProfileChanged,
}

// Keys identifying refreshable data
enum RefreshKey {
  productList,
  cartItems,
  userProfile,
}

3. Add the Mixin to Your Screen

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

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

class _HomeScreenState extends State<HomeScreen>
    with DirtyKeysRefresher<HomeScreen, AppEvent, RefreshKey> {

  List<Product> products = [];

  @override
  void initState() {
    super.initState();

    // Register refresh callbacks
    registerRefresher(RefreshKey.productList, _fetchProducts);

    // Initial load
    _fetchProducts();
  }

  Future<void> _fetchProducts() async {
    final data = await api.getProducts();
    setState(() => products = data);
  }

  // Map events to the keys that should refresh
  @override
  Map<AppEvent, List<RefreshKey>> get staleEventMap => {
    AppEvent.productUpdated: [RefreshKey.productList],
  };

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Products')),
      body: ListView.builder(
        itemCount: products.length,
        itemBuilder: (context, index) => ProductTile(
          product: products[index],
          onTap: () => Navigator.push(
            context,
            MaterialPageRoute(
              builder: (_) => EditProductScreen(product: products[index]),
            ),
          ),
        ),
      ),
    );
  }
}

4. Post Events When Data Changes

From any screen, post an event when data is modified:

class EditProductScreen extends StatelessWidget {
  final Product product;

  const EditProductScreen({super.key, required this.product});

  Future<void> _saveProduct(BuildContext context) async {
    await api.updateProduct(product);

    // Notify that product data changed
    GlobalMessenger.instance<AppEvent>().postMessage(AppEvent.productUpdated);

    Navigator.pop(context);
  }

  @override
  Widget build(BuildContext context) {
    // ... your edit form UI
  }
}

That's it! When the user saves and navigates back, the product list automatically refreshes.


API Reference

FlutterRouteRefresh

Initialize the package with your route observer.

// Initialize (call once at app startup)
FlutterRouteRefresh.init(routeObserver: yourRouteObserver);

// Check initialization status
bool ready = FlutterRouteRefresh.isInitialized;

// Access the route observer
RouteObserver observer = FlutterRouteRefresh.routeObserver;

DirtyKeysRefresher Mixin

Add to any State class to enable automatic refresh.

Method Description
registerRefresher(key, callback) Register a refresh callback for a key
unregisterRefresher(key) Remove a registered callback
markStaleKey(key) Manually mark a key as stale
markStaleKeys(keys) Mark multiple keys as stale
unmarkStaleKey(key) Remove a key from the stale set
clearAllStale() Clear all stale keys
Property Description
staleEventMap Override to map events → keys to refresh

GlobalMessenger

A lightweight event bus for posting events.

// Get the singleton instance for your event type
final messenger = GlobalMessenger.instance<AppEvent>();

// Post an event (notifies all listeners)
messenger.postMessage(AppEvent.productUpdated);

// Manual listener management (usually not needed)
messenger.addListener(AppEvent.productUpdated, myCallback);
messenger.removeListener(AppEvent.productUpdated, myCallback);

Advanced Usage

Multiple Keys per Event

Refresh multiple data sources when a single event occurs:

@override
Map<AppEvent, List<RefreshKey>> get staleEventMap => {
  AppEvent.orderPlaced: [
    RefreshKey.cartItems,      // Clear the cart
    RefreshKey.orderHistory,   // Update order list
    RefreshKey.userPoints,     // Update loyalty points
  ],
};

Multiple Events per Key

The same data can be refreshed by different events:

@override
Map<AppEvent, List<RefreshKey>> get staleEventMap => {
  AppEvent.productUpdated: [RefreshKey.productList],
  AppEvent.productDeleted: [RefreshKey.productList],
  AppEvent.categoryChanged: [RefreshKey.productList],
};

Manual Stale Marking

Mark data as stale without using events:

// Mark a single key
markStaleKey(RefreshKey.productList);

// Mark multiple keys
markStaleKeys([RefreshKey.productList, RefreshKey.cartItems]);

Architecture

┌─────────────────────────────────────────────────────────────────┐
│                        Your App                                  │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   ┌──────────────┐    posts event    ┌──────────────────────┐   │
│   │  Screen B    │ ───────────────── │   GlobalMessenger    │   │
│   │  (Editor)    │                   │   (Event Bus)        │   │
│   └──────────────┘                   └──────────┬───────────┘   │
│                                                  │               │
│                                        notifies  │               │
│                                                  ▼               │
│   ┌──────────────┐    route change   ┌──────────────────────┐   │
│   │  Screen A    │ ◄──────────────── │   RouteObserver      │   │
│   │  (List)      │                   │   (Flutter)          │   │
│   │              │                   └──────────────────────┘   │
│   │  with        │                                              │
│   │  DirtyKeys   │ ─── checks stale keys ──┐                    │
│   │  Refresher   │                         │                    │
│   │              │ ◄── executes callbacks ─┘                    │
│   └──────────────┘                                              │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Example Use Cases

Scenario Event Refreshed Data
User edits profile profileUpdated User info, avatar
Item added to cart cartModified Cart count, cart items
New comment posted commentAdded Comments list
Order placed orderPlaced Cart, order history, points
Settings changed settingsChanged Theme, locale, preferences

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License - see the LICENSE file for details.