inherited_state 0.2.0

  • Readme
  • Changelog
  • Example
  • Installing
  • 64

inherited_state #

Simple scoped reactive state management (using [InheritedWidget]) and DI. Supports both immutable and mutable updates.

Quick Start #

pubspec.yaml

inherited_state: ^0.2.0

Setup State Management #

InheritedState widget needs to be part of the tree as an ancestor to be able to use it from the descendent widgets similar to the usage of InheritedWidget. You can register the reactive states using the states argument.

InheritedState(
    states: [
        Inject<Counter>(() => Counter(0)),
    ],
    builder: (_) =>
    ...
)

Reactive State - UI Change Notifications #

// Plain dart class
class Counter {
    Counter(this.count);
    int count = 0;
}

// RS is an alias for ReactiveState.
void _incrementCounter() {
  // Immutable update
  final res = RS.set<Counter>(context, (counter) => Counter(counter.count + 1));
}

// Pass context to the `RS.get` method to subscribe to changes (widget automatically rebuilds when changes occur).
@override
Widget build(BuildContext context) {
    final counter = RS.get<Counter>(context);
    ...
}

Setup DI #

void main() {
  registerDependencies();
  runApp(MyApp());
}

void registerDependencies() {
  SL.register(
    () => const AppConfig(
      appName: 'Inherited State Example',
      baseUrl: 'https://reqres.in/api',
    ),
  );
  SL.register(() => ApiService(SL.get()));
  SL.register(() => CounterService(SL.get()));
}

Service Locator (DI) - Services, Configs, etc. #

// SL is an alias for ServiceLocator.
final counterService = SL.get<CounterService>();

Full Example with Reactive states and Services #

main.yaml
import 'package:flutter/material.dart';
import 'package:inherited_state/inherited_state.dart';

import 'models/counter.dart';
import 'services/api_service.dart';
import 'services/app_config.dart';
import 'services/counter_service.dart';

void main() {
  registerDependencies();
  runApp(MyApp());
}

void registerDependencies() {
  SL.register(
    () => const AppConfig(
      appName: 'Inherited State Example',
      baseUrl: 'https://reqres.in/api',
    ),
  );
  SL.register(() => ApiService(SL.get()));
  SL.register(() => CounterService(SL.get()));
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return InheritedState(
        states: [
          Inject<Counter>(() => Counter(0)),
        ],
        builder: (_) {
          // final appConfig = InheritedService.get<AppConfig>();
          final appConfig = SL.get<AppConfig>();
          return MaterialApp(
            title: appConfig.appName,
            home: MyHomePage(title: appConfig.appName),
          );
        });
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final counterService = SL.get<CounterService>();
  Future<int> initialCounterFuture;

  @override
  void initState() {
    super.initState();
    initialCounterFuture = counterService.getInitialCounter();
    // Long form
    // initialCounterFuture.then((value) =>
    //     ReactiveService.getReactive<Counter>().setState((counter) => counter.count = value));
    // Short form - Mutatable update
    initialCounterFuture.then((value) =>
        RS.set<Counter>(context, (counter) => counter.count = value));
  }

  void _incrementCounter() {
    // Immutable update
    final res =
        RS.set<Counter>(context, (counter) => Counter(counter.count + 1));
    print('increment result: $res');
  }

  @override
  Widget build(BuildContext context) {
    final counter = RS.get<Counter>(context);
    print('rebuild: $counter');
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            const SizedBox(height: 20),
            _buildFutureWaiter(
                (isReady) => Text(
                      '${counter.count}',
                      style: Theme.of(context).textTheme.headline4,
                    ),
                true),
          ],
        ),
      ),
      floatingActionButton: _buildFutureWaiter(
        (isReady) {
          print('floats $counter');
          return FloatingActionButton(
            backgroundColor: isReady ? null : Colors.grey,
            disabledElevation: 0,
            onPressed: isReady ? _incrementCounter : null,
            tooltip: 'Increment',
            child: const Icon(Icons.add),
          );
        },
      ),
    );
  }

  Widget _buildFutureWaiter(Widget Function(bool isReady) builder,
          [bool showSpinner = false]) =>
      FutureBuilder<int>(
        future: initialCounterFuture,
        builder: (_, snapshot) => showSpinner && !snapshot.hasData
            ? const CircularProgressIndicator()
            : builder(snapshot.hasData),
      );
}


View file

Inherited State Widget #

This widget is used to setup the reactive state instances available to the descendants. It can be used multiple times at different tree nodes.

Reactive State #

Reactive state allows for subscriptions and updates to the underlying model in an immutable and mutable fashion depending on developer preference.

Services #

ServiceLocator allows for a simple way of registering global dependencies that can be used anywhere within the app without relying on flutter context or any sort of UI related mechanism. Services can be accessed and referenced and the reference is always a singleton.

0.2.0 - 2020-06-07 #

  • Added admin page to example which allows for resetting the counter.
  • Rename BuildContext set extension method to dispatch.

0.1.1 - 2020-06-06 #

  • Fix changelog.

0.1.0 - 2020-06-06 #

  • Rename reactives argument to states in the InheritedState widget.

0.0.1 - 2020-06-06 #

  • Initial release.

example/lib/main.dart

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:inherited_state/inherited_state.dart';
import 'package:inherited_state_example/admin_page.dart';

import 'models/counter.dart';
import 'services/api_service.dart';
import 'services/app_config.dart';
import 'services/counter_service.dart';

void main() {
  registerDependencies();
  runApp(MyApp());
}

void registerDependencies() {
  SL.register(
    () => const AppConfig(
      appName: 'Inherited State Example',
      baseUrl: 'https://reqres.in/api',
    ),
  );
  SL.register(() => ApiService(SL.get()));
  SL.register(() => CounterService(SL.get()));
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return InheritedState(
        states: [
          Inject<Counter>(() => Counter(0)),
        ],
        builder: (_) {
          // final appConfig = InheritedService.get<AppConfig>();
          final appConfig = SL.get<AppConfig>();
          return MaterialApp(
            title: appConfig.appName,
            home: MyHomePage(title: appConfig.appName),
          );
        });
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final counterService = SL.get<CounterService>();
  Future<int> initialCounterFuture;

  @override
  void initState() {
    super.initState();
    initialCounterFuture = counterService.getInitialCounter();
    // Long form
    // initialCounterFuture.then((value) =>
    //     ReactiveService.getReactive<Counter>().setState((counter) => counter.count = value));
    // Short form - Mutatable update
    initialCounterFuture.then((value) =>
        RS.set<Counter>(context, (counter) => counter.count = value));
  }

  void _incrementCounter() {
    // Immutable update (creates a new instance)
    // final counter = RS.set<Counter>(
    //   context,
    //   (obj) => Counter(obj.count + 1),
    // );

    // Mutable update (reuses same instance)
    final result = context.dispatch<Counter>(
      (obj) => obj.count += 1,
    );

    print('increment result: $result / #code: ${result.hashCode}');
  }

  @override
  Widget build(BuildContext context) {
    // shortcut api
    // final counter = RS.get<Counter>(context);

    // extensions api
    final counter = context.on<Counter>();

    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
        actions: [
          IconButton(
            icon: const Icon(Icons.settings),
            onPressed: () => Navigator.push<dynamic>(
              context,
              CupertinoPageRoute<dynamic>(
                builder: (_) => AdminPage(),
              ),
            ),
          )
        ],
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            const SizedBox(height: 20),
            _buildFutureWaiter(
                (isReady) => Text(
                      '${counter.count}',
                      style: Theme.of(context).textTheme.headline4,
                    ),
                true),
          ],
        ),
      ),
      floatingActionButton: _buildFutureWaiter(
        (isReady) {
          print('floats $counter');
          return FloatingActionButton(
            backgroundColor: isReady ? null : Colors.grey,
            disabledElevation: 0,
            onPressed: isReady ? _incrementCounter : null,
            tooltip: 'Increment',
            child: const Icon(Icons.add),
          );
        },
      ),
    );
  }

  Widget _buildFutureWaiter(Widget Function(bool isReady) builder,
          [bool showSpinner = false]) =>
      FutureBuilder<int>(
        future: initialCounterFuture,
        builder: (_, snapshot) => showSpinner && !snapshot.hasData
            ? const CircularProgressIndicator()
            : builder(snapshot.hasData),
      );
}

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  inherited_state: ^0.2.0

2. Install it

You can install packages from the command line:

with Flutter:


$ flutter pub get

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:inherited_state/inherited_state.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
29
Health:
Code health derived from static analysis. [more]
100
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
100
Overall:
Weighted score of the above. [more]
64
Learn more about scoring.

We analyzed this package on Jul 7, 2020, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.8.4
  • pana: 0.13.13
  • Flutter: 1.17.5

Analysis suggestions

Package not compatible with SDK dart

because of import path [inherited_state] that is in a package requiring null.

Health suggestions

Format lib/inherited_state.dart.

Run flutter format to format lib/inherited_state.dart.

Format lib/src/inject.dart.

Run flutter format to format lib/src/inject.dart.

Format lib/src/inject_notifier.dart.

Run flutter format to format lib/src/inject_notifier.dart.

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.7.0 <3.0.0
flutter 0.0.0
Transitive dependencies
collection 1.14.12 1.14.13
meta 1.1.8 1.2.1
sky_engine 0.0.99
typed_data 1.1.6 1.2.0
vector_math 2.0.8
Dev dependencies
flutter_test