Flutter Super State: Simple State Management for Flutter

cretezy

Charles Crete

Posted on May 7, 2020

Flutter Super State: Simple State Management for Flutter

flutter_super_state is a new state management library for Flutter which:

  • Is simple to use. Simply create modules
  • Supports async actions easily
  • Can easily be integrated with Flutter

Today we will take a look at creating a simple application with a mock authentication and a counter.

Concepts

Super State has 2 main objects:

  • Store: Which holds all of your modules
  • StoreModule: Which you can extend to implement modules

Let's take a look at a simple module:

import 'package:flutter_super_state/flutter_super_state.dart';

// Modules extend `StoreModule`
class CounterModule extends StoreModule {
  // Read only property, to avoid accidentally setting `counter` without calling `setState`
  int get counter => _counter;
  var _counter = 0;

  // This automatically registers your module to your store
  CounterModule(Store store): super(store);

  // Synchronous actions
  void increment() {
    setState(() {
      _counter++;
    });
  }

  void decrement() {
    setState(() {
      _counter--;
    });
  }

  // Asynchronous action
  Future<void> reset() async {
    // Fake async delay
    await Future.delayed(Duration(milliseconds: 10));

    setState(() {
      _counter = 0;
    });
  }
}

A store is easy to setup, here's an example:

import 'package:flutter_super_state/flutter_super_state.dart';

final store = Store();

// Register modules. Order does not matter. You should register all modules on initialization
CounterModule(store);

You can then call get modules from the store and call actions:

store.getModule<CounterModule>().increment();

await store.getModule<CounterModule>().reset();

For Flutter integration, the package provides for following widgets:

  • StoreProvider: Provides the store to all children in sub-tree
  • ModuleBuilder: Access a module inside the Flutter widget tree (is updated when module calls setState)
  • StoreBuilder: Access the store inside the Flutter widget tree (is updated when any module calls setState)

Example

Let's start by creating a new project using flutter create super_state_example and adding the package to pubspec.yaml:

dependencies:
  flutter_super_state: # Optionally put latest version from pub.dev

Next, let's clear your main.dart, and place our CounterModule from above inside lib/src/store/counter.dart. Let's create our second module, AuthModule (inside lib/src/store/auth.dart):

import 'package:flutter_super_state/flutter_super_state.dart';
import 'package:state_test/src/store/counter.dart';

class AuthModule extends StoreModule {
  int get isLoggedIn => isLoggedIn;
  var _isLoggedIn = false;

  AuthModule(Store store) : super(store);

  Future<void> login() async {
    // Do network request...
    await Future.delayed(Duration(milliseconds: 100));

    setState(() {
      _isLoggedIn = true;
    });
  }

  Future<void> logout() async {
    // Do network request...
    await Future.delayed(Duration(milliseconds: 100));

    setState(() {
      _isLoggedIn = true;
    });

    // Call action in other module
    await store.getModule<CounterModule>().reset();
  }
}

We should have our modules complete! Next, let's setup 2 screens (LoginScreen and CounterScreen):

class LoginScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Login"),
      ),
      body: Center(
        // Login button
        child: ModuleBuilder<AuthModule>(
          builder: (context, authModule) {
            return RaisedButton(
              child: Text("Login"),
              onPressed: () async {
                // Call action
                await authModule.login();
                Navigator.pushReplacement(
                  context,
                  MaterialPageRoute(builder: (context) => CounterScreen()),
                );
              },
            );
          },
        ),
      ),
    );
  }
}

class CounterScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Counter"),
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          // Our counter module
          ModuleBuilder<CounterModule>(
            builder: (context, counterModule) {
              return Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  RaisedButton(
                    child: Text("Increment"),
                    onPressed: () => counterModule.increment(),
                  ),
                  Padding(
                    padding: const EdgeInsets.symmetric(horizontal: 16),
                    child: Text(counterModule.counter.toString()),
                  ),
                  RaisedButton(
                    child: Text("Decrement"),
                    onPressed: () => counterModule.decrement(),
                  ),
                ],
              );
            },
          ),
          // Logout button
          ModuleBuilder<AuthModule>(
            builder: (context, authModule) {
              return RaisedButton(
                child: Text("Logout"),
                onPressed: () async {
                  await authModule.logout();
                  Navigator.pushReplacement(
                    context,
                    MaterialPageRoute(builder: (context) => LoginScreen()),
                  );
                },
              );
            },
          ),
        ],
      ),
    );
  }
}

Next, let's setup our main.dart:

void main() {
  // Create the store
  final store = Store();

  // Register modules
  CounterModule(store);
  AuthModule(store);

  // Provide store to whole application
  runApp(StoreProvider(
    store: store,
    child: ExampleApp(),
  ));
}

class ExampleApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: LoginScreen(),
    );
  }
}

And we are done! Let's check it out:

You can view the whole project here.

Thanks for reading, don't forget to check out the package!

💖 💪 🙅 🚩
cretezy
Charles Crete

Posted on May 7, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related