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),
)
],
),
),
);
}
}