observable_pattern 1.0.5 observable_pattern: ^1.0.5 copied to clipboard
Observable Pattern is a pattern that use a class to hold a value to manage changes to dispatch notification for every callbacks
Observable Pattern #
Example #
import 'package:flutter/material.dart';
import 'package:observable_pattern/observable_pattern.dart';
//Create a store with variables of type Observable
class MyStore {
final email = Observable<String, String>();
final password = Observable<String, String>();
Observable<bool, void> _canSubmit;
final loading = Observable<bool, void>(false);
final done = Observable<bool, void>(false);
MyStore() {
email.transformer = (event) {
if (event == null || event.isEmpty) {
return 'Type your email';
} else {
return null;
}
};
password.transformer = (event) {
if (event == null || event.isEmpty) {
return 'Type your password';
} else {
return null;
}
};
}
void signIn() async {
if (loading.value) return;
canSubmit.validate();
if (!canSubmit.value) return;
loading.add(true);
await Future.delayed(const Duration(seconds: 3));
loading.add(false);
done.add(true);
}
Observable<bool, void> get canSubmit {
if (_canSubmit == null) {
_canSubmit = Observable.combine<String, String>([email, password]);
}
return _canSubmit;
}
}
//Initialize your app
class MyApp extends StatelessWidget {
final String title;
const MyApp({Key key, this.title}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: title,
theme: ThemeData(primarySwatch: Colors.blue),
home: LoginScreen(title: title),
);
}
}
//Create your own stateful screen
class LoginScreen extends StatefulWidget {
final String title;
const LoginScreen({Key key, this.title}) : super(key: key);
@override
_LoginScreenState createState() => _LoginScreenState();
}
//Into your state class put your store and the magic will begin
class _LoginScreenState extends State<LoginScreen> {
final store = MyStore();
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback(callback);
}
void callback(_) {
final focus = FocusScope.of(context);
store.loading.addListener((bool event) {
if (event) {
focus.unfocus();
//do something...
}
});
store.done.addListener((bool event) {
if (event) {
//do something...
}
});
}
@override
Widget build(BuildContext context) {
return Stack(
fit: StackFit.expand,
children: <Widget>[
Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text(widget.title),
),
body: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Observer<String, String>(
observable: store.email,
builder: (BuildContext context, String value, String error) =>
TextField(
decoration: InputDecoration(
labelText: 'E-mail',
errorText: error,
),
onChanged: store.email.add,
),
),
Observer(
observable: store.password,
builder: (BuildContext context, String value, String error) =>
TextField(
decoration: InputDecoration(
labelText: 'Password',
errorText: error,
),
obscureText: true,
onChanged: store.password.add,
),
),
const SizedBox(height: 24.0),
RaisedButton(
shape: StadiumBorder(),
color: Colors.blue,
onPressed: store.signIn,
child: Text(
'Sign in without reactive',
style: TextStyle(color: Colors.white),
),
),
const SizedBox(height: 24.0),
Observer<bool, void>(
observable: store.canSubmit,
builder: (BuildContext context, bool value, void error) =>
RaisedButton(
shape: StadiumBorder(),
color: Colors.blue,
onPressed: value ? store.signIn : null,
child: Text(
'Sign in with reactive',
style: TextStyle(color: Colors.white),
),
),
)
],
),
),
),
Observer<bool, void>(
observable: store.loading,
builder: (BuildContext context, bool value, void error) => value
? Material(
color: Colors.black54,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
],
),
)
: Container(),
)
],
);
}
}
//Initialize your dart app
void main() => runApp(MyApp(title: 'Observable Pattern Example'));
Observable class #
//This is how initialize the observable class that holds a value and listen to changes
//you don't need to pass an initial value, but you can set an initial value
Observable<T, E>('Initial Value'); // you can type like T is the type of the value and E is the type of the error.
//you can pass a function to validate the value and reproduce an error object.
Observable.transformer = (event) {
if (event == null || event.isEmpty) {
// return the type of the error if something is wrong
return 'Type your email';
} else {
//return null if everything is ok
return null;
}
};
//You can listen to changes when the value change with addListener.
Observable.addListener((event) {
// do something...
});
//you can change the value with two ways
Observable<String,String>.value = 'New Value';
//or
Observable<String,String>.add('New Value');
//after this the object will listen to the new value and validate automatically
//you can validate manually by yourself using
Observable.validate();
//you can validate and listen to all objects into one using a static function from the object
Observable.combine<T,E>(<Observable<T,E>>[object1, object2])
/*
this static function will return an Observable<bool,void> that listen for every elements inside and validate then using
Observable.validate(); and hold a bool value that indicate if every object inside is validated without error if false
it'll indicate that an object has an error.
*/
Observer Widget #
Observer Widget is a widget that listen changes when a value inside Observable class and change rebuild only where it's localized
in this example only the text widget will be rebuild.
Observer<T, E>(
observable: observableObject,
builder: (BuildContext context, T value, E error) {
return Text('$value');
},
ObserverInjection #
It is a simple way to inject dependencies using Inherited Widget and listen if the object injected change and rebuild everything that is inside it
//Inject
ObserverInjection<UserData>(
builder: (context) => UserData(),
child: Container(),
//If necessary you can dispose
dispose: (context, UserData user) => user.dispose(),
);
//Read
final UserData user = ObserverInjection.of<UserData>(context);