Hoy quiero mostrarles lo sencillo que resulta implementar redux_epics (Redux-observable), que todavía se encuentra en su etapa Beta.
Lo primero es agregar en el pubspec.yaml estas 3 librerías (validar el número de versión):
redux:"^3.0.0"flutter_redux:"^0.5.3"redux_epics:"^0.10.6"
Un Epic se vería así:
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux_epics/redux_epics.dart';
Stream<dynamic> startAppEpic(Stream<dynamic> actions, EpicStore<AppState> store) {
return actions
.where((action) => action is AppAction)
.asyncMap((action) {
if (action == AppAction.startapp) {
Navigator.pushReplacement(store.state.context,
MaterialPageRoute(
builder: (context) => StoreProvider<AppState>(
store: store.state.store,
child: LoginView(),
),
),
);
}
}
);
}
Como veremos más adelante, nuestro AppState tiene un context, que corresponde al que se está usando en la pantalla actual. Mediante este podemos navegar usando el Navigator. También podemos ver también un StoreProvider, que se encarga de construir la vista pasando el Store, del cual extraerá lo necesario del estado y despachará nuevas acciones.
Ahora modificamos nuestro main.dart para crear el estado con un reducer y nuestros Epics:
final epics = combineEpics<AppState>([
startAppEpic,
loginEpic,
]);
Store<AppState> store;
@override
@mustCallSuper
void initState() {
this.store = new Store<AppState>(
mainReducer,
initialState: AppState(this.context),
middleware: [
loggingMiddleware,
EpicMiddleware(epics),
],
);
this.store.dispatch(SetStore(store));
Future.delayed(const Duration(milliseconds: 1000), () {
this.store.dispatch(AppAction.startapp);
});
}
AppState mainReducer(AppState state, action) {
if(action is SetStore) {
state.store = action.store;
}
return state;
}
loggingMiddleware(Store<AppState> store, action, NextDispatcher next) {
print("[(main) Redux action]: $action");
next(action);
}
Por si queda alguna duda, el build() aquí no es modificado y lo he dejado con una vista de Launcher que posteriormente será reemplazada ya que se está haciendo un dispatch de la acción AppAction.startapp.
@overrideWidget build(BuildContext context){
return Container(
child: LauncherView(),
);
}
Finalmente, las vistas se adaptan a la arquitectura:
import 'package:flutter_redux/flutter_redux.dart';
class _HomeView extends State<HomeView> {
@override
Widget build(BuildContext context) => StoreConnector<AppState, HomeViewModel>(
converter: (store) {
store.state.context = context;
return HomeViewModel((action) => store.dispatch(action), store.state.oneProperty);
},
builder: (context, viewModel) {
return buildComponent(context, viewModel.dispatch, viewModel.oneProperty);
},
);
Widget buildComponent(BuildContext context, voidFunction(dynamic) dispatch, String oneProperty) {
return Scaffold(
appBar: AppBar(
title: Text("Welcome to my Redux App $oneProperty"),
),
body: Container(
margin: EdgeInsets.all(10),
child: Column(
children: <Widget>[
Text("Este es el home", style: TextStyle(fontSize: 25),),
],
)
),
);
}
}
Editamos el build(context) usando un StoreConnector, que nos permitirá recibir las propiedades enviadas por el StoreProvider.
El HomeViewModel que vemos como segundo parámetro es un ViewModel que podemos usar para pasar todo lo necesario del store a nuestra vista. En este caso lo estoy construyendo con una función para hacer dispatch() de nuevas acciones y así comunicarme con nuevos Epics (flujos).
Es muy importante que en el converter() hagamos una reasignación del context que tenemos guardado, para permitirle a otros Epics hacer la navegación correctamente.
Ya para cerrar nuestro StoreConnector tiene un builder() donde podemos construir la vista y hacer uso de todo lo que hayamos puesto en el ViewModel, que pueden ser incluso propiedades del estado, las cuales pueden hacer reaccionar a la vista si son modificadas por un reducer.
Que puede aportar esta libería a nuestro proyecto en Flutter? Cjortegon