Created
December 14, 2018 23:05
-
-
Save HansMuller/a3a6d520c6a24238bf1b1b9e3d473bf5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
ModelBinding<T>: a simple class for binding a Flutter app to a immutable | |
model of type T. | |
This is a complete application. The app shows the state of an instance of | |
MyModel in a button's label, and replaces its MyModel instance when the | |
button is pressed. | |
ModelBinding is an inherited widget that must be created in a context above | |
the widgets that will depend on it. A ModelBinding is created like this: | |
ModelBinding<MyModel>( | |
initialState: MyModel(), | |
child: child, | |
) | |
ModelBinding provides a static method 'of<T>(context)' that can be used | |
by any descendant to retrieve the current model instance, and a similar | |
static 'update<T>(context, T newModel)' method for replacing the | |
current model. Both methods implicitly create a dependency on the model: | |
when the model changes the corresponding context will be rebuilt. | |
In this example the model is retrieved and updated like this: | |
RaisedButton( | |
onPressed: () { | |
final MyModel model = ModelBinding.of<MyModel>(context); | |
ModelBinding.update<MyModel>(context, MyModel(value: model.value + 1)); | |
}, | |
child: Text('Hello World ${ModelBinding.of<MyModel>(context).value}'), | |
) | |
To use ModelBinding in your own app define an immutable model class like | |
MyModel that contains the application's state. Add the three ModelBinding | |
classes below (_ModelBindingScope, ModelBinding, _ModelBindingState) to a file | |
and import that where it's needed. | |
*/ | |
import 'package:flutter/material.dart'; | |
class MyModel { | |
const MyModel({ this.value = 0 }); | |
final int value; | |
@override | |
bool operator ==(Object other) { | |
if (identical(this, other)) | |
return true; | |
if (other.runtimeType != runtimeType) | |
return false; | |
final MyModel otherModel = other; | |
return otherModel.value == value; | |
} | |
@override | |
int get hashCode => value.hashCode; | |
} | |
class _ModelBindingScope<T> extends InheritedWidget { | |
const _ModelBindingScope({ | |
Key key, | |
this.modelBindingState, | |
Widget child | |
}) : super(key: key, child: child); | |
final _ModelBindingState<T> modelBindingState; | |
@override | |
bool updateShouldNotify(_ModelBindingScope oldWidget) => true; | |
} | |
class ModelBinding<T> extends StatefulWidget { | |
ModelBinding({ | |
Key key, | |
@required this.initialModel, | |
this.child, | |
}) : assert(initialModel != null), super(key: key); | |
final T initialModel; | |
final Widget child; | |
_ModelBindingState<T> createState() => _ModelBindingState<T>(); | |
static Type _typeOf<T>() => T; // https://github.com/dart-lang/sdk/issues/33297 | |
static T of<T>(BuildContext context) { | |
final Type scopeType = _typeOf<_ModelBindingScope<T>>(); | |
final _ModelBindingScope<T> scope = context.inheritFromWidgetOfExactType(scopeType); | |
return scope.modelBindingState.currentModel; | |
} | |
static void update<T>(BuildContext context, T newModel) { | |
final Type scopeType = _typeOf<_ModelBindingScope<T>>(); | |
final _ModelBindingScope<dynamic> scope = context.inheritFromWidgetOfExactType(scopeType); | |
scope.modelBindingState.updateModel(newModel); | |
} | |
} | |
class _ModelBindingState<T> extends State<ModelBinding<T>> { | |
T currentModel; | |
@override | |
void initState() { | |
super.initState(); | |
currentModel = widget.initialModel; | |
} | |
void updateModel(T newModel) { | |
if (newModel != currentModel) { | |
setState(() { | |
currentModel = newModel; | |
}); | |
} | |
} | |
@override | |
Widget build(BuildContext context) { | |
return _ModelBindingScope<T>( | |
modelBindingState: this, | |
child: widget.child, | |
); | |
} | |
} | |
class ViewController extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return RaisedButton( | |
onPressed: () { | |
final MyModel model = ModelBinding.of<MyModel>(context); | |
ModelBinding.update<MyModel>(context, MyModel(value: model.value + 1)); | |
}, | |
child: Text('Hello World ${ModelBinding.of<MyModel>(context).value}'), | |
); | |
} | |
} | |
class App extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
home: ModelBinding<MyModel>( | |
initialModel: const MyModel(), | |
child: Scaffold( | |
body: Center( | |
child: ViewController(), | |
), | |
), | |
), | |
); | |
} | |
} | |
void main() { | |
runApp(App()); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks for your code. I tried to use ModelBinding with a FutureBuilder but unfortunately it doesn't work. The returned scope is null in ModelBinding.of.