publisher_subscriber 0.0.2 copy "publisher_subscriber: ^0.0.2" to clipboard
publisher_subscriber: ^0.0.2 copied to clipboard

A simple, minimal, and opinionated state management library for Flutter.

example/lib/main.dart

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

// --- Concrete Publisher Implementations ---

class GlobalCounter extends Publisher<int> {
  GlobalCounter() : super(0);
  void increment() => setState(state + 1);
}

class ScopedCounter extends Publisher<int> {
  ScopedCounter() : super(0);
  void increment() => setState(state + 1);
}

// --- APP ENTRY POINT & RECIPE DEFINITIONS ---

void main() {
  GlobalPublisherObserver.on<ScopedCounter>(
    (state) => debugPrint('ScopedCounter changed: $state'),
  );
  GlobalPublisherObserver.on<GlobalCounter>(
    (state) => debugPrint('GlobalCounter changed: $state'),
  );

  runApp(const MainApp());
}

final globalCounterRecipe = Publisher.global(() => GlobalCounter());
final scopedCounterRecipe = Publisher.scoped(() => ScopedCounter());

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Simple State Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        brightness: Brightness.light,
        scaffoldBackgroundColor: Colors.grey.shade100,
        appBarTheme: const AppBarTheme(
          backgroundColor: Colors.white,
          foregroundColor: Colors.black87,
          elevation: 1,
        ),
        elevatedButtonTheme: ElevatedButtonThemeData(
          style: ElevatedButton.styleFrom(
            foregroundColor: Colors.black87,
            backgroundColor: Colors.white,
            elevation: 0,
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(8),
              side: BorderSide(color: Colors.grey.shade300),
            ),
            padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14),
          ),
        ),
      ),
      home: const HomePage(),
    );
  }
}

// --- UI (VIEWS) ---

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Home Page')),
      body: Subscriber(
        builder: (context) {
          // Access is now consistent for both types using the context.
          final globalCounter = context.read(globalCounterRecipe);
          final scopedCounter = context.read(scopedCounterRecipe);

          return Center(
            child: Padding(
              padding: const EdgeInsets.all(24.0),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: [
                  _CounterDisplay(
                    title: 'GLOBAL COUNTER',
                    subtitle: '(State persists across all pages)',
                    count: globalCounter.state,
                    onPressed: globalCounter.increment,
                  ),
                  const SizedBox(height: 40),
                  _CounterDisplay(
                    title: 'SCOPED COUNTER',
                    subtitle: '(State is disposed when leaving this page)',
                    count: scopedCounter.state,
                    onPressed: scopedCounter.increment,
                  ),
                  const SizedBox(height: 40),
                  ElevatedButton(
                    child: const Text('Go to Second Page'),
                    onPressed: () {
                      Navigator.pushReplacement(
                        context,
                        MaterialPageRoute(builder: (_) => const SecondPage()),
                      );
                    },
                  ),
                ],
              ),
            ),
          );
        },
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Second Page')),
      body: Subscriber(
        builder: (context) {
          // We only read the global recipe here.
          final globalCounter = context.read(globalCounterRecipe);

          return Center(
            child: Padding(
              padding: const EdgeInsets.all(24.0),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: [
                  _CounterDisplay(
                    title: 'GLOBAL COUNTER',
                    subtitle: '(State was preserved from Home Page)',
                    count: globalCounter.state,
                    onPressed: globalCounter.increment,
                  ),
                  const SizedBox(height: 40),
                  Container(
                    padding: const EdgeInsets.all(16),
                    decoration: BoxDecoration(
                      color: Colors.grey.shade200,
                      borderRadius: BorderRadius.circular(8),
                    ),
                    child: const Text(
                      'The Scoped Counter does not exist on this page. It was disposed when HomePage was replaced.',
                      textAlign: TextAlign.center,
                      style: TextStyle(color: Colors.black54),
                    ),
                  ),
                  const SizedBox(height: 40),
                  ElevatedButton(
                    child: const Text('Go Back to Home Page'),
                    onPressed: () {
                      Navigator.pushReplacement(
                        context,
                        MaterialPageRoute(builder: (_) => const HomePage()),
                      );
                    },
                  ),
                ],
              ),
            ),
          );
        },
      ),
    );
  }
}

// --- SHARED WIDGETS ---

class _CounterDisplay extends StatelessWidget {
  const _CounterDisplay({
    required this.title,
    required this.subtitle,
    required this.count,
    required this.onPressed,
  });

  final String title;
  final String subtitle;
  final int count;
  final VoidCallback onPressed;

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(8),
        border: Border.all(color: Colors.grey.shade300),
      ),
      child: Column(
        children: [
          Text(title, style: Theme.of(context).textTheme.titleLarge),
          const SizedBox(height: 4),
          Text(subtitle, style: Theme.of(context).textTheme.bodySmall),
          const SizedBox(height: 16),
          Text('$count', style: Theme.of(context).textTheme.displayMedium),
          const SizedBox(height: 16),
          ElevatedButton(onPressed: onPressed, child: const Text('Increment')),
        ],
      ),
    );
  }
}
0
likes
140
points
3
downloads

Publisher

unverified uploader

Weekly Downloads

A simple, minimal, and opinionated state management library for Flutter.

Repository (GitHub)
View/report issues

Topics

#riverpod #provider #bloc #getx #publisher-subscriber

Documentation

API reference

License

unknown (license)

Dependencies

flutter

More

Packages that depend on publisher_subscriber