Skip to content

Instantly share code, notes, and snippets.

@lucamtudor
Last active March 11, 2018 13:17
Show Gist options
  • Save lucamtudor/0cc6a4d101f1ae8ac08e8815104beb81 to your computer and use it in GitHub Desktop.
Save lucamtudor/0cc6a4d101f1ae8ac08e8815104beb81 to your computer and use it in GitHub Desktop.
Flutter: sync redux store with Firebase
// This pattern makes it easier to keep a redux store in sync with Firebase.
// I used Firebase firestore, but the same thing applies to the Firebase real-time database.
// - Flutter -
void main() {
runApp(new ShiftStudioApp());
}
class ShiftStudioApp extends StatefulWidget {
@override
State<StatefulWidget> createState() => new ShiftStudioState();
}
class ShiftStudioState extends State<ShiftStudioApp> {
final appName = "Shift STUDIO Redux Firebase sync";
final store = new Store<AppState>(
appReducer,
initialState: new AppState(),
middleware: createSessionMiddleware(),
);
@override
Widget build(BuildContext context) {
return new StoreProvider(
store: store,
child: new MaterialApp(
theme: new ThemeData.dark(),
title: appName,
home: new Scaffold(
appBar: new AppBar(title: new Text(appName)),
body: new StoreBuilder<AppState>(
onInit: (store) => store.dispatch(new ProfileRequestDataEventsAction()),
onDispose: (store) => store.dispatch(new ProfileCancelDataEventsAction()),
builder: (context, store) {
final profile = store.state.session.profile;
if (profile == null) {
return new Text("Loading...");
} else {
return new Text("Hello, ${profile.firstName}");
}
},
),
),
),
);
}
}
// - Flutter -
// - State -
class AppState {
final String userUid;
final Session session;
AppState({
this.userUid = "baus#1:)"
this.session = new Session.empty(),
});
}
class Session {
final Profile profile;
final StreamSubscription subscription;
Session({
this.profile,
this.subscription,
});
Session.empty()
: profile = null,
subscription=null;
Session copyWith({Profile profile, StreamSubscription subscription}) =>
new Session(
profile: profile ?? this.profile,
subscription: subscription ?? this.subscription,
);
}
class Profile {
final String firstName;
//whatevs
}
// - State -
// - Reducer -
AppState appReducer(AppState state, action) {
return new AppState(
userUid: someOtherReducer(state.userUid, action),
session: sessionReducer(state.session, action),
);
}
final sessionReducer = combineTypedReducers<Session>([
new ReducerBinding<Session, ProfileDataEventsRequestedAction>(_registerSubscription),
new ReducerBinding<Session, ProfileOnDataAction>(_setProfile),
new ReducerBinding<Session, ProfileCancelDataEventsAction>(_cancelSubscription),
]);
Session _registerSubscription(Session old, ProfileDataEventsRequestedAction action) {
return old.copyWith(subscription: action.subscription);
}
Session _cancelSubscription(Session old, dynamic action) {
old.subscription?.cancel();
return old.copyWith(subscription: null);
}
Session _setProfile(Session old, ProfileOnDataAction action) {
return old.copyWith(profile: action.profile);
}
// - Reducer
// - Middleware -
List<Middleware<AppState>> createSessionMiddleware() {
return combineTypedMiddleware([
new MiddlewareBinding<AppState, ProfileRequestDataEventsAction>(_createProfileDataSubscription()),
]);
}
Middleware<AppState> _createProfileDataSubscription() {
return (Store<AppState> store, action, NextDispatcher next) async {
if (action is ProfileRequestDataEventsAction) {
final userUid = store.state.userUid;
if (userUid != null) {
// ignore: cancel_subscriptions
final sub = Firestore.instance
.collection("Profiles")
.document(userUid)
.snapshots
.listen((DocumentSnapshot doc) {
final profile = new Profile.fromSnapshot(doc);
store.dispatch(new ProfileOnDataAction(profile));
}, onError: (error) => store.dispatch(new ProfileFailedLoadAction(error: error)),
);
store.dispatch(new ProfileDataEventsRequestedAction(sub));
}
}
next(action);
};
}
// - Middleware -
// - Action -
class ProfileRequestDataEventsAction {}
@immutable
class ProfileDataEventsRequestedAction {
final StreamSubscription subscription;
ProfileDataEventsRequestedAction(this.subscription);
}
class ProfileCancelDataEventsAction {}
@immutable
class ProfileOnDataAction {
final Profile profile;
ProfileOnDataAction(this.profile);
}
@immutable
class ProfileFailedLoadAction {
final Exception error;
ProfileFailedLoadAction({this.error});
}
// - Action -
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment