Giter Site home page Giter Site logo

brianegan / flutter_redux Goto Github PK

View Code? Open in Web Editor NEW
1.6K 30.0 219.0 7.79 MB

A library that connects Widgets to a Redux Store

License: MIT License

Objective-C 0.07% Dart 71.02% HTML 5.86% Kotlin 0.24% Swift 0.76% CMake 13.26% C++ 7.52% C 1.27%
flutter redux dart dartlang

flutter_redux's Introduction

flutter_redux

Build Status codecov

A set of utilities that allow you to easily consume a Redux Store to build Flutter Widgets.

This package supports null-safety and is built to work with Redux.dart 5.0.0+ and Flutter 3+.

Redux Widgets

  • StoreProvider - The base Widget. It will pass the given Redux Store to all descendants that request it.
  • StoreBuilder - A descendant Widget that gets the Store from a StoreProvider and passes it to a Widget builder function.
  • StoreConnector - A descendant Widget that gets the Store from the nearest StoreProvider ancestor, converts the Store into a ViewModel with the given converter function, and passes the ViewModel to a builder function. Any time the Store emits a change event, the Widget will automatically be rebuilt. No need to manage subscriptions!

Examples

  • Simple example - a port of the standard "Counter Button" example from Flutter
  • Github Search - an example of how to search as a user types, demonstrating both the Middleware and Epic approaches.
  • Todo app - a more complete example, with persistence, routing, and nested state.
  • Timy Messenger - large open source app that uses flutter_redux together with Firebase Firestore.

Companion Libraries

Usage

Let's demo the basic usage with the all-time favorite: A counter example!

Note: This example requires flutter_redux 0.4.0+ and Dart 2! If you're using Dart 1, see the old example.

import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';

// One simple action: Increment
enum Actions { Increment }

// The reducer, which takes the previous count and increments it in response
// to an Increment action.
int counterReducer(int state, dynamic action) {
  return action == Actions.Increment ? state + 1 : state;
}

void main() {
  // Create your store as a final variable in the main function or inside a
  // State object. This works better with Hot Reload than creating it directly
  // in the `build` function.
  final store = Store<int>(counterReducer, initialState: 0);

  runApp(FlutterReduxApp(
    title: 'Flutter Redux Demo',
    store: store,
  ));
}

class FlutterReduxApp extends StatelessWidget {
  final Store<int> store;
  final String title;

  FlutterReduxApp({Key key, this.store, this.title}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // The StoreProvider should wrap your MaterialApp or WidgetsApp. This will
    // ensure all routes have access to the store.
    return StoreProvider<int>(
      // Pass the store to the StoreProvider. Any ancestor `StoreConnector`
      // Widgets will find and use this value as the `Store`.
      store: store,
      child: MaterialApp(
        theme: ThemeData.dark(),
        title: title,
        home: Scaffold(
          appBar: AppBar(title: Text(title)),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                // Connect the Store to a Text Widget that renders the current
                // count.
                //
                // We'll wrap the Text Widget in a `StoreConnector` Widget. The
                // `StoreConnector` will find the `Store` from the nearest
                // `StoreProvider` ancestor, convert it into a String of the
                // latest count, and pass that String  to the `builder` function
                // as the `count`.
                //
                // Every time the button is tapped, an action is dispatched and
                // run through the reducer. After the reducer updates the state,
                // the Widget will be automatically rebuilt with the latest
                // count. No need to manually manage subscriptions or Streams!
                StoreConnector<int, String>(
                  converter: (store) => store.state.toString(),
                  builder: (context, count) {
                    return Text(
                      'The button has been pushed this many times: $count',
                      style: Theme.of(context).textTheme.display1,
                    );
                  },
                )
              ],
            ),
          ),
          // Connect the Store to a FloatingActionButton. In this case, we'll
          // use the Store to build a callback that will dispatch an Increment
          // Action.
          //
          // Then, we'll pass this callback to the button's `onPressed` handler.
          floatingActionButton: StoreConnector<int, VoidCallback>(
            converter: (store) {
              // Return a `VoidCallback`, which is a fancy name for a function
              // with no parameters and no return value. 
              // It only dispatches an Increment action.
              return () => store.dispatch(Actions.Increment);
            },
            builder: (context, callback) {
              return FloatingActionButton(
                // Attach the `callback` to the `onPressed` attribute
                onPressed: callback,
                tooltip: 'Increment',
                child: Icon(Icons.add),
              );
            },
          ),
        ),
      ),
    );
  }
}

Purpose

One question that reasonable people might ask: Why do you need all of this if StatefulWidget exists?

My advice is the same as the original Redux.JS author: If you've got a simple app, use the most straightforward thing possible. In Flutter, StatefulWidget is perfect for a simple counter app.

However, say you have the more complex app, such as an E-commerce app with a Shopping Cart. The Shopping Cart should appear on multiple screens in your app and should be updated by many different types of Widgets on those different screens (An "Add Item to Cart" Widget on all your Product Screens, "Remove Item from Cart" Widget on the Shopping Cart Screen, "Change quantity" Widgets, etc.).

Additionally, you want to test this logic, as it's the core business logic to your app!

Now, in this case, you could create a Testable ShoppingCart class as a Singleton or Create a Root StatefulWidget that passes the ShoppingCart Down Down Down through your widget hierarchy to the "add to cart" or "remove from cart" Widgets.

Singletons can be problematic for testing, and Flutter doesn't have a great Dependency Injection library (such as Dagger2) just yet, so I'd prefer to avoid those.

Yet passing the ShoppingCart all over the place can get messy. It also means

it's way harder to move that "Add to Item" button to a new location b/c you'd need up update the Widgets throughout your app that passes the state down.

Furthermore, you'd need a way to Observe when the ShoppingCart Changes so you could rebuild your Widgets when it does (from an "Add" button to an "Added" button, as an example).

One way to handle it would be to simply setState every time the ShoppingCart changes in your Root Widget, but then your whole app below the RootWidget would be required to rebuild as well! Flutter is fast, but we should be thoughtful about what we ask Flutter to rebuild!

Therefore, redux & redux_flutter was born for more complex stories like this one. It gives you a set of tools that allow your Widgets to dispatch actions in a naive way, then writes the business logic in another place that will take those actions and update the ShoppingCart in a safe, testable way.

Even more, once the ShoppingCart has been updated in the Store, the Store will emit an onChange event. This lets you listen to Store updates and rebuild your UI in the right places when it changes! Now, you can separate your business logic from your UI logic in a testable, observable way, without having to Wire up a bunch of stuff yourself!

Similar patterns in Android are the MVP Pattern or using Rx Observables to manage a View's State.

flutter_redux handles passing your Store down to all of your descendant StoreConnector Widgets. If your State emits a change event, only the StoreConnector Widgets and their descendants will be automatically rebuilt with the latest State of the Store!

This allows you to focus on what your app should look like and how it should work without thinking about all the glue code to hook everything together!

Contributors

flutter_redux's People

Contributors

baladhruv avatar bcko avatar brianegan avatar btastic avatar chrisabird-deliberate avatar christianedwardpadilla avatar dereklakin avatar esarbanis avatar j-j-gajjar avatar lijy91 avatar marcglasberg avatar martinloesethjensen avatar miquelbeltran avatar pin73 avatar psygo avatar taym95 avatar xqwzts 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

flutter_redux's Issues

Error with Store is defined outside main()

This is probably related to #28 ...

With Dart 2, this works great:

enum Actions { Increment }

// The reducer, which takes the previous count and increments it in response
// to an Increment action.
int counterReducer(int state, dynamic action) {
if (action == Actions.Increment) {
return state + 0;
}

return state;
}

void main() {
final store = new Store(counterReducer, initialState: 1);
runApp(new MyApp(store: store));
}

class MyApp extends StatelessWidget {
MyApp({this.store});
final Store store;

@OverRide
Widget build(BuildContext context) {
return StoreProvider(
store: store,
child: new MaterialApp(
title: 'Flutter',
home: StoreProvider(
store: store,
child: new Center(
child: new StoreConnector(
converter: (store) => store.state.toString(),
builder: (context, count) {
return new Text('$count');
}))),
),
);
}
}

But the following throws this error:

The following NoSuchMethodError was thrown building StoreConnector(dirty):
The getter '_store' was called on null.
Receiver: null
Tried calling: _store


enum Actions { Increment }

// The reducer, which takes the previous count and increments it in response
// to an Increment action.
int counterReducer(int state, dynamic action) {
if (action == Actions.Increment) {
return state + 0;
}

return state;
}

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

class MyApp extends StatelessWidget {
final store = new Store(counterReducer, initialState: 1);

@OverRide
Widget build(BuildContext context) {
return StoreProvider(
store: store,
child: new MaterialApp(
title: 'Flutter',
home: StoreProvider(
store: store,
child: new Center(
child: new StoreConnector(
converter: (store) => store.state.toString(),
builder: (context, count) {
return new Text('$count');
}))),
),
);
}
}

Store access in init

Sorry, me again :)

Another question: what would be the correct way to reset some state prior to entering a given view? Imagine a form in the app that should reset whenever the user starts it. But it's a multi-page form, so they can also return to the start via the back button (which should not reset the data). Therefore, I need to send a ResetAction during the init stage of the page.

However, achieving this seems problematic. The only way I know of obtaining the current store is:

new StoreProvider<AppState>.of(context).store

But this doesn't work during init:

The following assertion was thrown building _ModalScopeStatus(active, can pop):
I/flutter (13145): inheritFromWidgetOfExactType(StoreProvider) was called before
I/flutter (13145): _XXX.initState() completed.
I/flutter (13145): When an inherited widget changes, for example if the value of Theme.of() changes, its dependent
I/flutter (13145): widgets are rebuilt. If the dependent widget's reference to the inherited widget is in a constructor
I/flutter (13145): or an initState() method, then the rebuilt dependent widget will not reflect the changes in the
I/flutter (13145): inherited widget.
I/flutter (13145): Typically references to to inherited widgets should occur in widget build() methods. Alternatively,
I/flutter (13145): initialization based on inherited widgets can be placed in the didChangeDependencies method, which
I/flutter (13145): is called after initState and whenever the dependencies change thereafter.

I don't think didChangeDependencies is what I want, because then the data could reset in other circumstances.

I also considered having the reset just before the code that routes the user to the page. That totally works, and is what I've been running with for a while (and will have to continue doing so for now). However, I was trying to move away from this because it means one must remember to dispatch the action before routing. When you have multiple parts of the application routing to that same page, each of those parts must remember (or the routing must be abstracted/extracted in some fashion).

Any thoughts on this?

google_sign_in and flutter_redux

Hi,

First of all, thank you guys for writing such a great library; I am still learning Redux but this package has been a wonderful first introduction.

I am trying to use the google_sign_in package in order to sign users into my app, and it worked fine before I moved to Redux. However, it now throws an error any time I try to sign in to the app. The sign in box that normally pops up now does not, so I think that it might have to do with trying to render the sign in box from Middleware, which doesn't really have a context (?)

Here is the associated code:

SignIn

import 'dart:async';

import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:kickit/data/injectors.dart';
import 'package:kickit/data/profile_store.dart';
import 'package:kickit/database/networking.dart';
import 'package:kickit/database/profile_package.dart';

/// A collection of methods that handle the signing in and signing out of the
/// current user.
abstract class ISignIn {
  /// Signs in the current user, returning a [ProfilePackage] containing the
  /// current user's data.
  Future<ProfilePackage> signIn();

  /// Signs the current user out of Firebase and Google.
  Future<Null> signOut();

  /// Signs the current user out of Firebase and Google and deletes their profile
  /// from the database.
  Future<Null> signOutAndDelete(String uid);
}

/// Performs sign in operations with real network data.
class SignIn extends ISignIn {
  final IProfileStore store;

  SignIn() : store = new ProfileInjector().profileLoader;

  Future<ProfilePackage> signIn() async {
    // Check to see if there is currently a signed in user.
    final GoogleSignInAccount account = googleSignIn.currentUser;

    // Try to sign in without prompting the user.
    if (account == null) {
      account == await googleSignIn.signInSilently();
    }

    // If this doesn't work, prompt the user to sign in.
    if (account == null) {
      account == await googleSignIn.signIn();
    }

    // If this doesn't work, throw an error that should tell the user that
    // they must sign in.
    if (account == null) {
      throw new StateError("The user must log in.");
    }

    final GoogleSignInAuthentication auth = await account.authentication;

    // Authenticate the user with firebase.
    final FirebaseUser user = await firebaseAuth.signInWithGoogle(
        idToken: auth.idToken, accessToken: auth.accessToken);

    if (user == null || user.isAnonymous) {
      throw new StateError("Log in error.");
    }

    final ProfilePackage package = await store.loadProfile(user.uid);

    // No user was found, so create a new one and save it to the database.
    if (package == null) {
      ProfilePackage newPackage =
          new ProfilePackage.fromGoogleSignIn(account, user);
      await store.saveProfile(newPackage);
      return newPackage;
    } else {
      return package;
    }
  }

  Future<Null> signOut() async {
    await googleSignIn.signOut();
    await firebaseAuth.signOut();
  }

  Future<Null> signOutAndDelete(String uid) async {
    await store.deleteProfile(uid);
    await signOut();
  }
}

/// Performs sign in operations with mock data.
class MockSignIn implements ISignIn {
  @override
  Future<ProfilePackage> signIn() {
    return new Future.delayed(delayMedium,
        () => new ProfilePackage("_", "Jaewon Yang", "_", "Test profile."));
  }

  @override
  Future<Null> signOut() {
    return null;
  }

  @override
  Future<Null> signOutAndDelete(String uid) {
    return null;
  }
}

Middleware

import 'package:kickit/actions/sign_in_actions.dart';
import 'package:kickit/data/injectors.dart';
import 'package:kickit/data/sign_in.dart';
import 'package:kickit/models/app_state.dart';
import 'package:kickit/models/profile.dart';
import 'package:kickit/utils/values/internal_strings.dart';
import 'package:kickit/utils/values/keys.dart';
import 'package:redux/redux.dart';

/// Creates all the [Middleware] associated with signing the user in and out.
List<Middleware<AppState>> createSignInMiddleware() {
  final ISignIn signIn = new SignInInjector().signIn;

  final Middleware<AppState> mSignIn = _createSignIn(signIn);
  final Middleware<AppState> mSignOut = _createSignOut(signIn);
  final Middleware<AppState> mSignOutAndDelete =
      _createSignOutAndDelete(signIn);

  return combineTypedMiddleware(
    [
      new MiddlewareBinding<AppState, SignInAction>(mSignIn),
      new MiddlewareBinding<AppState, SignOutAction>(mSignOut),
      new MiddlewareBinding<AppState, SignOutAndDeleteAction>(
          mSignOutAndDelete),
    ],
  );
}

/// Creates the [Middleware] associated with a sign in attempt.
Middleware<AppState> _createSignIn(ISignIn signIn) {
  return (Store<AppState> store, action, NextDispatcher next) {
    signIn.signIn().then(
      (user) {
        store.dispatch(
          new SignedInAction(
            new Profile.fromPackage(user),
          ),
        );
        Keys.navigatorKey.currentState
            .pushReplacementNamed(InternalStrings.mainScreenRoute);
      },
    ).catchError((_) => store.dispatch(new FailedSignInAction()));

    next(action);
  };
}

/// Creates the [Middleware] associated with a sign out attempt.
Middleware<AppState> _createSignOut(ISignIn signIn) {
  return (Store<AppState> store, action, NextDispatcher next) {
    signIn.signOut().then(
      (_) {
        store.dispatch(new SignedOutAction());
        Keys.navigatorKey.currentState
            .pushReplacementNamed(InternalStrings.splashScreenRoute);
      },
    );

    next(action);
  };
}

/// Creates the [Middleware] associated with a sign out and delete attempt.
Middleware<AppState> _createSignOutAndDelete(ISignIn signIn) {
  return (Store<AppState> store, action, NextDispatcher next) {
    signIn.signOutAndDelete(store.state.profile.uid).then(
      (_) {
        store.dispatch(new SignedOutAction());
        Keys.navigatorKey.currentState
            .pushReplacementNamed(InternalStrings.splashScreenRoute);
      },
    );

    next(action);
  };
}

Any help would be appreciated!

App Crashes with "No StoreProvider<Store<State>> found." on launch.

I can't seem to get this working. My app crashes right at boot since attempting to upgrade to the latest flutter redux. Below is the currently set up that I am working with.

dependencies:
  # Redux
    redux: "^3.0.0"
    flutter_redux: "^0.5.0"
Flutter 0.3.2 • channel beta • https://github.com/flutter/flutter.git
Framework • revision 44b7e7d3f4 (3 weeks ago) • 2018-04-20 01:02:44 -0700
Engine • revision 09d05a3891
Tools • Dart 2.0.0-dev.48.0.flutter-fe606f890b

The Code Which is Crashing:

final store = new Store<PrimeAppStateModel>(
    appReducer,
    initialState: new PrimeAppStateModel().initState(), /* initState Returns instance of PrimeAppStateModel */
    middleware: [loggingMiddleware]
  );

  runApp(
    new StoreProvider<PrimeAppStateModel>(
      store: store,
      child: new MaterialApp(
        debugShowCheckedModeBanner: _isDebugBannerOn,
        localizationsDelegates: [
          const HLocalizationsDelegate(),
          GlobalMaterialLocalizations.delegate,
          GlobalWidgetsLocalizations.delegate,
        ],
        supportedLocales: AppSettings.supportedLocales,
        onGenerateTitle: (BuildContext context) => HLocalizations.of(context).appName,
        color: HColors.canvas,
        theme: HTheme.baseTheme(),
        home: new AppLaunch()
      )
    )
  );

class AppLaunch extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
   return new Scaffold(
      backgroundColor: HColors.purple,
      body: new StoreConnector<PrimeAppStateModel, Store<PrimeAppStateModel>>(
        converter: (store) => store,
        onInit: (store){ 
            AppEntryModel modalModel = new AppEntryModel.set(
                HLocalizations.of(context),
                modalMode: ModalMode.login
          );
          store.dispatch(new AppEntryInitAction(modalModel));
        }
        builder: (BuildContext build, store) => new Container()

The Error On Build Completion:

I/flutter (20772): :: ACTION LOG :: 2018-05-09 00:35:43.225096: Instance of 'AppEntryInitAction'
I/flutter (20772): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter (20772): The following StoreProviderError was thrown building StoreConnector<Store<PrimeAppStateModel>, () =>
I/flutter (20772): void>(dirty):
I/flutter (20772): Error: No StoreProvider<Store<PrimeAppStateModel>> found. To fix, please try:
I/flutter (20772):           
I/flutter (20772):   * Using Dart 2 (required) by using the --preview-dart-2 flag
I/flutter (20772):   * Wrapping your MaterialApp with the StoreProvider<State>, rather than an individual Route
I/flutter (20772):   * Providing full type information to your Store<State>, StoreProvider<State> and
I/flutter (20772):     StoreConnector<State, ViewModel>

I have checked to make sure that the app is running in Dart 2.0, and it is enabled in Android Studio -> Preferences -> Languages & Frameworks -> Flutter

The launch screen is displayed and then the AppLaunch's StoreConnector runs, since I get a log notice from my logging middleware that store.dispatch(new AppEntryInitAction(modalModel)); action has started. At that point the App Crashes.

I've tried some time trying to isolate where this is happening, and can't seem to pin-point it.

Call showSnackBar from middleware

Hi, I dunno if its possible to call showSnackBar from a middleware, but I want a Snackbar to show on the active page when I catch an error. Is there a better more redux-y way of doing this?

Can't Find Store Producer and Reducer

I read issue #46 and that does seem related to my issue however how there is a bit more in my case.

I took a stateless widget and wrapped the Store Provider in it and attached the Reducer to this widget as a static function. When I tried to wrap the root widget in this stateless widget it gave me the "can't find store provider" error like in #46. If I transplant the Store Provider from the stateless widget and directly put it into my root widget, I am unable to gain access to my reducer (I took the reducer out of the wrapper stateless widget and just made it a normal top level function). I happened to leave the wrapper widget code mostly alone (aside from removing the reducer from it) but its not attached to anything and it finds the reducer when it is passed into the Store Provider there.

On the other hand when I pass the same reducer into the Store Provider in the root widget it gives me this very bizarre error: the argument type '(AppState, dynamic) → AppState can't be assigned to the parameter type '(AppState, dynamic) → AppState

Any Ideas of what exactly is going on here? This is a really confusing error and even taking the reducer and its logic and putting it directly into my main file does not solve this problem.

Using flutter redux and Refresh Indicator

Hi,

Great library, I've really enjoyed implementing it but I've had a bit of a mammoth session and can't seem to wrap my head around this one. I am pretty new to Flutter and Dart so apologies if this is a simple question.

I want to use a pull to refresh which sends an action and retrieves a new set of items from the web via the middleware. Doing this without the refresh indicator works well, but the indicator needs a Future passed to it that resolves when the action has finished. Is there any way that I can use the existing stock widget to start and stop based on actions?

Thanks

Is middleware always appropriate?

Hi. I'm just trying out redux for the first time. Sorry if this isn't the right place for design questions. I considered using Stackoverflow, but I've had these kinds of questions closed there in the past.

Basically, what I'm pondering is how much functionality will reside in middleware and whether it's really the right place for it. To make this discussion concrete, I've got two examples:

  1. Validating input
  2. Formatting input

Validating Input

Consider the scenario where the user enters some text and that text needs to be validated+converted to an integer.

To implement this, I'm imagining the following:

  1. The store includes rawInput (a String), isRawInputValid (a bool), convertedValue (an int) and possibly even a validationError (a String). All this could be wrapped up in a Validated<T> class, which I could use for any validated field. Thus, the store itself would just have a single instance of Validated<int>.
  2. A SendRawInput action would be defined, along with SendValidatedInput.
  3. The view would send a SendRawInput action.
  4. Some "validation" middleware would intercept SendRawInput and perform the validation, then instead send through a SendValidatedInput action.

Formatting Input

Now imagine the user chooses a DateTime, which gets sent through to the store. Depending on the date, the view wants to display either "Yesterday", "Today", "Tomorrow" or some formatted date like "ddd MMM, yyyy".

Where does this formatting logic reside?

Similarly to the above, one could make the case for "formatting" middleware that intercepts a RawDateAction, formats the date within, then forwards a Formatted<Date> instance (which just contains the original value and a formatted value) onto the store via a separate action.

But is this the way it's generally done with redux? It seems like a lot of moving parts for something so simple. Moreover, I can imagine a store where there are a half dozen or more such fields, making things a little unwieldy.

One alternative to this I considered was some kind of view model - something that wraps a store instance and provides formatting over the top of it. However, that is also another moving part, and it's not clear to me whether it's really a good idea.

Any thoughts on this?

StoreConnector for PreferredSizeWidget

Do you have any suggestions for how to to implement a StoreConnector for a PreferredSizeWidget?

Specifically the example I have is AppBar where bottom is a TabBar. The tabs may need to be updated if the app state changes.

There is one more motive of failure when it cannot find the StoreConnector.

I got this:

I/flutter (15445): The following StoreProviderError was thrown building StoreConnector<AppState, _ViewModel>(dirty):
I/flutter (15445): Error: No StoreProvider<AppState> found. To fix, please try:
I/flutter (15445):   * Using Dart 2 (required) by using the --preview-dart-2 flag
I/flutter (15445):   * Wrapping your MaterialApp with the StoreProvider<State>, rather than an individual Route
I/flutter (15445):   * Providing full type information to your Store<State>, StoreProvider<State> and
I/flutter (15445):     StoreConnector<State, ViewModel>
I/flutter (15445): If none of these solutions work, please file a bug at:
I/flutter (15445): https://github.com/brianegan/flutter_redux/issues/new

Steps to reproduce:

  1. Create a file to hold AppState named task_list_redux.dart, inside of the sandbox package, directory abc.
  2. In another file in the same abc directory, that builds the StoreConnector, import it like this: import 'package:sandbox/abc/task_list_REDUX.dart';.

Then it fails.

To solve it, simple change the import to import 'task_list_REDUX.dart';.

Incorrect docstring for StoreProvider

The docstring for StoreProvider states -

Provides a Redux [Store] to all ancestors of this Widget. This should generally be a root widget in your App. Connect to the Store provided by this Widget using a [StoreConnector] or [StoreBuilder].

It should be descendants and not ancestors.

Quesiton: Option for pre-build call back

I've been toying with the idea of introducing an additional callback, something like.

typedef OnWillChangeCallback<ViewModel> = bool Function(ViewModel viewModel);

The reason i found i wanted this was because there are times where it's desirable to trigger some form of navigation action as result of state change. You can't do that during the build action (as far as i understand), but you can do it in the IgnoreChangeCallback pre-build. This works, but feels odd for two reasons. One it happens before the distinct filter which sometimes is useful, and two it's passed the State and not the ViewModel so i can end up duplicating some conversion i've done for the viewModel.

Interested in hearing thoughts and opinions on approaches to dealing with this. I've made the above suggested change locally and using it, so i can submit a pull request if deemed a good idea.

Getting error when using combineTypedReducers

Given

final store = new Store<int>(appReducer, initialState: 0);

final appReducer = combineTypedReducers(
    [new ReducerBinding<int, Increment>((state, action) => state + 1)]);

class Increment{}

@override
  Widget build(BuildContext context) {
    return new StoreProvider<int>(
        store: store,
        child: new MaterialApp(
          title: 'Flutter Demo',
          theme: new ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: new MyHomePage(title: 'Flutter Demo Home Page'),
        ));
  }

I get the following error when trying to dispatch an Increment action:

type '(int, Increment) => int' is not a subtype of type '(int, dynamic) => int'

This is when running flutter run --preview-dart-2. When running flutter run, I get the follwing error:

The following StoreProviderError was thrown building StoreConnector<int, Store<int>>(dirty):
Error: No dynamic found. To fix, please try:

* Using Dart 2 (required) by using the --preview-dart-2 flag
* Wrapping your MaterialApp with the StoreProvider<State>, rather than an individual Route
* Providing full type information to your Store<State>, StoreProvider<State> and
StoreConnector<State, ViewModel>
flutter --version
Flutter 0.2.3 • channel beta • https://github.com/flutter/flutter.git
Framework • revision 5a58b36e36 (2 weeks ago) • 2018-03-13 13:20:13 -0700
Engine • revision e61bb9ac3a
Tools • Dart 2.0.0-dev.35.flutter-290c576264

Repro project can be found here: https://github.com/jonstodle/flutter_redux_reproduction

Auto search on type without having to return

Been thinking about this for a while now and it's been mind boggling coming up with how to implement automatically searching when a user types with flutter_redux.
I found this repo https://github.com/brianegan/dart_redux_epics and I feel like this is the correct way to do it after checking out the readme file.
My questions are:

  1. Can redux_epics including rxdart be combined with flutter_redux for this use case? If yes how will I go about doing this??
  2. Is there a way to achieve what I'm trying to with just flutter_redux and plain middlewares? If yes, help would be greatly appreciated.

Thank you.

Perform action automatically on a flutter page based on appState changes.

I'm trying to implement this idea where I dispatch a log-in action and from the log-in middleware, launch another action that changes a variable in the appState if there's an error in the log in process. That's done.
The issue is, I tried to use the storeConnector, since it updates the application based on appState changes, to check for the change and open a SnackBar when the variable changes as shown in the code for the container below, but I'm obviously not doing this the right way:

import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';

import 'package:takeout/models/app_state.dart';
import 'package:takeout/presentation/login_form.dart';
import 'package:takeout/actions/auth_actions.dart';


class AuthLogin extends StatelessWidget {

  AuthLogin({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
//    String isLoginFailed = StoreProvider.of<AppState>(context).state.isLoginFailed;

    return new StoreConnector<AppState, _ViewModel>(
        distinct: true,
        converter: _ViewModel.fromStore,
        builder: (BuildContext context, _ViewModel vm){


//          if (!StoreProvider.of<AppState>(context).reducer == "") {
//            Scaffold.of(context).showSnackBar(new SnackBar(
//                backgroundColor: Colors.red,
//                content: new Text("Error Logging In")));
//            //put reset state.isLoginFailed action here
//            isLoginFailed = StoreProvider.of<AppState>(context).state.isLoginFailed;
//          }


          return new LoginForm(
            email: vm.email,
            password: vm.password,
            saveEmail: vm.saveEmail,
            savePassword: vm.savePassword,
            attemptLogin: vm.attemptLogin,
          );
        }
    );
  }
}

class _ViewModel {
  // It should take in whatever it is you want to 'watch'
  final Function saveEmail;
  final Function savePassword;
  final Function attemptLogin;
  final String email;
  final String password;

  _ViewModel({
    this.email,
    this.password,
    this.saveEmail,
    this.savePassword,
    this.attemptLogin
  });

  // This is simply a constructor method.
  // This creates a new instance of this _viewModel
  // with the proper data from the Store.

  static _ViewModel fromStore(Store<AppState> store) {
    return new _ViewModel(
      email: store.state.typedEmail,
      password: store.state.typedPassword,
      ///...
      saveEmail: (val){
        store.dispatch(new SaveEmail(val));
      },
      savePassword: (val){
        store.dispatch(new SavePassword(val));
      },
      attemptLogin: (){
        store.dispatch(
          new LogInAction(store.state.typedEmail, store.state.typedPassword)
        );
      }
    );
  }
}

I'm sorry if this is such a tyro question but I'm just getting started with Redux and I still dont know how everything works out yet. The commented part is the part I tried to implement the "listen" for the appState change.

Access store outside flutter widget tree?

I'm having issues accessing the store from outside the flutter widget tree, and I'm not quite sure if the problem lies with me or if it's simply not possible.

Say I define a store in my main.dart:

Store<AppState> store = new Store(counterReducer, initialState: new myAppState());

class FlutterReduxApp extends StatelessWidget {
  // Create your store as a final variable in a base Widget. This works better
  // with Hot Reload than creating it directly in the `build` function.
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      theme: new ThemeData.dark(),
      home: new StoreProvider(
        store: store,
....

Then modify the state in the StoreProvider's descendants and try to access it:

@override build(BuildContext context) {
  String attrA = new StoreProvider.of(context).store.state.myAttribute;
  String attrB = store.state.myAttribute; // Direct access `store` from `main.dart`
  assert(attrA == attrB); // Fails
}

In the example above attrA has the most recent state, but attrB seems to have the store's initial state. Am I making a mistake somewhere or is this a limitation of flutter_redux?

The reason I'd like to get the store singleton outside the widget tree is to add extra store-based functionality to my models, e.g. searching the store for related models etc...

Help with routing and making store available on routes.

Need some help figuring out how to do routing with StoreConnectors.
Created a basic app with navigation to 2 screens.
Screen 2 is uses a StoreConnector while Screen3 does not. Navigation to Screen 2 fails while getting the store.

Here is the call stack of the error

The following NoSuchMethodError was thrown building StoreConnector<dynamic, Store>(dirty):
The getter 'store' was called on null.
Receiver: null
Tried calling: store
When the exception was thrown, this was the stack:
#0 Object.noSuchMethod (dart:core-patch/dart:core/object_patch.dart:46)
#1 StoreConnector.build (package:flutter_redux/flutter_redux.dart:156:44)
#2 StatelessElement.build (package:flutter/src/widgets/framework.dart:3655:28)
#3 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3602:15)

The route for Screen2 looks like this

routes: <String, WidgetBuilder>{
                '/screen2': (BuildContext context) => new StoreBuilder( builder: (context, store) {
                            return new Screen2();
                        }),

Below is the complete code.

import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';

dynamic reducer(dynamic state, action) {
  return state;
}

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

class MyApp extends StatelessWidget {
    final store = new Store(reducer, initialState: {"1" : 1, "2": 2});
    
    Widget build(BuildContext context) {
        return new MaterialApp(
            home: new StoreProvider(
                store: store,
                child: new Screen1(),
            ),
            routes: <String, WidgetBuilder>{
                '/screen2': (BuildContext context) => new StoreBuilder( builder: (context, store) {
                            return new Screen2();
                        }),
                '/screen3': (BuildContext context) => new Screen3()
            },
        );
    }
}

class Screen1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Scaffold( // 1
      appBar: new AppBar(
        title: new Text("Screen 1"), // screen title
      ),
      body: new StoreConnector<dynamic, Map>(
            converter: (store) => (store.state['1']),
            builder: (context, jsonNodeData) => new Center(
              child: new Column(
                mainAxisSize: MainAxisSize.min,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: <Widget>[
                  new RaisedButton(
                    onPressed:(){
                      Navigator.of(context).pushNamed("/screen2");
                    },
                    child: new Text("Go to Screen 2"),
                  ),
                  new RaisedButton(
                    onPressed:(){
                      Navigator.of(context).pushNamed("/screen3");
                    },
                    child: new Text("Go to Screen 3"),
                  ),
                ]
              )
            )
          )
        );
  }
}


class Screen2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("Screen 2"),
      ),
      body: new StoreConnector<dynamic, Map>(
            converter: (store) => (store.state['1']),
            builder: (context, jsonNodeData) => new Text("Screen 2")
       ) ,
    );
  }
}


class Screen3 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("Screen 3"),
      ),
      body: new Text("Screen 3")
    );
  }
}

main.dart.zip

Middleware that acts as NavigatorObserver

Hello! Here's another tasty design question with respect to redux and flutter...

I need to support undo in my app, so that users can wantonly and confidently enact destructive changes, and also so that state management is generally easier. In my case, I have a hierarchy of items, and items within those items. Users can add/edit/delete (and undo those deletions). So something as simple as hitting an ADD button, then hitting the navigation's back button needs to intelligently manage state.

My implementation is based on a stack of immutable (built) DTOs that are within my state. Then there are three actions to manage this state (BeginTransaction, CommitTransaction, RollbackTransaction). This part was really easy to get up and running. The reducers just do what you'd expect, and include assertions to ensure that the state maintains its integrity (always at least one item on the stack).

The hard part was figuring out where to dispatch these actions and finding a way to somewhat guarantee that transactions will remain balanced. Initially, I just sprinkled them through my views. It worked, but was terrible and I'm pretty sure there were some edge cases that I'd missed. I abandoned that and attempted to manifest transactions solely within middleware. Sounds an attractive proposition, but it was difficult/impossible to achieve cleanly.

Here's a look at my middleware that handles this:

class Transactions extends NavigatorObserver {
  Transactions(globals.CompositeNavigatorObserver compositeNavigatorObserver) {
    compositeNavigatorObserver.addObserver(this);
  }

  List<Middleware<AppState>> getMiddlewareBindings() => [
        new TypedMiddleware<AppState, AddEntry>(_beginTransactionReducer),
        new TypedMiddleware<AppState, RemoveEntry>(_beginTransactionReducer),
        new TypedMiddleware<AppState, AddAllowance>(_beginTransactionReducer),
        new TypedMiddleware<AppState, RemoveAllowance>(_beginTransactionReducer),
      ];

  void _beginTransactionReducer(Store<AppState> store, dynamic action, NextDispatcher next) {
    _beginTransaction(store);
    next(action);
  }

  void _beginTransaction(Store<AppState> store) {
    store.dispatch(new BeginTimesheetTransaction());
  }

  void _commitTransaction(Store<AppState> store) {
    store.dispatch(new CommitTimesheetTransaction());
  }

  void _rollbackTransaction(Store<AppState> store) {
    store.dispatch(new RollbackTimesheetTransaction());
  }

  @override
  void didPush(Route<dynamic> route, Route<dynamic> previousRoute) {
    var store = globals.store;
    var name = route.settings.name;

    if (name == RouteNames.editEntryJobDetails || name == RouteNames.editEntryAllowance) {
      _beginTransaction(store);
    }
  }

  @override
  void didPop(Route<dynamic> route, Route<dynamic> previousRoute) {
    var store = globals.store;
    var name = route.settings.name;

    if (route is MaterialPageRoute<dynamic>) {
      if (name == RouteNames.addEntryJobDetails || name == RouteNames.editEntryJobDetails || name == RouteNames.addEntryAllowance || name == RouteNames.editEntryAllowance) {
        route.completed.then((result) {
          if (result == true) {
            _commitTransaction(store);
          } else {
            _rollbackTransaction(store);
          }
        });
      }
    }
  }
}

And here are the main problems I have with this approach:

  • I had to create a CompositeNavigatorObserver class, which just manages any number of NavigatorObserver instances, forwarding all events onto them. I pass this CompositeNavigatorObserver into my MaterialApp. This allows the middleware to monitor and react to navigation events. I could not think of a cleaner way.
  • Ugh, look how I sometimes use the global Store instance, and other times it's passed in so I don't have to. I also used Scaffold.of at one point, but that was no better (and was messier).
  • Not the end of the world, but now I have to give my routes names because otherwise I have no sane means of triggering transactional logic based on navigation events. Moreover, those names need to distinguish between add/edit scenarios, because the transactional approach is different (with add, the transaction begins before the item is added, with edit there is no specific action so I need to trigger it upon navigation - hmm, maybe I should have a edit actions... 🤔 ).
  • In addition, my pages need to pop true when editing completes successfully. This was also a bit messy (in a separate piece of middleware).
  • Really not sure how I'd test this effectively, especially given the dependency on global state. I could pass in a delegate that is used to resolve the state, I suppose.

OK, sorry for the rando post again. Really just interested in hearing any thoughts on this direction or how it could be improved.

Thanks!

Error after updating to 0.4.0

Hey @brianegan

after updating to 0.4.0 Im getting the following error. Any idea what could cause the issue?

══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞══
The following NoSuchMethodError was thrown building StoreConnector<AppState,
Store<AppState>>(dirty):
The getter 'store' was called on null.
Receiver: null
Tried calling: store

flutter Doctor

[✓] Flutter (Channel master, v0.2.4-pre.64, on Mac OS X 10.13.3 17D102, locale en-DE)
    • Flutter version 0.2.4-pre.64 at /Users/eikebartels/Applications/flutter
    • Framework revision 619ebd67a9 (5 hours ago), 2018-03-20 00:26:10 -0700
    • Engine revision c3ab0c9143
    • Dart version 2.0.0-dev.39.0.flutter-f1ebe2bd5c

Main

void main() {
  final store = new Store<AppState>(
    appStateReducer,
    initialState: new AppState.loading(),
    middleware: createStoreMiddleware()
  );
  runApp(new App(store: store));
}

App

class App extends StatelessWidget {
  final Store store;

  App({Key key, this.store }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return new StoreProvider<AppState>(
      store: store,
      child: new MaterialApp(
        theme: AppTheme.theme,
        routes: {
          "/": (context) {
            return new StoreBuilder<AppState>(
                builder: (context, store) {
                  return new OnBoarding();
                });
          }
        },
      ),
    );
  }
}

counter demo: UI doesn't update after hot reload

I'm following the tutorial from https://flutterbyexample.com/redux-app-getting-to-start
However, when I hot reload, the UI does not update with the new value. This issue is same as #42 . Btw, hot reload works fine on other projects without using redux. Another interesting thing is that after hot reloading, the logger middleware prints the same message twice for a single tap event.

image

Here is the output from flutter doctor:

Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter (Channel beta, v0.4.4, on Microsoft Windows [Version 10.0.17134.112], locale en-US)
[√] Android toolchain - develop for Android devices (Android SDK 26.0.2)
[√] Android Studio (version 2.3)
    X Flutter plugin not installed; this adds Flutter specific functionality.
    X Dart plugin not installed; this adds Dart specific functionality.
[!] IntelliJ IDEA Ultimate Edition (version 2017.1)
    X Flutter plugin not installed; this adds Flutter specific functionality.
    X Dart plugin not installed; this adds Dart specific functionality.
[√] VS Code, 32-bit edition (version 1.23.1)
[√] Connected devices (1 available)

! Doctor found issues in 1 category.

I've attached the contents of lib folder and pubspec.yaml
firebaseredux.zip

CopyWith to allow setting values to null - like spread operator

In the following code, I use the CopyWith pattern. One problem I'm experiencing with this is if I want to set a property to a null value (this is quite easy using the spread operator in js). The value of the name field of the 'Joe' object below is not null, it is 'Bob'. Do you know of any way to overcome this problem in Dart?

void main() {
  var bob = new Person(age: 15, name: 'Bob');
  print(bob.toString());
  var joe = bob.copyWith(name: null);
  print(joe.toString());
}

class Person {
  final int age;
  final String name;

  Person({this.age, this.name});

  Person copyWith({int age, String name}) {
    return new Person(
      age: age ?? this.age,
      name: name ?? this.name,
    );
  }
}

Model updates but widget does not redraw.

Hi

This works fine

new Text(viewModel.game.duration.toString())

but this does not.

new Text(viewModel.game.formattedDuration(viewModel.game.duration),

Am I doing this wrong? Should I be formatting my duration inside the reducer?

(I'm on a slightly older version so I'm updating now)

No storeprovider<dynamic> found

I keep getting not storeprovider found. here is my main.dart code:

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

class MyApp extends StatelessWidget {

  final store = new Store<AppState>(
    appReducer,
    initialState: new AppState.loading(),
  );

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return new StoreProvider<AppState>(
      store: store,
      child: new MaterialApp(
        title: 'Flutter Demo',
        routes: <String, WidgetBuilder>{
          FamilRoutes.home: (context) {
            return new StoreBuilder(
                onInit: (store) =>
                    store.dispatch(new LoadAreasAction()),
                builder: (context, store) {
                  return new ListAreas();
                });
          },
          FamilRoutes.addArea: (context) {
              return new AddArea();
          },
        }
      ),
    );
  }
}`

Here is my package versions:

google_sign_in: "^3.0.3+1"
  firebase_auth: "^0.5.10"
  cloud_firestore: "^0.7.2"
  redux: "^3.0.0"
  flutter_redux: "^0.5.1"

Wrong version code deployed (0.3.6 instead of 0.4.0)

First of all, thanks for putting the effort to release a version of flutter_redux that support Dart 2 this quickly, really appreciated 💯

Now, the issue: In the repo it looks like the latest version of the package that's been released is 0.4.0 but actually in pub it got released as 0.3.6 https://pub.dartlang.org/packages/flutter_redux/versions/0.3.6

Version 0.3.6 of the flutter_redux package seems to work well with Dart 2, so It should probably be deleted and re-released as 0.4.0, as people can get confused as to which version to use for Dart 1 and Dart 2.

Dispatching actions that have no reducers/middleware handling them

Hello,

This one is a doozy - strap yourself in!

I just found something the (super) hard way, and I want to record my thoughts before I leave work for the day. If necessary, I'm happy to put more effort in here and try to produce a succinct repro. Right now I need some validation/feedback on whether I'm doing something obviously wrong.

I was writing an automatic logout feature for my app, so if the user stops interacting for, say, 30 seconds, it logs them out. To achieve this, I wrote an InputDetector control like this:

class InputDetector extends StatefulWidget {
  final VoidCallback onInput;
  final Widget child;

  InputDetector({@required this.child, @required this.onInput})
      : assert(child != null),
        assert(onInput != null);

  @override
  State<StatefulWidget> createState() => new _InputDetectorState();
}

class _InputDetectorState extends State<InputDetector> {
  @override
  void initState() {
    super.initState();

    // Detect raw input from the keyboard (alas, only the hardware keyboard).
    RawKeyboard.instance.addListener(_onRawKeyEvent);
  }

  @override
  void dispose() {
    RawKeyboard.instance.removeListener(_onRawKeyEvent);

    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    var touchDetector = new Listener(
      behavior: HitTestBehavior.translucent,
      child: widget.child,
      onPointerDown: (_) => widget.onInput(),
      onPointerMove: (_) => widget.onInput(),
      onPointerCancel: (_) => widget.onInput(),
      onPointerUp: (_) => widget.onInput(),
    );

    return touchDetector;
  }

  void _onRawKeyEvent(RawKeyEvent event) => widget.onInput();
}

It works fine, apart from not being able to detect software keyboard input (but I'm chasing that up separately).

Anyway, I hook it into the root of my visual tree, and then dispatch an action whenever input is detected:

var inputDetector = new InputDetector(
  child: materialApp,
  onInput: () => store.dispatch(new DeferIdleLogout()),
);

My middleware then uses these actions to manage a timer, which will log the user out if it expires. I added a global flag to enable/disable that particular middleware, since it would be super annoying for developers to be logged out all the time whilst working on the app. The CI server substitutes in an appropriate value for the global prior to building, so there's no chance of developers accidentally disabling the feature.

This was all working wonderfully, except some of my widget tests started to fail. I dug in and found that all tests performing swipe interactions were failing. Weird. I tried running up the app and, sure enough, I couldn't swipe my Dismissible widgets any more. Wat.

After initially suspecting flutter was to blame - something to do with using a Listener above a Dismissible, I quickly dismissed this when an attempted repro worked fine. I had to whittle things down, control by control, line by line. It took quite a while, but I eventually narrowed it down to this code from above:

var inputDetector = new InputDetector(
  child: materialApp,
  onInput: () => store.dispatch(new DeferIdleLogout()),
);

Specifically, dispatching the action. If I commented out the dispatch and did nothing, it all worked fine.

Digging even further, I found that if I only forwarded onPointerCancel and onPointerUp, it worked. Forwarding either onPointerDown or onPointerMove exhibited the same problem. Now I started to suspect the frequency of the action dispatches or something. But then I tried only onPointerDown and it still didn't work. Wowsers. Now I started thinking maybe dispatching the action at the start of the interaction was somehow thwarting the interaction, even though nothing was handling that action.

Wait, I thought, nothing is handling the action. Maybe that's relevant (and maybe the issue title was a total spoiler). I enabled the middleware that dealt with this action (even though I don't ordinarily want it enabled for developers), and sure enough, it worked. Even with all input events forwarded, things were working fine.

So, um, sorry for the short story, but I'm really confused. I can see an easy way around this: instead of selectively registering the middleware, always register it and check the global value when handling the action. But I don't understand why this is happening. If an action has no reducer/middleware anywhere, would you expect Weird Things like this to occur?

Thanks for reading.

StoreProvider not found

I am getting StoreProvider not found when I try connecting from another class. Hoping the code below helps understanding the issue.

 return new StoreProvider<AppState>(
      store: store,
      child: new MaterialApp(
        title: title,
        theme: new ThemeData(
          primaryColor: Colors.lightBlue,
        ),
        home: new HomePage(), /// <<--- USING STORE_CONNECTOR AS BELOW FAILS WITHIN HOMEPAGE
        // home: new StoreConnector<AppState, int>(
        //   converter: (storexx) => storexx.state.luckyNumber,
        //   builder: (context, count) {
        //     return new Text(count.toString());
        //   },
        // ),
      ),
    );

Scrolling down to bottom of screen when Redux store changes

Hi Brian,

I'm trying to build a small chat app with Redux, and when new list items (chat messages) are added to the store I want to animate a scroll down to the bottom of the list so they are in view.

Without Redux, in Flutter you're able to use ScrollController then call _scrollController.animateTo when you call setState to add the new items (source).

In React, you would have componentDidUpdate, so you would be able to animate the scroll to the bottom in componentDidUpdate when your Redux store changes (source).

Wondering what is the equivalent way to do this in Redux with Flutter since StatelessWidget doesn't provide hooks like componentDidUpdate? Unfortunately, just calling _scrollController.animateTo after dispatching a Redux action doesn't scroll to the bottom position after the Redux store updates.

Initializing stuff with store.dispatch

Hi,

I have this pattern where I initially injected a store instance in all my components using it (I wasn't aware of new StoreProvider.of(...)).

In my App, in some places, I also do 1 time initializations (ie: fetch data via store.dispatch(...)) in initState(). Previously, I could do it fine, because I had an instance of my Store.

Now I tried to move towards to new StoreProvider.of(...), but I faced the issue where context.inheritFromWidgetOfExactType(StoreProvider) can't be called in initState. Is there a way to achieve that somehow?

Seems like the Flutter docs suggest that I use didChangeDependencies() but that is not called only 1 time.

Thanks

Add link to redux_persist

Hey, this is my first day of Dart and I found there was no way to persist to a redux store without writing the code yourself all the time. I made a small library called redux_persist to help with that.

I'd like to add a link to the library in the README, as this is probably a common pattern with flutter_redux!

Also, if you have a few minutes to space, check out the library and let me know of any issues you see on it! Thanks for flutter_redux!

Question: What is the difference btw flutter_redux and flutter_greencat? Which one should I choose?

Hi, I don't know if I could ask a question in issues. If not, I am very sorry about this.
I am new to flutter and mobile development.
The Purpose of this project in README.md is very well written and I am glad the author wrote it. But I am still confused the difference between flutter_redux and flutter_greencat. They are created by the same author and the README.md mentions each other. I googled greencat and find out it is a port of redux from reactjs to Dart. In another word, it means greencat in dart is redux. But now, they become two different projects flutter_redux and flutter_greencat. I am wondering which one I should choose and will both projects well supported in the near future?

PS: can I use flutter_redux in production? since it's a very new project, I am wondering if it's stable to be used in produciton.

Thank you very much for your help.

Accessing store in StoreConnector.builder

Hi,

I need to have access to the store in the builder function so I can dispatch calls to the middleware. Is there a way to do this "nicely" without storing the store during the call to onInit?

I am new to dart and I just cannot see how to do this.

The use case I am looking at is having a widget display some stateful data and a button. The button when pressed should invoke the store.dispatch(new SomeAction());

Paul

Can middleware trigger reducer execution?

I just tried this, expecting it to work:

void _handleApprove(Store<AppState> store, Approve action, NextDispatcher next) {
  next(action);
  store.dispatch(new Save());
}

My expectation was that the Approve reducer would execute, and then the Save action would be instigated. In reality, the opposite is true.

Have I fundamentally misunderstood the middleware/reducer relationship here? Shouldn't forward an action onto the next piece of middleware eventuate in the action reaching the reducers before middleware continues execution?

The getter 'store' was called on null

Hi,

I'm trying to implement redux on my flutter app, versions:

  redux: "^3.0.0"
  flutter_redux: "^0.5.0"

When I run my app the error in the title is thrown.
I followed the Dart 2 migration guide for both the packages.

Flutter: 0.2.8
Dart: 2.0.0

Error thrown when using Dart 2 Preview

I tried to get the flutter-redux example working with the Dart 2 preview, but I get the following error when I try to run it:

══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
The following NoSuchMethodError was thrown building StoreConnector<int, String>(dirty):
The getter 'store' was called on null.
Receiver: null
Tried calling: store
When the exception was thrown, this was the stack:
#0 Object.noSuchMethod (dart:core/object_patch.dart:46:5)
#1 StoreConnector.build (package:flutter_redux/flutter_redux.dart:156:44)
#2 StatelessElement.build (package:flutter/src/widgets/framework.dart:3678:28)

Works fine when not activating the preview. The Flutter build I'm using is:

Flutter 0.1.5 • channel beta • https://github.com/flutter/flutter.git
Framework • revision 3ea4d06340 (3 weeks ago) • 2018-02-22 11:12:39 -0800
Engine • revision ead227f118
Tools • Dart 2.0.0-dev.28.0.flutter-0b4f01f759

Fix StoreProvider.of for Reified types

I'm repeatedly getting

I/flutter ( 9311): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter ( 9311): The following NoSuchMethodError was thrown building StoreConnector<int, Store<int>>(dirty):
I/flutter ( 9311): The getter 'store' was called on null.
I/flutter ( 9311): Receiver: null
I/flutter ( 9311): Tried calling: store

error when trying to use StoreBuilder or StoreConnector in the hierarchy. My app widget builds the rest like this:

@override
Widget build(BuildContext context) {
  return new StoreProvider<int>(
    store: new Store<int>(
            (i, action) => i + 1,
        initialState: 0
    ),
    child: new MaterialApp(
      title: 'App',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new Scaffold(
          appBar: new AppBar(
            title: new Text("App"),
          ),
          body: new MainScreen(),
    ),
  );
}

I can't get it to work, I understand StoreProvider is a simple InheritedWidget, but even if I try to manually call new StoreProvider.of(context) in a widget down the tree, I get null. I don't understand, since I've created dead simple inherited widget and placed it instead of StoreProvider and I could fetch it with of() method without issues (I almost did a copy-paste from the StoreProvider class).

The int state and inline reducer is just a result of me trying to simplify the things as much as I can, with no luck. Any idea why would it be like this?

flutter_redux with form fields

Hi, I'm new to flutter/dart and I'm developing a simple use-case: 1 form across 2 pages
I want to persist data and be able to get the first screen data when I'm on the second view. After hours of research, I found this library and I can't get it to work properly for my case.
Can you provide a simple example with forms and dart 2.0 ? I think my issue is maybe related to #20, but mostly because I'm a beginner in the language.
It's all so foggy to me with controllers, focusNode, event propagation... I'm a backend guy, I'm used to simpler things.
My code is on this branch, I'm sorry for the mess, its a poc for now...

https://github.com/Vinubaba/OM-App/blob/230658b4defb1ee7244788cd2e63398fc3d8992f/lib/main.dart#L252-L279

I've tried to understand your example on gitlab, but I don't understand every part of it

VM -> widget data transfer: am I doing it right?

Hi,

There's something I've found myself doing in each of my views, and I can't help but think it's more complicated than it should be, so I just wanted to check in to see how I should be handling this.

The scenario is simply that I want to transfer data from the VM to the view for a TextField that has a controller. The reasons I might need to do this include:

  1. The view has just been created and it needs to reflect data in the VM that has been loaded.
  2. Reducers have decided that a particular value should change. That change needs to propagate to the view (via the VM).

As a simple example, imagine a login page. When login fails, the reducer resets the password back to empty (or null). That should of course be reflected in the view. However, I don't see a way to ensure this without manually applying the value to the TextEditingController.

Manually transferring the values isn't so bad, except that it quickly gets complicated because I need to take selection into account. Currently my helper method looks like this:

void _updateText(TextEditingController controller, String text) {
  text = text ?? "";
  var selection = controller.selection;
  controller.text = text;

  if (selection.start > text.length || selection.end > text.length) {
    return;
  }

  controller.selection = selection;
}

I call this at the start of my build method for each TextEditingController, passing in the value provided by the VM. It's all a bit awkward, so I feel like I'm missing something obvious here.

Any thoughts?

How do I dispatch actions to the Store without building a widget?

Hi there.

How was your trip? 😄

I've been happily using flutter_redux, but I'm having trouble with one thing in particular.
I want some methods inside my Widgets to dispatch actions to the Store from the StoreProvider, but not necessarily build a widget.

Since StoreConnector() is only intended for passing the store to a Widget in the context of the build method, I'm wondering: is there any class or function I can use inside of a method that returns void (for example) that just provides me with the Store, so I can dispatch an action to it without needing to build a widget?

Thank you.

NoVersionException is throwing while trying to install

Hi, I am getting an error while trying to install this lib. I don't get the exact reason why this happens.

`[flutter_redux_boilerplate] flutter packages get
Running "flutter packages get" in flutter_redux_boilerplate...
Package flutter_redux has no versions that match 0.4.1 derived from:

  • flutter_redux_boilerplate depends on version 0.4.1
    ---- Log transcript ----
    FINE: Pub 2.0.0-dev.16.0
    MSG : Resolving dependencies...
    SLVR: Solving dependencies:
    | - cupertino_icons ^0.1.0 from hosted (cupertino_icons) (locked to 0.1.1)
    | - flutter any from sdk (flutter) (locked to 0.0.0)
    IO : Get versions from https://pub.dartlang.org/api/packages/flutter_redux.
    IO : HTTP GET https://pub.dartlang.org/api/packages/flutter_redux
    | Accept: application/vnd.pub.v2+json
    | X-Pub-OS: macos
    | X-Pub-Command: get
    | X-Pub-Session-ID: E1E8D34B-A97D-46A3-B242-DD2F405A3190
    | X-Pub-Environment: flutter_cli:get
    | X-Pub-Reason: dev
    | user-agent: Dart pub 2.0.0-dev.16.0
    IO : HTTP response 200 OK for GET https://pub.dartlang.org/api/packages/flutter_redux
    | took 0:00:00.757222
    | transfer-encoding: chunked
    | date: Thu, 29 Mar 2018 04:13:37 GMT
    | content-encoding: gzip
    | vary: Accept-Encoding
    | via: 1.1 google
    | content-type: application/json
    | x-frame-options: SAMEORIGIN
    | x-xss-protection: 1; mode=block
    | x-content-type-options: nosniff
    | server: dart:io with Shelf
    IO : Get versions from https://pub.dartlang.org/api/packages/redux.
    IO : HTTP GET https://pub.dartlang.org/api/packages/redux
    | Accept: application/vnd.pub.v2+json
    | X-Pub-OS: macos
    | X-Pub-Command: get
    | X-Pub-Session-ID: E1E8D34B-A97D-46A3-B242-DD2F405A3190
    | X-Pub-Environment: flutter_cli:get
    | X-Pub-Reason: dev
    | user-agent: Dart pub 2.0.0-dev.16.0
    IO : HTTP response 200 OK for GET https://pub.dartlang.org/api/packages/redux
    | took 0:00:00.765028
    | transfer-encoding: chunked
    | date: Thu, 29 Mar 2018 04:13:37 GMT
    | content-encoding: gzip
    | vary: Accept-Encoding
    | via: 1.1 google
    | content-type: application/json
    | x-frame-options: SAMEORIGIN
    | x-xss-protection: 1; mode=block
    | x-content-type-options: nosniff
    | server: dart:io with Shelf
    SLVR: * start at root
    SLVR: | cupertino_icons 0.1.1 from hosted is locked
    SLVR: | * select cupertino_icons 0.1.1 from hosted
    SLVR: | | flutter 0.0.0 from sdk is locked
    SLVR: | | * select flutter 0.0.0 from sdk
    SLVR: | | | collection 1.14.5 from hosted is locked
    SLVR: | | | * select collection 1.14.5 from hosted
    SLVR: | | | | flutter_test 0.0.0 from sdk is locked
    SLVR: | | | | * select flutter_test 0.0.0 from sdk
    SLVR: | | | | | * select pub itself
    SLVR: | | | | | | args 1.2.0 from hosted is locked
    SLVR: | | | | | | * select args 1.2.0 from hosted
    SLVR: | | | | | | | async 2.0.3 from hosted is locked
    SLVR: | | | | | | | * select async 2.0.3 from hosted
    SLVR: | | | | | | | | barback 0.15.2+14 from hosted is locked
    SLVR: | | | | | | | | * select barback 0.15.2+14 from hosted
    SLVR: | | | | | | | | | boolean_selector 1.0.2 from hosted is locked
    SLVR: | | | | | | | | | * select boolean_selector 1.0.2 from hosted
    SLVR: | | | | | | | | | | charcode 1.1.1 from hosted is locked
    SLVR: | | | | | | | | | | * select charcode 1.1.1 from hosted
    SLVR: | | | | | | | | | | | cli_util 0.1.2+1 from hosted is locked
    SLVR: | | | | | | | | | | | * select cli_util 0.1.2+1 from hosted
    SLVR: | | | | | | | | | | | | convert 2.0.1 from hosted is locked
    SLVR: | | | | | | | | | | | | * select convert 2.0.1 from hosted
    SLVR: | | | | | | | | | | | | | crypto 2.0.2+1 from hosted is locked
    SLVR: | | | | | | | | | | | | | * select crypto 2.0.2+1 from hosted
    SLVR: | | | | | | | | | | | | | | csslib 0.14.1 from hosted is locked
    SLVR: | | | | | | | | | | | | | | * select csslib 0.14.1 from hosted
    SLVR: | | | | | | | | | | | | | | | glob 1.1.5 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | * select glob 1.1.5 from hosted
    SLVR: | | | | | | | | | | | | | | | | html 0.13.2+2 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | * select html 0.13.2+2 from hosted
    SLVR: | | | | | | | | | | | | | | | | | http 0.11.3+16 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | * select http 0.11.3+16 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | http_multi_server 2.0.4 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | * select http_multi_server 2.0.4 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | http_parser 3.1.1 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | | * select http_parser 3.1.1 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | | io 0.3.1 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | | | * select io 0.3.1 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | | | isolate 1.1.0 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | | | | * select isolate 1.1.0 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | | | | js 0.6.1 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | | | | | * select js 0.6.1 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | logging 0.11.3+1 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | * select logging 0.11.3+1 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | matcher 0.12.1+4 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | * select matcher 0.12.1+4 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | meta 1.1.1 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | * select meta 1.1.1 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | mime 0.9.5 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | * select mime 0.9.5 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | mockito 2.2.1 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | * select mockito 2.2.1 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | multi_server_socket 1.0.1 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | * select multi_server_socket 1.0.1 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | node_preamble 1.4.0 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | * select node_preamble 1.4.0 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | package_config 1.0.3 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | * select package_config 1.0.3 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | package_resolver 1.0.2 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | * select package_resolver 1.0.2 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | path 1.5.1 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | * select path 1.5.1 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | plugin 0.2.0+2 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | * select plugin 0.2.0+2 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | pool 1.3.4 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | * select pool 1.3.4 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | pub_semver 1.3.2 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | * select pub_semver 1.3.2 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | quiver 0.28.0 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | * select quiver 0.28.0 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | shelf 0.7.2 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | * select shelf 0.7.2 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | shelf_packages_handler 1.0.3 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | * select shelf_packages_handler 1.0.3 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | shelf_static 0.2.7 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | * select shelf_static 0.2.7 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | shelf_web_socket 0.2.2 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | * select shelf_web_socket 0.2.2 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | sky_engine 0.0.99 from path is locked
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | * select sky_engine 0.0.99 from path
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | source_map_stack_trace 1.1.4 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | * select source_map_stack_trace 1.1.4 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | source_maps 0.10.4 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | * select source_maps 0.10.4 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | source_span 1.4.0 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | * select source_span 1.4.0 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | stack_trace 1.9.1 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | * select stack_trace 1.9.1 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | stream_channel 1.6.3 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | * select stream_channel 1.6.3 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | string_scanner 1.0.2 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | * select string_scanner 1.0.2 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | term_glyph 1.0.0 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | * select term_glyph 1.0.0 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | test 0.12.30+1 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | * select test 0.12.30+1 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | analyzer 0.31.1 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | * select analyzer 0.31.1 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | front_end 0.1.0-alpha.9 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | * select front_end 0.1.0-alpha.9 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | kernel 0.3.0-alpha.9 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | * select kernel 0.3.0-alpha.9 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | typed_data 1.1.4 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | * select typed_data 1.1.4 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | utf 0.9.0+3 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | * select utf 0.9.0+3 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | vector_math 2.0.5 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | * select vector_math 2.0.5 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | watcher 0.9.7+6 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | * select watcher 0.9.7+6 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | web_socket_channel 1.0.6 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | * select web_socket_channel 1.0.6 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | yaml 2.1.13 from hosted is locked
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | * select yaml 2.1.13 from hosted
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | version 0.4.0 of flutter_redux doesn't match 0.4.1:
    | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | flutter_redux_boilerplate 0.0.0 (root) -> flutter_redux 0.4.1 from hosted (flutter_redux)
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | version 0.3.6 of flutter_redux doesn't match 0.4.1:
    | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | flutter_redux_boilerplate 0.0.0 (root) -> flutter_redux 0.4.1 from hosted (flutter_redux)
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | version 0.3.5 of flutter_redux doesn't match 0.4.1:
    | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | flutter_redux_boilerplate 0.0.0 (root) -> flutter_redux 0.4.1 from hosted (flutter_redux)
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | version 0.3.4 of flutter_redux doesn't match 0.4.1:
    | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | flutter_redux_boilerplate 0.0.0 (root) -> flutter_redux 0.4.1 from hosted (flutter_redux)
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | version 0.3.3 of flutter_redux doesn't match 0.4.1:
    | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | flutter_redux_boilerplate 0.0.0 (root) -> flutter_redux 0.4.1 from hosted (flutter_redux)
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | version 0.3.2 of flutter_redux doesn't match 0.4.1:
    | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | flutter_redux_boilerplate 0.0.0 (root) -> flutter_redux 0.4.1 from hosted (flutter_redux)
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | version 0.3.1 of flutter_redux doesn't match 0.4.1:
    | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | flutter_redux_boilerplate 0.0.0 (root) -> flutter_redux 0.4.1 from hosted (flutter_redux)
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | version 0.3.0 of flutter_redux doesn't match 0.4.1:
    | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | flutter_redux_boilerplate 0.0.0 (root) -> flutter_redux 0.4.1 from hosted (flutter_redux)
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | version 0.2.0 of flutter_redux doesn't match 0.4.1:
    | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | flutter_redux_boilerplate 0.0.0 (root) -> flutter_redux 0.4.1 from hosted (flutter_redux)
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | version 0.1.1 of flutter_redux doesn't match 0.4.1:
    | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | flutter_redux_boilerplate 0.0.0 (root) -> flutter_redux 0.4.1 from hosted (flutter_redux)
    SLVR: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | version 0.1.0 of flutter_redux doesn't match 0.4.1:
    | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | flutter_redux_boilerplate 0.0.0 (root) -> flutter_redux 0.4.1 from hosted (flutter_redux)
    SLVR: BacktrackingSolver took 0:00:02.239035 seconds.
    | - Tried 1 solutions
    | - Requested 2 version lists
    | - Looked up 1 cached version lists
    |
    FINE: Resolving dependencies finished (2.2s).
    ERR : Package flutter_redux has no versions that match 0.4.1 derived from:
    | - flutter_redux_boilerplate depends on version 0.4.1
    FINE: Exception type: NoVersionException
    FINE: package:pub/src/entrypoint.dart 216 Entrypoint.acquireDependencies
    | package:pub/src/command/get.dart 38 GetCommand.run
    | package:args/command_runner.dart 194 CommandRunner.runCommand
    | package:pub/src/command_runner.dart 167 PubCommandRunner.runCommand.
    | dart:async new Future.sync
    | package:pub/src/utils.dart 103 captureErrors.
    | package:stack_trace Chain.capture
    | package:pub/src/utils.dart 118 captureErrors
    | package:pub/src/command_runner.dart 167 PubCommandRunner.runCommand
    | package:pub/src/command_runner.dart 116 PubCommandRunner.run
    | /b/build/slave/dart-sdk-mac-dev/build/sdk/third_party/pkg/pub/bin/pub.dart 8 main
    | ===== asynchronous gap ===========================
    | dart:async _Completer.completeError
    | package:pub/src/entrypoint.dart Entrypoint.acquireDependencies
    | ===== asynchronous gap ===========================
    | dart:async _asyncThenWrapperHelper
    | package:pub/src/entrypoint.dart 194 Entrypoint.acquireDependencies
    | package:pub/src/command/get.dart 38 GetCommand.run
    | package:args/command_runner.dart 194 CommandRunner.runCommand
    | ===== asynchronous gap ===========================
    | dart:async new Future.microtask
    | package:args/command_runner.dart 142 CommandRunner.runCommand
    | package:pub/src/command_runner.dart 167 PubCommandRunner.runCommand.
    | dart:async new Future.sync
    | package:pub/src/utils.dart 103 captureErrors.
    | package:stack_trace Chain.capture
    | package:pub/src/utils.dart 118 captureErrors
    | package:pub/src/command_runner.dart 167 PubCommandRunner.runCommand
    ---- End log transcript ----
    pub get failed (1)
    exit code 1`

Is there a better way to handle nested properties in state?

For example, I have the following nested state (only left the relevant parts for clarity):

class AppState {

   final OpenBook openBook;
}

class OpenBook {

  final bool headerIsFetching;
}

 
And the reducer:

new AppState(

  openBook: new OpenBook(//<- This can be optimized! new instance is built for every cycle regardless of the action
    headerIsFetching: buildFetchingReducer<FetchOpenBookHeaderAction, FetchOpenBookHeaderSucceededAction, FetchOpenBookHeaderFailedAction>(state.openBook.headerFetching, action)
  )  
);

 
I have thought 2 options:

  1. Encapsulate the instantiation of the OpenBook inside an if that tests for the relevant actions, but this leads to code duplication and it goes against the Open/closed principle (SOLID)
  2. Use a switch and for each relevant action create a new OpenBook and initialize only the relevant property, but it feels weird as Dart doesn't have the Spread operator (the three dots in javascript)
  3. Flatten the state

Do you have a recommendation?

onInit does not triggers state change

Hey @brianegan

first of all thanks for the awesome library!!!

I noticed the following:
Im having an app state requestLessonState which is on app launch null. onInit I would like to dispatch an action to assign requestLessonState a value, in this case false. The action get dispatched as expected and the state changed to the expected value. BUT the view model get generated before the state changed. So for me it is not possible to basically create an init value for the model.

Is this an issue or an expected behaviour (something async or so)? If it is an expected behaviour, do you something in mind to solve this "issue"?

I hope you can help me some how or push me in the right direction.

Thanks in advance

Imagine the following setup

Main App State

@immutable
class AppState {

  final RequestLessonState requestLessonState;

  AppState({
    this.requestLessonState,
  });

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
          other is AppState &&
              runtimeType == other.runtimeType &&
              requestLessonState == other.requestLessonState;

  @override
  int get hashCode => requestLessonState.hashCode;
  
}

Sub App State

@immutable
class RequestLessonState {
  final bool dropOffIsDifferent;
  
  RequestLessonState({
      this.dropOffIsDifferent,
  });

  RequestLessonState copyWith({
     String dropOffLocation,
  }) {

    return new RequestLessonState(
        dropOffIsDifferent: dropOffIsDifferent ?? this.dropOffIsDifferent,
    );
  }

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
          other is RequestLessonState &&
              runtimeType == other.runtimeType &&
              dropOffIsDifferent == other.dropOffIsDifferent;

  @override
  int get hashCode => dropOffIsDifferent.hashCode;
  
}

Widget

class CreateLessonScreen extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return new StoreConnector<AppState, _ViewModel>(
      onInit: (store) => store.dispatch(new InitRequestLessonAction()),
      distinct: true,
      converter: _ViewModel.fromStore,
      builder: (context, vm) {
        return new Scaffold(
            appBar: new AppBar(),
            body: new SafeArea(
                  bottom: true,
                  child: new Column(
                    children: <Widget>[
                      new Switch(value: vm.dropOfIsDifferent, onChanged: (value){}),
                    ],
                  ),
                ),
        );
      },
    );
  }
}

My View Model

class _ViewModel {

  final bool dropOfIsDifferent;
  
  _ViewModel({
    this.dropOfIsDifferent,
 
  });

  static _ViewModel fromStore(Store<AppState> store) {
      return new _ViewModel(
          dropOfIsDifferent: store.state.requestLessonState.dropOffIsDifferent,
      );
  }

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
          other is _ViewModel &&
              runtimeType == other.runtimeType &&
              dropOfIsDifferent == other.dropOfIsDifferent;

  @override
  int get hashCode => dropOfIsDifferent.hashCode;
  
}

Action

class InitRequestLessonAction {
  InitRequestLessonAction();

  @override
  String toString() {
    return 'InitRequestLessonAction{}';
  }
}

Reducer

// the main reducer 
AppState appReducer(AppState state, action) {
  return new AppState(
      requestLessonState: requestLessonReducer(state.requestLessonState, action),
  );
}


final requestLessonReducer = combineTypedReducers<RequestLessonState>([
  new ReducerBinding<RequestLessonState, InitRequestLessonAction>(_setInitRequestLessonAction),
]);

RequestLessonState _setInitRequestLessonAction(RequestLessonState state, InitRequestLessonAction action) {
  return new RequestLessonState(dropOffIsDifferent: false);
}

Is StoreConnector<T, Tuple2<T,T>> an anti-pattern?

Coming from a ReduxJS/React background it is very common to pass multiple items from a redux store to a child component (e.g. using both mapStateToProps and mapDispatchToProps).

I have not seen in any example where you may want to pass both a state and a dispatcher to the same child component.

Is it considered correct to returning a tuple from a StoreConnector?

          // Connect the Store to a FloatingActionButton. In this case, we'll
          // use the Store to build a callback that with dispatch an Increment
          // Action.
          //
          // Then, we'll pass this callback to the button's `onPressed` handler.
          floatingActionButton:
              new StoreConnector<int, Tuple2<int, VoidCallback>>(
            converter: (store) {
              // Return a Tuple2 - one like mapStateToProps and one mapDispatchToProps
              // Return a `VoidCallback`, which is a fancy name for a function
              // with no parameters. It only dispatches an Increment action.
              return new Tuple2(
                  store.state, () => store.dispatch(Actions.Increment));
            },
            builder: (context, tuple) {
              return new FloatingActionButton(
                  // Attach the `callback` to the `onPressed` attribute
                  onPressed: tuple.item2,
                  tooltip:
                      tuple.item1.isOdd ? 'Increment_ODD' : 'Increment_EVEN',
                  child: new Icon(Icons.add));
            },
          ),
        ),

When using fluro, No StoreProvider<AppState> found.

Error creating widget with fluro:

I/flutter ( 6397): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter ( 6397): The following StoreProviderError was thrown building StoreConnector<AppState,
I/flutter ( 6397): BangumiListState>(dirty):
I/flutter ( 6397): Error: No StoreProvider found. To fix, please try:
I/flutter ( 6397):
I/flutter ( 6397): * Using Dart 2 (required) by using the --preview-dart-2 flag
I/flutter ( 6397): * Wrapping your MaterialApp with the StoreProvider, rather than an individual Route
I/flutter ( 6397): * Providing full type information to your Store, StoreProvider and
I/flutter ( 6397): StoreConnector<State, ViewModel>
I/flutter ( 6397):
I/flutter ( 6397): If none of these solutions work, please file a bug at:
I/flutter ( 6397): https://github.com/brianegan/flutter_redux/issues/new
I/flutter ( 6397):
I/flutter ( 6397): When the exception was thrown, this was the stack:
I/flutter ( 6397): #0 StoreProvider.of (package:flutter_redux/flutter_redux.dart:29:27)
I/flutter ( 6397): #1 StoreConnector.build (package:flutter_redux/flutter_redux.dart:184:28)
I/flutter ( 6397): #2 StatelessElement.build (package:flutter/src/widgets/framework.dart:3687:28)
I/flutter ( 6397): #3 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3634:15)
I/flutter ( 6397): #4 Element.rebuild (package:flutter/src/widgets/framework.dart:3487:5)
I/flutter ( 6397): #5 StatelessElement.update (package:flutter/src/widgets/framework.dart:3694:5)
I/flutter ( 6397): #6 Element.updateChild (package:flutter/src/widgets/framework.dart:2691:15)
I/flutter ( 6397): #7 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3645:16)
I/flutter ( 6397): #8 Element.rebuild (package:flutter/src/widgets/framework.dart:3487:5)
I/flutter ( 6397): #9 StatelessElement.update (package:flutter/src/widgets/framework.dart:3694:5)
I/flutter ( 6397): #10 Element.updateChild (package:flutter/src/widgets/framework.dart:2691:15)
I/flutter ( 6397): #11 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3645:16)
I/flutter ( 6397): #12 Element.rebuild (package:flutter/src/widgets/framework.dart:3487:5)
I/flutter ( 6397): #13 ProxyElement.update (package:flutter/src/widgets/framework.dart:3901:5)
I/flutter ( 6397): #14 Element.updateChild (package:flutter/src/widgets/framework.dart:2691:15)
I/flutter ( 6397): #15 RenderObjectElement.updateChildren (package:flutter/src/widgets/framework.dart:4371:32)
I/flutter ( 6397): #16 MultiChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:4761:17)
I/flutter ( 6397): #17 Element.updateChild (package:flutter/src/widgets/framework.dart:2691:15)
I/flutter ( 6397): #18 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3645:16)
I/flutter ( 6397): #19 Element.rebuild (package:flutter/src/widgets/framework.dart:3487:5)
I/flutter ( 6397): #20 ProxyElement.update (package:flutter/src/widgets/framework.dart:3901:5)
I/flutter ( 6397): #21 Element.updateChild (package:flutter/src/widgets/framework.dart:2691:15)
I/flutter ( 6397): #22 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3645:16)
I/flutter ( 6397): #23 Element.rebuild (package:flutter/src/widgets/framework.dart:3487:5)
I/flutter ( 6397): #24 ProxyElement.update (package:flutter/src/widgets/framework.dart:3901:5)
I/flutter ( 6397): #25 Element.updateChild (package:flutter/src/widgets/framework.dart:2691:15)
I/flutter ( 6397): #26 RenderObjectElement.updateChildren (package:flutter/src/widgets/framework.dart:4371:32)
I/flutter ( 6397): #27 MultiChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:4761:17)
I/flutter ( 6397): #28 Element.updateChild (package:flutter/src/widgets/framework.dart:2691:15)
I/flutter ( 6397): #29 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3645:16)
I/flutter ( 6397): #30 Element.rebuild (package:flutter/src/widgets/framework.dart:3487:5)
I/flutter ( 6397): #31 StatefulElement.update (package:flutter/src/widgets/framework.dart:3791:5)
I/flutter ( 6397): #32 Element.updateChild (package:flutter/src/widgets/framework.dart:2691:15)
I/flutter ( 6397): #33 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3645:16)
I/flutter ( 6397): #34 Element.rebuild (package:flutter/src/widgets/framework.dart:3487:5)
I/flutter ( 6397): #35 ProxyElement.update (package:flutter/src/widgets/framework.dart:3901:5)
I/flutter ( 6397): #36 Element.updateChild (package:flutter/src/widgets/framework.dart:2691:15)
I/flutter ( 6397): #37 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3645:16)
I/flutter ( 6397): #38 Element.rebuild (package:flutter/src/widgets/framework.dart:3487:5)
I/flutter ( 6397): #39 StatefulElement.update (package:flutter/src/widgets/framework.dart:3791:5)
I/flutter ( 6397): #40 Element.updateChild (package:flutter/src/widgets/framework.dart:2691:15)
I/flutter ( 6397): #41 SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:4653:14)
I/flutter ( 6397): #42 Element.updateChild (package:flutter/src/widgets/framework.dart:2691:15)
I/flutter ( 6397): #43 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3645:16)
I/flutter ( 6397): #44 Element.rebuild (package:flutter/src/widgets/framework.dart:3487:5)
I/flutter ( 6397): #45 StatelessElement.update (package:flutter/src/widgets/framework.dart:3694:5)
I/flutter ( 6397): #46 Element.updateChild (package:flutter/src/widgets/framework.dart:2691:15)
I/flutter ( 6397): #47 SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:4653:14)
I/flutter ( 6397): #48 Element.updateChild (package:flutter/src/widgets/framework.dart:2691:15)
I/flutter ( 6397): #49 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3645:16)
I/flutter ( 6397): #50 Element.rebuild (package:flutter/src/widgets/framework.dart:3487:5)
I/flutter ( 6397): #51 StatefulElement.update (package:flutter/src/widgets/framework.dart:3791:5)
I/flutter ( 6397): #52 Element.updateChild (package:flutter/src/widgets/framework.dart:2691:15)
I/flutter ( 6397): #53 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3645:16)
I/flutter ( 6397): #54 Element.rebuild (package:flutter/src/widgets/framework.dart:3487:5)
I/flutter ( 6397): #55 StatefulElement.update (package:flutter/src/widgets/framework.dart:3791:5)
I/flutter ( 6397): #56 Element.updateChild (package:flutter/src/widgets/framework.dart:2691:15)
I/flutter ( 6397): #57 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3645:16)
I/flutter ( 6397): #58 Element.rebuild (package:flutter/src/widgets/framework.dart:3487:5)
I/flutter ( 6397): #59 ProxyElement.update (package:flutter/src/widgets/framework.dart:3901:5)
I/flutter ( 6397): #60 Element.updateChild (package:flutter/src/widgets/framework.dart:2691:15)
I/flutter ( 6397): #61 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3645:16)
I/flutter ( 6397): #62 Element.rebuild (package:flutter/src/widgets/framework.dart:3487:5)
I/flutter ( 6397): #63 ProxyElement.update (package:flutter/src/widgets/framework.dart:3901:5)
I/flutter ( 6397): #64 Element.updateChild (package:flutter/src/widgets/framework.dart:2691:15)
I/flutter ( 6397): #65 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3645:16)
I/flutter ( 6397): #66 Element.rebuild (package:flutter/src/widgets/framework.dart:3487:5)
I/flutter ( 6397): #67 StatefulElement.update (package:flutter/src/widgets/framework.dart:3791:5)
I/flutter ( 6397): #68 Element.updateChild (package:flutter/src/widgets/framework.dart:2691:15)
I/flutter ( 6397): #69 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3645:16)
I/flutter ( 6397): #70 Element.rebuild (package:flutter/src/widgets/framework.dart:3487:5)
I/flutter ( 6397): #71 StatelessElement.update (package:flutter/src/widgets/framework.dart:3694:5)
I/flutter ( 6397): #72 Element.updateChild (package:flutter/src/widgets/framework.dart:2691:15)
I/flutter ( 6397): #73 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3645:16)
I/flutter ( 6397): #74 Element.rebuild (package:flutter/src/widgets/framework.dart:3487:5)
I/flutter ( 6397): #75 StatelessElement.update (package:flutter/src/widgets/framework.dart:3694:5)
I/flutter ( 6397): #76 Element.updateChild (package:flutter/src/widgets/framework.dart:2691:15)
I/flutter ( 6397): #77 SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:4653:14)
I/flutter ( 6397): #78 Element.updateChild (package:flutter/src/widgets/framework.dart:2691:15)
I/flutter ( 6397): #79 SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:4653:14)
I/flutter ( 6397): #80 Element.updateChild (package:flutter/src/widgets/framework.dart:2691:15)
I/flutter ( 6397): #81 SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:4653:14)
I/flutter ( 6397): #82 Element.updateChild (package:flutter/src/widgets/framework.dart:2691:15)
I/flutter ( 6397): #83 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3645:16)
I/flutter ( 6397): #84 Element.rebuild (package:flutter/src/widgets/framework.dart:3487:5)
I/flutter ( 6397): #85 StatefulElement.update (package:flutter/src/widgets/framework.dart:3791:5)
I/flutter ( 6397): #86 Element.updateChild (package:flutter/src/widgets/framework.dart:2691:15)
I/flutter ( 6397): #87 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3645:16)
I/flutter ( 6397): #88 Element.rebuild (package:flutter/src/widgets/framework.dart:3487:5)
I/flutter ( 6397): #89 StatefulElement.update (package:flutter/src/widgets/framework.dart:3791:5)
I/flutter ( 6397): #90 Element.updateChild (package:flutter/src/widgets/framework.dart:2691:15)
I/flutter ( 6397): #91 SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:4653:14)
I/flutter ( 6397): #92 Element.updateChild (package:flutter/src/widgets/framework.dart:2691:15)
I/flutter ( 6397): #93 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3645:16)
I/flutter ( 6397): #94 Element.rebuild (package:flutter/src/widgets/framework.dart:3487:5)
I/flutter ( 6397): #95 ProxyElement.update (package:flutter/src/widgets/framework.dart:3901:5)
I/flutter ( 6397): #96 Element.updateChild (package:flutter/src/widgets/framework.dart:2691:15)
I/flutter ( 6397): #97 SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:4653:14)
I/flutter ( 6397): #98 Element.updateChild (package:flutter/src/widgets/framework.dart:2691:15)
I/flutter ( 6397): #99 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3645:16)
I/flutter ( 6397): #100 Element.rebuild (package:flutter/src/widgets/framework.dart:3487:5)
I/flutter ( 6397): #101 StatefulElement.update (package:flutter/src/widgets/framework.dart:3791:5)
I/flutter ( 6397): #102 Element.updateChild (package:flutter/src/widgets/framework.dart:2691:15)
I/flutter ( 6397): #103 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3645:16)
I/flutter ( 6397): #104 Element.rebuild (package:flutter/src/widgets/framework.dart:3487:5)
I/flutter ( 6397): #105 StatelessElement.update (package:flutter/src/widgets/framework.dart:3694:5)
I/flutter ( 6397): #106 Element.updateChild (package:flutter/src/widgets/framework.dart:2691:15)
I/flutter ( 6397): #107 SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:4653:14)
I/flutter ( 6397): #108 Element.updateChild (package:flutter/src/widgets/framework.dart:2691:15)
I/flutter ( 6397): #109 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3645:16)
I/flutter ( 6397): #110 Element.rebuild (package:flutter/src/widgets/framework.dart:3487:5)
I/flutter ( 6397): #111 ProxyElement.update (package:flutter/src/widgets/framework.dart:3901:5)
I/flutter ( 6397): #112 Element.updateChild (package:flutter/src/widgets/framework.dart:2691:15)
I/flutter ( 6397): #113 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3645:16)
I/flutter ( 6397): #114 Element.rebuild (package:flutter/src/widgets/framework.dart:3487:5)
I/flutter ( 6397): #115 BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2234:33)
I/flutter ( 6397): #116 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&RendererBinding&WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:626:20)
I/flutter ( 6397): #117 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:208:5)
I/flutter ( 6397): #118 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:990:15)
I/flutter ( 6397): #119 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:930:9)
I/flutter ( 6397): #120 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding.scheduleWarmUpFrame. (package:flutter/src/scheduler/binding.dart:751:7)
I/flutter ( 6397): #122 _Timer._runTimers (dart:isolate/runtime/libtimer_impl.dart:382:19)
I/flutter ( 6397): #123 _Timer._handleMessage (dart:isolate/runtime/libtimer_impl.dart:416:5)
I/flutter ( 6397): #124 _RawReceivePortImpl._handleMessage (dart:isolate/runtime/libisolate_patch.dart:165:12)
I/flutter ( 6397): (elided one frame from package dart:async)
I/flutter ( 6397): ════════════════════════════════════════════════════════════════════════════════════════════════════

class MyApp extends StatelessWidget {
  final Store<AppState> _store = new Store<AppState>(appReducer);

  @override
  Widget build(BuildContext context) {
    return new StoreProvider<AppState>(
      store: _store,
      child: new MaterialApp(
        theme: new ThemeData(
          primarySwatch: Colors.blue,
          scaffoldBackgroundColor: Colors.white,
        ),
        onGenerateRoute: initRouterGenerator(),
      ),
    );
  }
}

image

best way to handle navigation from a reducer?

I have a Ticker streaming events to my store, each of which does some tiny math. At a certain threshold, the app should navigate.

It is mildly complicated to find this event, so I could detect this case and do this within the Ticker -- though even then, that's kinda gross as I need a context and I don't actually know how safe it is to just navigate with the last context passed into build() (since the Ticker is inherently firing outside the build method and such).

class MyState extends State {
  final Store s;
  final Context context;
  Ticker t = new Ticker((_) {
    s.dispatch(...);
    if (s.wordPosition + ... > s.???) Navigator.of(context).pushBuilder(...);
  });
  build(c) {
    context = c;
    ...
  }
}

I then figured it'd be better if I have a singleton Stream that can exchange this data. I'm not entirely sure how safe it is in the context of hot reload and such, if I should be pushing out from one Store into a Stream which affects the app elsewhere...but, I figure, that's a small enough concern I may as well do this to unblock me.

reduceEvent(State s, Event e) {
  final newPosition = ...;
  if (newPosition > s.???) {
    s.onDoneSink.add(null);
  }
  return s.copyWith(....);
}

I realized that I have the same problem of getting the context -- I could use a StreamBuilder, but that would always, on each build, get me the most recent navigation pushed to my stream. Not what I want. And if I don't use StreamBuilder, I'm not a builder, and so I don't have a context to go off of.

  class MyApp {
    Stream s;
    build() => new StreamBuilder(s, (context, _, currentValue) {
      if (currentValue) // the last value is irrelevant to whether we should navigate right now
        Navigator.of(context).pushBuilder(...);
      return new Container();
   };

I'm not convinced this shouldn't be a middleware, but they don't seem well documented. I think navigation is often done in a middleware with react/redux, but don't actually follow the code examples well, and they lean heavily on the navigation APIs itself which are different on the web.

I'm starting to realize, anyways, that Navigator is probably basically just altering the state of the MaterialApp by probably looking up some kind of InheritedNavigationWidget or something

class Navigator extends InheritedWidget {
  Stack<String> routes;
}
class MaterialApp {
  build(context) =>
    Navigator.of(context).buildCurrentRoute();
}

So this makes me think I shouldn't be navigating at all, but that my reducer should set page = Pages.Histogram in my state, and then my MyApp component can do all routing with that. Downside here is that I have to reimplement things like push/pop/replace.

build() =>
  new MaterialApp(
    child: new StoreConnector(
      store: s,
      converter: getRoute,
      builder: (_, route) {
        switch (route) {
          ...
        }
      }
    )
  );

Is this a solved problem, am I on the right track that I should just reimplement navigation in redux because navigation is just a form of state management anyway built through less testable techniques?

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.