Flutter: ChangeNotifier、ValueNotifier、Stream を使ったカウンターアプリ

Flutter: ChangeNotifier、ValueNotifier、Stream を使ったカウンターアプリ

June 3, 2024

こんな感じです。

ChangeNotifier #

import 'package:flutter/material.dart';

class Counter with ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}

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

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

  final Counter _counter = Counter();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyWidget(
        counter: _counter,
      ),
    );
  }
}

class MyWidget extends StatelessWidget {
  const MyWidget({
    super.key,
    required this.counter,
  });

  final Counter counter;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ListenableBuilder(
              listenable: counter,
              builder: (context, child) {
                return Text('${counter.count}');
              },
            ),
            IconButton(
              onPressed: counter.increment,
              icon: const Icon(Icons.add),
            )
          ],
        ),
      ),
    );
  }
}

ValueNotifier #

import 'package:flutter/material.dart';

class Counter {
  final ValueNotifier<int> count = ValueNotifier<int>(0);

  void increment() {
    count.value++;
  }
}

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

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

  final Counter _counter = Counter();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyWidget(
        counter: _counter,
      ),
    );
  }
}

class MyWidget extends StatelessWidget {
  const MyWidget({
    super.key,
    required this.counter,
  });

  final Counter counter;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ValueListenableBuilder(
              valueListenable: counter.count,
              builder: (context, value, child) {
                return Text('${counter.count.value}');
              },
            ),
            IconButton(
              onPressed: counter.increment,
              icon: const Icon(Icons.add),
            )
          ],
        ),
      ),
    );
  }
}

ちなみに ValueNotifier は単一の値のみを保持する場合における ChangeNotifier をよりシンプルに使いやすくしたものです。そのため、機能的には ChangeNotifier で実装した場合とほとんど違いありません。

// 重要部分のみ抜粋

class ValueNotifier<T> extends ChangeNotifier implements ValueListenable<T> {
  ValueNotifier(this._value) {
      ChangeNotifier.maybeDispatchObjectCreation(this);
  }

  @override
  T get value => _value;

  T _value;

  set value(T newValue) {
    if (_value == newValue) {
      return;
    }
    _value = newValue;
    notifyListeners();
  }
}

Stream #

import 'dart:async';

import 'package:flutter/material.dart';

class Counter {
  Counter() {
    _streamController.add(_count);
  }

  final _streamController = StreamController<int>();

  Stream<int> get stream {
    return _streamController.stream;
  }

  int _count = 0;
  int get count => _count;

  void increment() {
    _count++;
    _streamController.add(_count);
  }
}

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

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

  final Counter _counter = Counter();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyWidget(
        counter: _counter,
      ),
    );
  }
}

class MyWidget extends StatelessWidget {
  const MyWidget({
    super.key,
    required this.counter,
  });

  final Counter counter;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            StreamBuilder<int>(
              stream: counter.stream,
              builder: (context, snapshot) {
                return Text('${snapshot.data}');
              },
            ),
            IconButton(
              onPressed: counter.increment,
              icon: const Icon(Icons.add),
            )
          ],
        ),
      ),
    );
  }
}

ChangeNotifier with Provider #

ちなみに ChangeNotifier のパターンに Provider を組み合わせるとこんな感じです。

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

class Counter with ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: ChangeNotifierProvider(
        create: (_) => Counter(),
        child: const MyWidget(),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    final counter = context.watch<Counter>();

    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ListenableBuilder(
              listenable: counter,
              builder: (context, child) {
                return Text('${counter.count}');
              },
            ),
            IconButton(
              onPressed: counter.increment,
              icon: const Icon(Icons.add),
            )
          ],
        ),
      ),
    );
  }
}