cubes 1.0.2 cubes: ^1.0.2 copied to clipboard
Simple State Manager (Focusing on simplicity and rebuilding only the necessary)
Cubes #
Simple State Manager with dependency injection and no code generation required.
With Cubes, manage the state of the application in a simple and objective way and reconstructing in your widget tree only where necessary!
No uses RxDart, Cubes
uses ChangeNotifier due to its simplicity and immediate updating of the observable values.
Install #
To use this plugin, add cubes
as a dependency in your pubspec.yaml file.
Usage #
- Creating a Cube:
class CounterCube extends Cube {
final count = 0.obsValue;
// or final count = ObservableValue<int>(value: 0)
@override
void onReady(Object arguments) {
// do anything when view is ready
super.ready(arguments);
}
void increment() {
count.modify((value) => value + 1); // or count.update(newValue);
}
}
- Registering Cubes and or dependencies:
import 'package:cubes/cubes.dart';
import 'package:flutter/material.dart';
void main() {
// register cube
Cubes.registerDependency((i) => CounterCube());
// Example register singleton Cube
// Cubes.registerDependency((i) => CounterCube(),isSingleton: true);
// Example register repositories or anything
// Cubes.registerDependency((i) => SingletonRepository(i.getDependency(),isSingleton: true);
// Cubes.registerDependency((i) => FactoryRepository(i.getDependency());
runApp(MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: Home(),
));
}
- Creating view using
CubeBuilder
class CounterScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CubeBuilder<CounterCube>(
onAction: (cube, action) { // optional
print(action);
},
dispose:(CounterCube cube){ // optional
// do anything
return true; // If return false CubeBuilder note call dispose in Cube
},
arguments: 'Hi', //if not passed, get arguments from `ModalRoute.of(context).settings.arguments;`. this will be returning in onReady in your Cube
builder: (BuildContext context, CounterCube cube) {
return Scaffold(
appBar: AppBar(
title: Text('Counter'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('You have pushed the button this many times:'),
cube.count.build<int>(
(value) => Text(value.toString()),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: cube.increment,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
},
);
}
}
or use CubeWidget
class Home extends CubeWidget<CounterCube> {
@override
Widget buildView(BuildContext context, CounterCube cube) {
return Scaffold(
appBar: AppBar(
title: Text('Home'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
cube.count.build<int>((value) {
return Text(value.toString());
}),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: cube.increment,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
If you want to use cubes in a StatefulWidget
you can use the mixin CubeStateMixin<StatefulWidget,Cube>
in the state, see this example.
Cube and its dependencies are injected automatically.
By doing this:
cube.count.build<int>((value) {
return Text(value.toString());
}),
we register by listening to the Observable count
, and every time this variable is changed, the View
is notified by running the code block again:
return Text(value.toString());
This guarantees that in the whole widget tree of your screen, only the necessary is rebuilt.
Listening observable variables #
You can listen to observables in two ways, using the extension build
as in the example above or using the Observer
widget:
Extension 'build' #
cube.count.build<int>(
(value) => Text(value.toString()), // Here you build the widget and it will be rebuilt every time the variable is modified and will leave the conditions of `when`.
animate: true, // Setting to `true`, fadeIn animation will be performed between widget changes.
when: (last, next) => last != next, // You can decide when rebuild widget using previous and next value. (For a good functioning of this feature use immutable variables)
transitionBuilder: AnimatedSwitcher.defaultTransitionBuilder, // Here you can modify the default animation which is FadeIn.
duration: Duration(milliseconds: 300), // Sets the duration of the animation.
),
Widget CObserver #
return CObserver<int>(
observable: cube.count,
builder: (value)=> Text(value.toString()),
when: (last, next) => true,
animate:true,
transitionBuilder: AnimatedSwitcher.defaultTransitionBuilder,
duration: Duration(milliseconds: 300),
);
Provider #
To get the Cube by the children of CubeBuilder
, CubeWidget
you can use Cubes.of<MyCube>(context)
;
Methods inner Cube #
onAction #
onAction
is used to send any type of action or message to a view. You simply create an 'action' extending from CubeAction
.
class NavigationAction extends CubeAction {
final String route;
NavigationAction({this.route});
}
// sending action
onAction(NavigationAction(route: "/home"));
you will receive this action in the View
through the method:
@override
void onAction(BuildContext context, MyCube cube, CubeAction action) {
if(action is NavigationAction) Navigator.pushNamed(context, (action as NavigationAction).route);
super.onAction(context, cube, data);
}
this will be useful for navigation, to start some more complex animation, among other needs that View
has to perform.
runDebounce #
This method will help you to debounce
the execution of something.
runDebounce(
'increment', // identify
() => print(count.value),
duration: Duration(seconds: 1),
);
listen #
Use to listen ObservableValue.
listen(count,(value){
// do anything
});
listenActions #
Use to listen to Action
sent to view.
listenActions((action){
// do anything
});
Useful Widgets #
CAnimatedList #
This is a version of AnimatedList that simplifies its use for the Cube context.
CAnimatedList<String>(
observable: cube.todoList,
itemBuilder: (context, item, animation, type) {
return ScaleTransition(
scale: animation,
child: _buildItem(item),
);
},
)
Full usage example here.
CFeedBackManager #
Use this widget if you want to reactively control your Dialog
, BottomSheet
and SnackBar
using an ObservableValue.
Creating observable to control:
final bottomSheetControl = CFeedBackControl(data:'test').obsValue;
final dialogControl = CFeedBackControl(data:'test').obsValue;
final snackBarControl = CFeedBackControl<String>().obsValue;
Now just add the widget to your tree and settings:
FeedBackManager(
dialogControllers:[ // You can add as many different dialogs as you like
CDialogController<String>(
observable: cube.dialogControl,
// dismissible: bool,
// barrierColor: Color,
// routeSettings: RouteSettings,
// useRootNavigator: bool,
// useSafeArea: bool,
builder: (data, context) {
return Container(height: 200, child: Center(child: Text('Dialog: $data')));
},
),
],
bottomSheetControllers: [ // You can add as many different BottomSheets as you like
CBottomSheetController<String>(
observable: cube.bottomSheetControl,
// dismissible: bool,
// useRootNavigator: bool,
// routeSettings: RouteSettings,
// barrierColor: Color,
// backgroundColor: Color,
// elevation: double,
// shape: ShapeBorder,
// clipBehavior: Clip,
// enableDrag: bool,
// isScrollControlled: bool,
// useSafeArea: bool,
builder: (data, context) {
return Container(height: 200, child: Center(child: Text('BottomSheet: $data')));
},
),
],
snackBarControllers: [
CSnackBarController<String>(
observable: cube.snackBarControl,
builder: (data, context) {
return SnackBar(content: Text(data));
},
),
],
child: ...
)
To show or hide:
bottomSheetControl.show(); // or hide();
dialogControl.show(); // or hide();
snackBarControl.show(data: 'Success');
Full usage example here.
CTextFormField #
Widget created to use TextFormField
with ObservableValue
.
With it you can work reactively with your TextFormField
. Being able to modify and read its value, set error, enable and disable.
/// code in Cube
final textFieldControl = CTextFormFieldControl(text: '').obsValue;
// final text = textFieldControl.text; // get text
// textFieldControl.text = 'New text'; // change text
// textFieldControl.error = 'error example'; // set error
// textFieldControl.enable = true; // enable or disable
// textFieldControl.enableObscureText = true; // enable or disable obscureText
// code in Widget
CTextFormField(
observable: cube.textFieldControl,
obscureTextButtonConfiguration: CObscureTextButtonConfiguration( // use to configure the hide and show content icon in case of obscureText = true.
align: CObscureTextAlign.right,
iconHide: Icon(Icons.visibility_off_outlined),
iconShow: Icon(Icons.visibility_outlined),
),
decoration: InputDecoration(hintText: 'Type something'),
// ... All other TextFormField attributes
),
It is exactly the same as the conventional TextFormField
with two more fields, the observable
and obscureTextButtonConfiguration
.
Full usage example here.
Internationalization support #
With Cubes you can configure internationalization in your application. in a simple way using .json files.
Using #
Create a folder named lang
and put your files with name location. This way:
Add path in your pubspec.yaml
:
# To add assets to your application, add an assets section, like this:
assets:
- lang/
In your MaterialApp
you can configure the CubesLocalizationDelegate
:
final cubeLocation = CubesLocalizationDelegate(
[
Locale('en', 'US'),
Locale('pt', 'BR'),
],
);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My app',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
localizationsDelegates: cubeLocation.delegates, // see here
supportedLocales: cubeLocation.supportedLocations, // see here
home: Home(),
);
}
Ready!!! Your application already supports internationalization. Bas get the strings as follows:
String text = Cubes.getString('welcome');
Custom dependency injection #
By default, we use get_it to manage dependencies. if you want to use another one you can overwrite the Injector:
class MyInjector extends Injector {
@override
T getDependency<T>({String dependencyName}) {
// your implementation
}
@override
void registerDependency<T>(DependencyInjectorBuilder<T> builder, {String dependencyName, bool isSingleton = false}) {
// your implementation
}
@override
void reset() {
// your implementation
}
}
Cubes().injector = MyInjector();
Useful extensions #
// BuildContextExtensions
context.goTo(Widget());
context.goToReplacement(Widget());
context.goToAndRemoveUntil(Widget(),RoutePredicate);
context.mediaQuery; // MediaQuery.of(context);
context.padding; // MediaQuery.of(context).padding;
context.viewInsets; // MediaQuery.of(context).viewInsets;
context.sizeScreen; // MediaQuery.of(context).size;
context.widthScreen; // MediaQuery.of(context).size.width;
context.heightScreen; // MediaQuery.of(context).size.height;
context.theme;
context.scaffold;
context.showSnackBar(SnackBar());
context.arguments;
context.getCube<MyCube>(); // get Cube from provided
Testing #
import 'package:flutter_test/flutter_test.dart';
void main() {
CounterCube cube;
setUp(() {
cube = CounterCube();
});
tearDown(() {
cube?.dispose();
});
test('initial value', () {
expect(cube.count.value, 0);
});
test('increment value', () {
cube.increment();
expect(cube.count.value, 1);
});
test('increment value 3 times', () {
cube.increment();
cube.increment();
cube.increment();
expect(cube.count.value, 3);
});
}
Example with asynchronous call here Example widget test here
Any questions see our example.