blob: f2a2bc6cbc3e691e3ad267b192ac74994a7cc983 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:flutter/widgets.dart';
/// Base class for classes that provide data via [InheritedWidget]s.
abstract class Model extends Listenable {
final Set<VoidCallback> _listeners = <VoidCallback>{};
int _version = 0;
int _microtaskVersion = 0;
/// [listener] will be notified when the model changes.
@override
void addListener(VoidCallback listener) {
_listeners.add(listener);
}
/// [listener] will no longer be notified when the model changes.
@override
void removeListener(VoidCallback listener) {
_listeners.remove(listener);
}
/// Returns the number of listeners listening to this model.
int get listenerCount => _listeners.length;
/// Should be called only by [Model] when the model has changed.
void notifyListeners() {
// We schedule a microtask as it's not uncommon for changes that trigger
// listener notifications to occur in a build step and for listeners to
// call setState. Its a big no-no to setState during build so we schedule
// for them to happen later.
// TODO(apwilson): This is a bad-flutter-code-smell. Eliminate the need for
// this scheduleMicrotask.
if (_microtaskVersion == _version) {
_microtaskVersion++;
scheduleMicrotask(() {
_version++;
_microtaskVersion = _version;
for (VoidCallback listener in _listeners) {
listener();
}
});
}
}
}
/// Finds a [Model]. This class is necessary as templated classes are relified
/// but static templated functions are not.
class ModelFinder<T extends Model> {
/// Returns the [Model] of type [T] of the closest ancestor [ScopedModel].
///
/// [Widget]s who call [of] with a [rebuildOnChange] of true will be rebuilt
/// whenever there's a change to the returned model.
T of(BuildContext context, {bool rebuildOnChange = false}) {
// ignore: prefer_const_constructors
final Type type = _InheritedModel<T>.forRuntimeType().runtimeType;
Widget widget = rebuildOnChange
? context.inheritFromWidgetOfExactType(type)
: context.ancestorWidgetOfExactType(type);
return (widget is _InheritedModel<T>) ? widget.model : null;
}
}
/// Allows the given [model] to be accessed by [child] or any of its descendants
/// using [ModelFinder].
class ScopedModel<T extends Model> extends StatelessWidget {
/// The [Model] to provide to [child] and its descendants.
final T model;
/// The [Widget] the [model] will be available to.
final Widget child;
/// Constructor.
const ScopedModel({this.model, this.child});
@override
Widget build(BuildContext context) => _ModelListener(
model: model,
builder: (BuildContext context) => _InheritedModel<T>(
model: model,
child: child,
),
);
}
/// Listens to [model] and calls [builder] whenever [model] changes.
class _ModelListener extends StatefulWidget {
final Model model;
final WidgetBuilder builder;
const _ModelListener({this.model, this.builder});
@override
_ModelListenerState createState() => _ModelListenerState();
}
class _ModelListenerState extends State<_ModelListener> {
@override
void initState() {
super.initState();
widget.model.addListener(_onChange);
}
@override
void didUpdateWidget(_ModelListener oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.model != oldWidget.model) {
oldWidget.model.removeListener(_onChange);
widget.model.addListener(_onChange);
}
}
@override
void dispose() {
widget.model.removeListener(_onChange);
super.dispose();
}
@override
Widget build(BuildContext context) => widget.builder(context);
void _onChange() => setState(() {});
}
/// Provides [model] to its [child] [Widget] tree via [InheritedWidget]. When
/// [version] changes, all descendants who request (via
/// [BuildContext.inheritFromWidgetOfExactType]) to be rebuilt when the model
/// changes will do so.
class _InheritedModel<T extends Model> extends InheritedWidget {
final T model;
final int version;
_InheritedModel({Key key, Widget child, this.model})
: version = model._version,
super(key: key, child: child);
/// Used to return the runtime type.
const _InheritedModel.forRuntimeType()
: model = null,
version = 0;
@override
bool updateShouldNotify(_InheritedModel<T> oldWidget) =>
oldWidget.version != version;
}
/// Builds a child for a [ScopedModelDescendant].
typedef ScopedModelDescendantBuilder<T extends Model> = Widget Function(
BuildContext context,
Widget child,
T model,
);
/// A [Widget] who rebuilds its child by calling [builder] whenever the [Model]
/// provided by an ancestor [ScopedModel] changes.
class ScopedModelDescendant<T extends Model> extends StatelessWidget {
/// Called whenever the [Model] changes.
final ScopedModelDescendantBuilder<T> builder;
/// An optional constant child that does not depend on the model. This will
/// be passed as the child of [builder].
final Widget child;
/// Constructor.
const ScopedModelDescendant({this.builder, this.child});
@override
Widget build(BuildContext context) => builder(
context,
child,
ModelFinder<T>().of(context, rebuildOnChange: true),
);
}