flutter_flux 4.1.1

  • README.md
  • CHANGELOG.md
  • Example
  • Installing
  • Versions
  • 90

flutter_flux

A Dart app architecture library with uni-directional data flow inspired by RefluxJS and Facebook's Flux.

This is an experimental package and does not have official support from the Flutter team. However, feedback is most welcome!


Overview

flux-diagram

flutter_flux implements a uni-directional data flow pattern comprised of Actions, Stores, and StoreWatchers. It is based on w_flux, but modified to use Flutter instead of React.

  • Actions initiate mutation of app data that resides in Stores.
  • Data mutations within Stores trigger re-rendering of app view (defined in StoreWatcher).
  • Flutter Widgets and other interaction sources dispatch Actions in response to user interaction.
  • and the cycle continues...

What's Included

Action

An Action is a command that can be dispatched (with an optional data payload) and listened to.

In flutter_flux, Actions are the sole driver of application state change. Widgets and other objects dispatch Actions in response to user interaction with the rendered view. Stores listen for these Action dispatches and mutate their internal data in response, taking the Action payload into account as appropriate.

import 'package:flutter_flux/flutter_flux.dart';

// define an action
final Action<String> displayString = new Action<String>();

// dispatch the action with a payload
displayString('somePayload');

// listen for action dispatches
displayString.listen(_displayAlert);

_displayAlert(String payload) {
  print(payload);
}

BONUS: Actions are await-able!

They return a Future that completes after all registered Action listeners complete. It's NOT generally recommended to use this feature within normal app code, but it is quite useful in unit test code.

Store

A Store is a repository and manager of app state. The base Store class provided by flutter_flux should be extended to fit the needs of your app and its data. App state may be spread across many independent stores depending on the complexity of the app and your desired app architecture.

By convention, a Store's internal data cannot be mutated directly. Instead, Store data is mutated internally in response to Action dispatches. Stores should otherwise be considered read-only, publicly exposing relevant data ONLY via getter methods. This limited data access ensures that the integrity of the uni-directional data flow is maintained.

A Store can be listened to to receive external notification of its data mutations. Whenever the data within a Store is mutated, the trigger method is used to notify any registered listeners that updated data is available. In flutter_flux, StoreWatchers listen to Stores, typically triggering re-rendering of UI elements based on the updated Store data.

import 'package:flutter_flux/flutter_flux.dart';

class RandomColorStore extends Store {

  // Public data is only available via getter method
  String _backgroundColor = 'gray';
  String get backgroundColor => _backgroundColor;

  // Actions relevant to the store are passed in during instantiation
  RandomColorActions _actions;

  RandomColorStore(RandomColorActions this._actions) {
    // listen for relevant action dispatches
    _actions.changeBackgroundColor.listen(_changeBackgroundColor);
  }

  _changeBackgroundColor(_) {
    // action dispatches trigger internal data mutations
    _backgroundColor = '#' + (new Random().nextDouble() * 16777215).floor().toRadixString(16);

    // trigger to notify external listeners that new data is available
    trigger();
  }
}

**BONUS:** `Stores` provide an optional terse syntax for action -> data mutation -> trigger operations.

// verbose syntax actions.incrementCounter.listen(_handleAction);

_handleAction(payload) { // perform data mutation counter += payload; trigger(); }

// equivalent terse syntax triggerOnAction(actions.incrementCounter, (payload) => counter += payload);


---

## Examples

Simple examples of `flutter_flux` usage can be found in the `example` directory. The example [README](example/README.md)
includes instructions for building / running them.


---

## External Consumption

`flutter_flux` implements a uni-directional data flow within an isolated application or code module. If `flutter_flux` is used as the
internal architecture of a library, this internal data flow should be considered when defining the external API.

- External API methods intended to mutate internal state should dispatch `Actions`, just like any internal user interaction.
- External API methods intended to query internal state should leverage the existing read-only `Store` getter methods.
- External API streams intended to notify the consumer about internal state changes should be dispatched from the
internal `Stores`, similar to their `triggers`.


4.1.1

  • Updated iOS and Android build resources for Flutter beta.

4.1.0

Fix build errors with Flutter beta.

Fixed in the library:

  • Error: A value of type '(dart.core::String) → dart.core::Null' can't be assigned to a variable of type '(dynamic) → dynamic'.
  • Version in pubspec.yaml needs to be bumped.
  • Error from flutter analyze: fix mixin_inherits_from_not_object. (Added analysis_options.)

Fixed in the example:

  • Text editor for the message was broken because insert cursor was at the wrong position.

Finally, removed ThrottledStore from README and test - it's not used.

4.0.1

Rewrote the sample code to use StoreWatcherMixin

The StoreWatcher class has proved to be confusing to use. Most developers want to add the Store functionality into an existing class, not add something else into the hierarchy. Rewrote the sample code to show how to use StoreWatcherMixin to add notifications to your own widget.

example/lib/main.dart

// Copyright 2016, the Flutter project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:flutter/material.dart';
import 'package:flutter_flux/flutter_flux.dart';
import 'stores.dart';

void main() {
  runApp(new MaterialApp(
      title: 'Chat',
      theme: new ThemeData(
          primarySwatch: Colors.purple, accentColor: Colors.orangeAccent[400]),
      home: new ChatScreen()));
}

class ChatScreen extends StatefulWidget {
  /// Creates a widget that watches stores.
  ChatScreen({Key key}) : super(key: key);

  @override
  ChatScreenState createState() => new ChatScreenState();
}

// To use StoreWatcherMixin in your widget's State class:
// 1. Add "with StoreWatcherMixin<yyy>" to the class declaration where yyy is
//    the type of your StatefulWidget.
// 2. Add the Store declarations to your class.
// 3. Add initState() function that calls listenToStore() for each store to
//    be monitored.
// 4. Use the information from your store(s) in your build() function.

class ChatScreenState extends State<ChatScreen>
    with StoreWatcherMixin<ChatScreen> {
  // Never write to these stores directly. Use Actions.
  ChatMessageStore messageStore;
  ChatUserStore chatUserStore;

  final TextEditingController msgController = new TextEditingController();

  /// Override this function to configure which stores to listen to.
  ///
  /// This function is called by [StoreWatcherState] during its
  /// [State.initState] lifecycle callback, which means it is called once per
  /// inflation of the widget. As a result, the set of stores you listen to
  /// should not depend on any constructor parameters for this object because
  /// if the parent rebuilds and supplies new constructor arguments, this
  /// function will not be called again.
  @override
  void initState() {
    super.initState();

    // Demonstrates using a custom change handler.
    messageStore =
        listenToStore(messageStoreToken, handleChatMessageStoreChanged);

    // Demonstrates using the default handler, which just calls setState().
    chatUserStore = listenToStore(userStoreToken);
  }

  void handleChatMessageStoreChanged(Store store) {
    ChatMessageStore messageStore = store;
    if (messageStore.currentMessage.isEmpty) {
        msgController.clear();
    }
    setState(() {});
  }

  Widget _buildTextComposer(BuildContext context, ChatMessageStore messageStore,
      ChatUserStore userStore) {
    final ValueChanged<String> commitMessage = (String _) {
      commitCurrentMessageAction(userStore.me);
    };

    ThemeData themeData = Theme.of(context);
    return new Row(children: <Widget>[
      new Flexible(
          child: new TextField(
              key: const Key("msgField"),
              controller: msgController,
              decoration: const InputDecoration(hintText: 'Enter message'),
              onSubmitted: commitMessage,
              onChanged: setCurrentMessageAction)),
      new Container(
          margin: new EdgeInsets.symmetric(horizontal: 4.0),
          child: new IconButton(
              icon: new Icon(Icons.send),
              onPressed:
                  messageStore.isComposing ? () => commitMessage(null) : null,
              color: messageStore.isComposing
                  ? themeData.accentColor
                  : themeData.disabledColor))
    ]);
  }

  Widget build(BuildContext context) {
    return new Scaffold(
        appBar:
            new AppBar(title: new Text('Chatting as ${chatUserStore.me.name}')),
        body: new Column(children: <Widget>[
          new Flexible(
              child: new ListView(
                  padding: new EdgeInsets.symmetric(horizontal: 8.0),
                  children: messageStore.messages
                      .map((ChatMessage m) => new ChatMessageListItem(m))
                      .toList())),
          _buildTextComposer(context, messageStore, chatUserStore),
        ]));
  }
}

class ChatMessageListItem extends StatefulWidget {
  ChatMessageListItem(ChatMessage m)
      : message = m,
        super(key: new ObjectKey(m));

  final ChatMessage message;

  @override
  State createState() => new ChatMessageListItemState();
}

class ChatMessageListItemState extends State<ChatMessageListItem>
    with SingleTickerProviderStateMixin {
  AnimationController _animationController;
  Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _animationController = new AnimationController(
        vsync: this, duration: new Duration(milliseconds: 700));
    _animation = new CurvedAnimation(
        parent: _animationController, curve: Curves.easeOut);
    _animationController.forward();
  }

  @override
  void dispose() {
    _animationController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final ChatMessage message = widget.message;
    return new SizeTransition(
        sizeFactor: _animation,
        axisAlignment: 0.0,
        child: new ListTile(
            dense: true,
            leading: new CircleAvatar(
                child: new Text(message.sender.name[0]),
                backgroundColor: message.sender.color),
            title: new Text(message.sender.name),
            subtitle: new Text(message.text)));
  }
}

1. Depend on it

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


dependencies:
  flutter_flux: "^4.1.1"

2. Install it

You can install packages from the command line:

with Flutter:


$ flutter packages get

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

3. Import it

Now in your Dart code, you can use:


import 'package:flutter_flux/flutter_flux.dart';
        
Version Uploaded Documentation Archive
4.1.1 Mar 24, 2018 Go to the documentation of flutter_flux 4.1.1 Download flutter_flux 4.1.1 archive
4.0.1 Apr 19, 2017 Go to the documentation of flutter_flux 4.0.1 Download flutter_flux 4.0.1 archive
4.0.0 Nov 7, 2016 Go to the documentation of flutter_flux 4.0.0 Download flutter_flux 4.0.0 archive

Analysis

We analyzed this package on Apr 23, 2018, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.0.0-dev.49.0
  • pana: 0.10.6
  • Flutter: 0.3.2

Scores

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

Platforms

Detected platforms: Flutter

References Flutter, and has no conflicting libraries.

Suggestions

  • Fix analysis and formatting issues.

    Analysis or formatting checks reported 3 hints.

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

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

    Similar analysis of the following files failed:

    • lib/src/store_watcher.dart (hint)
  • Enable strong mode analysis.

    Strong mode helps you to detect bugs and potential issues earlier.Start your analysis_options.yaml file with the following:

    analyzer:
      strong-mode: true
    

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=1.24.0 <2.0.0
flutter 0.0.0
meta ^1.0.3 1.1.2
Transitive dependencies
collection 1.14.6 1.14.9
sky_engine 0.0.99
typed_data 1.1.5
vector_math 2.0.6
Dev dependencies
flutter_test
quiver >=0.24.0 <1.0.0
test ^0.12.13