cubes 1.0.1
cubes: ^1.0.1 copied to clipboard

Simple State Manager (Focusing on simplicity and rebuilding only the necessary)

pub package style: effective dart

Cubes

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, theView 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 withObservableValue. 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, theobservable 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.

14
likes
130
pub points
70%
popularity

Simple State Manager (Focusing on simplicity and rebuilding only the necessary)

Repository (GitHub)
View/report issues

Documentation

API reference

Uploader

rafaelbarbosatec@gmail.com

License

MIT (LICENSE)

Dependencies

flutter, flutter_localizations, get_it

More

Packages that depend on cubes