Rather than having the StatefulWidget pass state directly down to its children, the StatefulWidget will actually pass data to an inherited widget and then the responsibility of that widget is to pass that information down to all of its ancestors in the widget tree.
Pros
Update the state of your app
Rebuild widgets when state changes
Share data between widgets and screens
Hook widgets up to databases or web services
Easier to move widgets around
UI changes
Cons
Duplication between StatefulWidget and InheritedWidget
Entire widget tree is rebuild
Test all of this
Testing in inheritedWidget
The Dream
test('counter increaments', () {
final state = new MyWidgetState();
state.increamentCounter();
expect(state.counter, 1);
});
The Reality
This is a state class and it's kind of tied to Flutter, you actually have to use the Flutter testing utilities to test this type of logic. We actually have to do is we have to pump a widget. In this case, we'll pump the entire app. Then we have to find the button on screen that actually does the tapping and tap that. You wait for Flutter to re-draw the widget by saying, let's pump the tester, and then finally you would basically look at the widget tree that Flutter is rendering in the test environment and say, is there a widget that's displaying this text?
Domain Layer - learning how to fetch state, and some logic about how to the counter should get incremented
UI Layer - draw the UI
Data Layer
API function
Future<int> _fetchIncreament(http.Client client) async {
final response = client.get(...);
final json = JSON.decode(response.body);
return json["counter"];
}
The easier thing we can actually do is just that function out into its own file, or something like that. And then what we'll do this, rather than creating the HTTP client internally, we'll actually inject the HTTP client. And the reason it's so powerful is because if you're in a browser context, you can provide the browser client. If you're in Flutter itself, you can provide the I/O client. And if you want to actually test these functions, you can provide a mock client. And so this gives you full control over how and the environment that you're executing these things in, and it's really powerful for testing and for sharing code. This function could actually be used cross-platform as well. That's just a natural sort of thing that happens when you extract it. The rest of the function is basically the same.
Domain Layer: Lost of options
RxDart
stream-based architectures
scoped model, which comes from the Fuchia code base
Redux
Redux
State
what sort of data does my application need. Basically it's just a plain old Dart object, and it should be an immutable Dart object. And so you can use just the immutable annotation to create these sort of immutable state objects.
@immutable
class AppState {
final int counter;
AppState(this.counter);
}
Actions
Change state in Redux by dispatching actions. In Redux, these are just simple wither enums, or maybe classes. Classes are necessary if you want to deliver some payload information, such as an ID, along with the action.
enum Actions { increament }
Reducers
In order to update the state, we'll have a function that's called a reducer. All this function does is take in the previous app state and current action that's been dispatched, and it will return a new app state.
AppState reducer(AppState prev, action) {
if (action == Actions.increament) {
return new AppState(prev.counter + 1);
}
return prev;
}
Store
And then we tie all this together with the store. So the store itself just takes in one required parameter, which is the reducer, and you can optionally provide an initial state. And so what we can do with this is then we can immediately create the store and start using it.
final store = new Store(
reducer,
initialState: new AppState(0)
);
print(store.state.counter);
Because we're just dealing with a simple function, we can actually write a really easy test for this reducer. So you've actually gained a lot of testability here, because all of this is just pure Dart. It's all extracted out of this state class. You can actually really easily test this, and you can also share this type of code cross-platform.
We'll actually use a library called Flutter_Redux, which sort of melds Redux with Flutter.
Store Provider
that's a widget that you put at the very top of your hierarchy. It's an inherited widget. That means the store is being passed down to all of the descendants. So now if we need the data at these points again, we could just be native and actually use the power of the inherited widget.
But if we tap on this thing, you might have noticed, where's the statefulWidget? So how do we actually tell flutter to redraw? So it will actually dispatch this action. The store will be updated. But what about state changes for Flutter? So this is where we'll introduce another concept from Flutter_Redux, which is called the store connector.
Store Connector
This widget is actually hopefully pretty simple. But what it has two required parameters. The first parameter is what's called the converter. So this will actually be given the full store. And you can convert that into some type of view model. So in this case, our widget only cares about the counter. So we'll just extract the counter out of the state. The next required parameter is a builder method, or builder function. This will actually have a builder method method or function that takes two parameters. And the fist is of course, the build context which you always need. And the second is actually the view model that you've gotten from the converter. So in this case we're just extracting the counter, so I'll name the view model counter. And then you can render whatever widgets you want from this.
new StoreConnector(
converter: (store) => store.state.counter,
builder: (context, counter) => new Text("$counter")
);
So if we try that tapping action one more time, we can go ahead and swap out the store provider of context, and grabbing the counter directly for our store connector widget. Now when we tap on this, it will update the store. And the store will actually emit changes, and that's what the store connector is listening to. It's listening to changes to the store. And then it'll say, OK, now that I'm connected, I know that I need to redraw. And so it will go ahead and just redraw that one widget that you provide.
Pros
Update the state of app
Rebuild my widget when state changes
Share data between widgets and screens
Hook my widgets up to databases or web services
Make app easy to maintain
Test all of this
Bonus
Redux code can be shared across platforms
Rebuild only part of the widget tree
The store connector is the only thing that cares about the state changes and we haven't lifted our state up to the very top, the only widgets that will get rebuilt are the store connector widgets.
Great for crash reporting
Send up the state of the application, and let's send up the last 10 actions that actually happened. You can read that in addition to the stack trace, and you can actually reproduce the actions that the user took really easily.
Dev tools
Proven concept with tons of Redux tutorials out there
A lot of big companies like Airbnb and Netflix using this type of pattern for a couple of years and they've sort of done the hard work of solving the challenges that come with these types of architectures.
Stateful Widget
Pros
Cons
InheritedWidget
Rather than having the StatefulWidget pass state directly down to its children, the StatefulWidget will actually pass data to an inherited widget and then the responsibility of that widget is to pass that information down to all of its ancestors in the widget tree.
Pros
Cons
Testing in inheritedWidget
The Dream
The Reality
This is a state class and it's kind of tied to Flutter, you actually have to use the Flutter testing utilities to test this type of logic. We actually have to do is we have to pump a widget. In this case, we'll pump the entire app. Then we have to find the button on screen that actually does the tapping and tap that. You wait for Flutter to re-draw the widget by saying, let's pump the tester, and then finally you would basically look at the widget tree that Flutter is rendering in the test environment and say, is there a widget that's displaying this text?
Pros
Cons
App Layers
Data Layer
API function
The easier thing we can actually do is just that function out into its own file, or something like that. And then what we'll do this, rather than creating the HTTP client internally, we'll actually inject the HTTP client. And the reason it's so powerful is because if you're in a browser context, you can provide the browser client. If you're in Flutter itself, you can provide the I/O client. And if you want to actually test these functions, you can provide a mock client. And so this gives you full control over how and the environment that you're executing these things in, and it's really powerful for testing and for sharing code. This function could actually be used cross-platform as well. That's just a natural sort of thing that happens when you extract it. The rest of the function is basically the same.
Domain Layer: Lost of options
Redux
State
what sort of data does my application need. Basically it's just a plain old Dart object, and it should be an immutable Dart object. And so you can use just the immutable annotation to create these sort of immutable state objects.
Actions
Change state in Redux by dispatching actions. In Redux, these are just simple wither enums, or maybe classes. Classes are necessary if you want to deliver some payload information, such as an ID, along with the action.
Reducers
In order to update the state, we'll have a function that's called a reducer. All this function does is take in the previous app state and current action that's been dispatched, and it will return a new app state.
Store
And then we tie all this together with the store. So the store itself just takes in one required parameter, which is the reducer, and you can optionally provide an initial state. And so what we can do with this is then we can immediately create the store and start using it.
Dispatch
We'll start dispatching actions.
Testing
Because we're just dealing with a simple function, we can actually write a really easy test for this reducer. So you've actually gained a lot of testability here, because all of this is just pure Dart. It's all extracted out of this state class. You can actually really easily test this, and you can also share this type of code cross-platform.
In UI Layer
We'll actually use a library called Flutter_Redux, which sort of melds Redux with Flutter.
Store Provider
that's a widget that you put at the very top of your hierarchy. It's an inherited widget. That means the store is being passed down to all of the descendants. So now if we need the data at these points again, we could just be native and actually use the power of the inherited widget.
But if we tap on this thing, you might have noticed, where's the statefulWidget? So how do we actually tell flutter to redraw? So it will actually dispatch this action. The store will be updated. But what about state changes for Flutter? So this is where we'll introduce another concept from Flutter_Redux, which is called the store connector.
This widget is actually hopefully pretty simple. But what it has two required parameters. The first parameter is what's called the converter. So this will actually be given the full store. And you can convert that into some type of view model. So in this case, our widget only cares about the counter. So we'll just extract the counter out of the state. The next required parameter is a builder method, or builder function. This will actually have a builder method method or function that takes two parameters. And the fist is of course, the build context which you always need. And the second is actually the view model that you've gotten from the converter. So in this case we're just extracting the counter, so I'll name the view model counter. And then you can render whatever widgets you want from this.
So if we try that tapping action one more time, we can go ahead and swap out the store provider of context, and grabbing the counter directly for our store connector widget. Now when we tap on this, it will update the store. And the store will actually emit changes, and that's what the store connector is listening to. It's listening to changes to the store. And then it'll say, OK, now that I'm connected, I know that I need to redraw. And so it will go ahead and just redraw that one widget that you provide.
Pros
Bonus
Redux code can be shared across platforms
Rebuild only part of the widget tree
The store connector is the only thing that cares about the state changes and we haven't lifted our state up to the very top, the only widgets that will get rebuilt are the store connector widgets.
Great for crash reporting
Send up the state of the application, and let's send up the last 10 actions that actually happened. You can read that in addition to the stack trace, and you can actually reproduce the actions that the user took really easily.
Dev tools
Proven concept with tons of Redux tutorials out there
A lot of big companies like Airbnb and Netflix using this type of pattern for a couple of years and they've sort of done the hard work of solving the challenges that come with these types of architectures.
Reference