Git Product home page Git Product logo

flutter_flux's Introduction

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 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.

flutter_flux's People

Contributors

abarth avatar andrehaueisen avatar franklinharper avatar gmcdowell avatar hixie avatar jimbeveridge avatar rxlabz avatar takahirom avatar yyoon avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

flutter_flux's Issues

Flutter Web Support ?

Someone plan to bring compatibility to flutter web ? or who know what i need change to achieve it runing on web ?

Confused by StoreWatcher

Flutter stateless widgets have build method. That's where you build your widget. Stateful widgets on other hand have createState method, and are delegating build to State class.

What's confusing to me that StoreWatcher is a StatefulWidget, but it has both build and createState methods. As far as I understood it, you can use build when you don't need extra state, so you don't create extra class. But if you need it, you can override createState class and have StoreWatcherState.

So I debugged it, and when you override createState on StoreWatcher, the build method in StoreWatcher is ignored, and State class will build a widget. Which is OK, but now you cannot remove the StoreWatcher build method because it needs concrete implementation? Am I doing something wrong?

Guidance please

Hi,

I've been spiking out flutter flux and could use some guidance. I have a repo here. Note that my dependency on flutter_flux is a source dependency, due to the binaries on pub being old.

My use case is a login dialog. I already have it working in plain old flutter, but I want to separate the logic out, hence my spiking with flutter_flux. I've left a few TODO comments in the code, but in short I'm a bit stuck on how to do things The Right Way™.

And there's one debilitating bug in my code that I've not found a workaround for yet. I need to pop the modal when authentication succeeds, but I'm doing so from my build method, which of course triggers a setState() or markNeedsBuild() called during build assertion violation. I'm just not sure where else I could do this.

Can someone please offer some guidance?

Thanks

Is it okay to update widget's store in action callback?

I am referring to the example provided in this repo.
https://github.com/google/flutter_flux/blob/master/example/lib/main.dart#L62

The handleChatMessageStoreChanged function is called with the Store parameter and from the example it looks like, the downcasted store is assigned to a local scoped store and not the widget's store directly.

Can that function be written as

class ChatScreenState extends State<ChatScreen>
    with StoreWatcherMixin<ChatScreen> {
  // Never write to these stores directly. Use Actions.
  ChatMessageStore messageStore;
  // ...
  void handleChatMessageStoreChanged(Store store) {
    // ChatMessageStore messageStore = store; // instead of this local scoped store ...
    messageStore = store; // ... assign to widget's store
    if (messageStore.currentMessage.isEmpty) {
        msgController.clear();
    }
    setState(() {});
  }
  // ...
}

If this is not okay, I would like to know why.

How to change TextField text from Store?

In the example, when we commit a new message, the _currentMessage is emptied but the TextField does not reflect that changes.

This is the code in the store:

triggerOnAction(commitCurrentMessageAction, (ChatUser me) {
      final ChatMessage message =
          new ChatMessage(sender: me, text: _currentMessage);
      _messages.add(message);
      _currentMessage = '';
    });

The view uses a TextEditingController as a controller for the TextField Widget so I understand why it is not updated.

How can we empty the TextField from the Store with flutter_flux?

Call setState inside StoreWatcher

Using StoreWatcher, how can I call setState() inside my custom handler?

class MyWidget extends StoreWatcher {
  @override
  void initStores(ListenToStore listenToStore) {
    listenToStore(storeToken, customHandleStoreChanged);
  }
  void customHandleStoreChanged(Store store) {
    // ...
    // setState(() {});
  }
}

Outdated SDK version

I'm getting the following error:

Because cronicalia_flutter depends on flutter_flux >=4.0.1 which requires SDK version >=1.19.0 <2.0.0, version solving failed.

Could you update the pubspec.yaml?
Instead of

environment:
  sdk: ">=1.24.0 <2.0.0"

use

environment:
  sdk: ">=1.24.0 <3.0.0"

how to listen to similar stores with different arguments

There are some tab pages like fragment on android, I use constructor arguments to conotrol their stores. Like this:

List<GankPage> pages = [
  TabPage('aaa'),
  TabPage('bbb'),
]

class TabPage extends StoreWatcher {
  String type;
  StoreToken pageStoreToken;
  TabPage({Key k, this.type}) : super(key: k);

  @override
  void initStores(ListenToStore listenToStore) {
    pageStoreToken = StoreToken(PageStore(this.type));
    listenToStore(pageStoreToken);
    pageAction.call();
  }

}

If I write like this, when I switch tab the old actions will be called.
I saw the note is that stores should not depend on any constructor parameters.
Any suggetions?

StoreWatcherMixin can't be used as mixin because it extends a class other than Object

IDE: VSCode

Doesn't seem to preclude builds and debugging, however is triggering a heap of 'errors' in analyzer.

Thoughts?

`[✓] Flutter (Channel dev, v0.10.1, on Mac OS X 10.13.6 17G65, locale en-NZ)
• Flutter version 0.10.1 at /Users/greg/Library/Developer/flutter
• Framework revision 6a3ff018b1 (13 days ago), 2018-10-18 18:38:26 -0400
• Engine revision 3860a43379
• Dart version 2.1.0-dev.7.1.flutter-b99bcfd309

[✓] Android toolchain - develop for Android devices (Android SDK 28.0.3)
• Android SDK at /Users/greg/Library/Android/sdk
• Android NDK at /Users/greg/Library/Android/sdk/ndk-bundle
• Platform android-28, build-tools 28.0.3
• Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
• Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1136-b06)
• All Android licenses accepted.

[✓] iOS toolchain - develop for iOS devices (Xcode 10.1)
• Xcode at /Applications/Xcode.app/Contents/Developer
• Xcode 10.1, Build version 10B61
• ios-deploy 1.9.2
• CocoaPods version 1.5.3

[✓] Android Studio (version 3.2)
• Android Studio at /Applications/Android Studio.app/Contents • Flutter plugin version 29.1.1
• Dart plugin version 181.5656
• Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1136-b06)
[✓] VS Code (version 1.28.2)
• VS Code at /Applications/Visual Studio Code.app/Contents
• Flutter extension version 2.19.0

[!] Connected device
! No devices available! Doctor found issues in 1 category.greg@iMac:~/Projects/Tierra/logmate_flutter$`

screen shot 2018-11-01 at 17 41 34

Chat example should be updated to current Flutter version

Which means moving to new Gradle build as well as some changes in the Flutter API itself (ListTile instead of ListItem, ListView instead of Block, etc.).

I've already done so locally and got the app running with today's Flutter version. I can also submit a pull request, but should I put the updated example in a new folder or just overwrite example/chap_app?

Dispose override

If i create a widget that need to cancel stream subscriptions of firebase in the dispose method how can I do it with a storewatcher?

Pubspec.yaml is missing for chat_app

Could you upgrade the instructions as i want to play around with this example...

env:
x-MacBook-Pro:chat_app apple$ flutter doctor
[✓] Flutter (on Mac OS, channel master)
• Flutter at /usr/local/flutter/flutter
• Framework revision 16ce090fde (5 hours ago), 2016-11-01 20:34:36
• Engine revision 63e71803de
• Tools Dart version 1.21.0-dev.2.0

[✓] Android toolchain - develop for Android devices (Android SDK 24.0.3)
• Android SDK at /Users/apple/Library/Android/sdk
• Platform android-24, build-tools 24.0.3
• ANDROID_HOME = /Users/apple/Library/Android/sdk
• Java(TM) SE Runtime Environment (build 1.8.0_60-b27)

[✓] iOS toolchain - develop for iOS devices (Xcode 8.1)
• XCode at /Applications/Xcode.app/Contents/Developer
• Xcode 8.1, Build version 8B62

[x] Flutter IDE Support (No supported IDEs installed)
• IntelliJ - https://www.jetbrains.com/idea/

[✓] Connected devices
• None
x-MacBook-Pro:chat_app apple$

error:

x-MacBook-Pro:chat_app apple$ pwd
/Users/apple/workspace/go/src/github.com/flutter/flutter_flux/example/chat_app
x-MacBook-Pro:chat_app apple$ pub get
Could not find a file named "pubspec.yaml" in "/Users/apple/workspace/go/src/github.com/flutter/flutter_flux/example/chat_app".
x-MacBook-Pro:chat_app apple$ 

removed unused dart:async

removed unused dart:async as Future and Streams are already implemented in dart:core as per Dart 2.1

Method listening to an action gets called multiple times

If a method is listening to an action then for first time it gets hit once, then for next time if user gets back to the same page then the method gets hit twice and on third time its 3 times and increases every time.

Below is a code to simulate the same on click of Next a action is called and on action change the user is taken to second page, but on second time the action listening method gets hit twice.

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

    void main() => runApp(MyApp());

    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: MyHome(),
        );
      }
    }
    //========
    class MyHome extends StatefulWidget {
      @override
      _MyHomeState createState() => _MyHomeState();
    }

    class _MyHomeState extends State<MyHome> with StoreWatcherMixin<MyHome>  {
      AppStore store;
      @override
      void initState() {
        store = listenToStore(appStoreToken);
        changeStatus.listen(_openSetupDialogue);
        super.initState();
      }

      void _openSetupDialogue(dynamic payload){
        if(payload == true) {
          print("method got hit");
          Navigator.push(context, MaterialPageRoute(builder: (context) => SecondPage()));
        }
      }

      @override
      Widget build(BuildContext context) {
        return RaisedButton(
          child: Text("Next"),
          onPressed: (){
            changeStatus(true);
          },
        );
      }
    }
    //===========
    class SecondPage extends StatefulWidget {
      @override
      _SecondPageState createState() => _SecondPageState();
    }

    class _SecondPageState extends State<SecondPage> {
      @override
      Widget build(BuildContext context) {
        return RaisedButton(
          child: Text("back"),
          onPressed: (){Navigator.push(context, MaterialPageRoute(builder: (context) =>MyHome()),
            );
          },
        );
      }
    }
    //===========
    class AppStore extends Store {
      bool state = true;
      AppStore(){
        triggerOnAction(changeStatus, (status){
          state = status;
        });
      }
      bool get getAppStore => state;
    }
    final Action changeStatus = Action<bool>();
    final appStoreToken = StoreToken(AppStore());

Wrong flutter_flux code when pulled down from Dart package

Hey all, trying out flutter_flux in a project, but can't seem to get the correct flutter_flux source pulled down. I have defined flutter_flux: 4.0.1 in my pubspec.yaml, but when I look at the Dart Packages in Android Studio the code is not correct when compared to the GitHub repo for flutter_flux 4.0.1. For example, here is a the StoreWatcherMixin signature from my local machine: abstract class StoreWatcherMixin implements State<dynamic> and here is the same on GitHub: abstract class StoreWatcherMixin<T extends StatefulWidget> implements State<T>. I have tried re-installing everything, clearing the cache, cleaning the project... I am out of idea. Any thoughts?

Will this be maintained?

I'd really like to use this library for flutter. Will it be maintained over time or is it just experimental?
Thanks for your hard work.

How to make network calls?

The title of this issue could be a little misleading but I was not sure how else to describe my thought process, so please stay with me and read along.

I am writing a Flutter application that uses flutter_flux. I have a few stores and a few views (widgets) so far. Right now I am in a dilemma on how to do the network API calls. My widget loads but does not have any data to display yet, which is fine, but now it needs to call my network API service that responds with data. Here is what I am thinking design-wise. Please share your opinions on it.

Widget -> Action -> Store -> Network Service Call -> trigger()

In this design, the widget kicks off an action something like fetchMessages and the store listening to this action MessageStore receives a call to this action. In the action callback, this store kicks the network call that returns Future. In the then() callback of that Future, this store mutates it's own data and calls trigger() so that the original view that started this now receives a callback. Similarly, also in catchError of the same Future, this store calls trigger() with some other mutation that maintains error states.

In this design, I am not exactly sure where do I call action from the widget? In build or in initState?
My thinking is that in initState the widget will grab data from store but it will be null or empty initially, and build will render empty view but at the same time, kickstarts the cycle described above, which calls back to the widget later and the build function now has data to render, re-grabbed from store.

Do you think it is a good pattern? It still abides to Flux's unidirectional data flow and does not let store data to be manipulated in any way other than an action callback.

triggerOnConditionalAction parameter type error

in store.dart,

class Store {
    // ...
void triggerOnConditionalAction<T>(Action<T> action, bool onAction(T payload)) {
    assert(action != null);
    action.listen((dynamic payload) async {
      // Action functions must return bool, or a Future<bool>.
      dynamic result = onAction(payload);
      bool wasChanged;
      if (result is Future) {
        wasChanged = await result;
      } else {
        wasChanged = result;
      }
      if (wasChanged) {
        trigger();
      }
    });
  }
    // ...
}

the 2nd param onAction defined bool return type. it should be dynamic.

StoreWatcherMixin Widget not rebuilding

Hi,

I've been wrestling with this for a while now and can't see what is going wrong, but my StoreWatcherMixin widget does not get build() called after setting state in the flux store.

Main.dart:

void main() => runApp(new MyApp());

class MyApp extends StatefulWidget {
  @override
  State createState() => new AppState();
}

class AppState extends State<MyApp>
  with StoreWatcherMixin<MyApp> {

  var appStore;

  @override
  void initState() {
    super.initState();
    appStore = listenToStore(_appStoreToken);
  }

  @override
  Widget build(BuildContext context) {
      ...
      return new SecondPage(_appStoreToken);
   }

The store:

final Action<List<Foo>> setFoos = new Action<List<Foo>>();
final AppStore _store = new AppStore();
final StoreToken _appStoreToken = new StoreToken(_store);

class AppStore extends Store {
  List<Foo> _allFoos = new List<Foo>();
  List<ArkhamDBCardPack> get allFoos => _allFoos;

  AppStore() {
    triggerOnAction(setFoos, (value) => _allFoos = value);
  }
}

Second page:

class SecondPage extends StatefulWidget {

  final StoreToken _storeToken;
  SecondPage(this._storeToken);

  @override
  State createState() => new SecondPageState(_storeToken);
}

class SecondPageState extends State<SecondPage>
  with StoreWatcherMixin<SecondPage> {

  var appStore;
  final StoreToken _storeToken;
  List<Foo> foos;

  SecondPageState(this._storeToken);

  @override
  void initState() {
    super.initState();
    debugPrint('x');
    appStore = listenToStore(_storeToken);
    foos = appStore.allFoos;
  }

  @override
  Widget build(BuildContext context) {
    debugPrint('rebuilding');
    return new IconButton(
              icon: new Icon(Icons.refresh),
              onPressed: () async {
                var data = await fetchFoos();
                setFoos.call(data);
              },
            )
   }
}

This isn't the exact code, but its close enough. The first time it runs, I press the button on the second page to fetch the data and call setFoos. debugPrints show that it has set the value correctly into the store, but the build() and debugPrint('rebuilding') are never called a second time.

What am I doing wrong? I really want to get this working as flutter and flux seem like a great tech. Thanks in advance!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.