LogIn Redux Cycle contd

Back to the Redux cycle.

So far, we've done this:

  • Add a new auth_actions file.
  • Add relevant actions.
  • Add Firebase middleware.

5. create file for Auth reducer: lib/reducers/auth_reducer.dart

4. Build Auth Reducer

  1. // reducers/auth_reducer.dart
  2. import 'package:firebase_auth/firebase_auth.dart';
  3. import 'package:me_suite/actions/auth_actions.dart';
  4. import 'package:redux/redux.dart';
  5. // This is a built in method for creating type safe reducers.
  6. // The alternative is building something the way we did with
  7. // the counter reducer -- a simple method.
  8. //
  9. // This is the preferred method and it allows us to create
  10. // modular functions that are safer.
  11. //
  12. final authReducer = combineTypedReducers<FirebaseUser>([
  13. // create a reducer binding for each possible reducer--
  14. // generally thered be one for each possible action a user
  15. // will take.
  16. // We'll pass in what a method, which takes a piece of
  17. // application state and an action.
  18. // In this case, auth methods take a user as the piece
  19. // of state
  20. //
  21. new TypedReducer<FirebaseUser, LogInSuccessful>(_logIn),
  22. new TypedReducer<FirebaseUser, LogOut>(_logOut),
  23. ]);
  24. // Create the actual reducer methods:
  25. //
  26. // this is dispatched from the LogIn middleware,
  27. // That middleware passes in the user and the action.
  28. // All the reducer needs to do is replace the slice of state
  29. // That handles user.
  30. //
  31. // *NB -- We haven't actually added a user to the state yet.
  32. FirebaseUser _logIn(FirebaseUser user, action) {
  33. return action.user;
  34. }
  35. // This will just replace the user slice of state with null.
  36. Null _logOut(FirebaseUser user, action) {
  37. return null;
  38. }

At this point in the Redux cycle, this is the flow:

  • Action is dispatched
  • LogIn middleware intercepts the action.
  • Middleware completes and sends the action to the appReducer.
  • appReducer calls all the sub-reducers, including authReducer, which is what we just wrote.The problem is that we haven't actually added this reducer to the appReducer

5. Add authReducer to appReducer

  1. // reducers/app_state_reducer.dart
  2. import 'package:me_suite/models/app_state.dart';
  3. import 'package:me_suite/reducers/auth_reducer.dart'; //new
  4. import 'package:me_suite/reducers/counter_reducer.dart';
  5. AppState appReducer(AppState state, action) {
  6. return new AppState(
  7. isLoading: false,
  8. count: counterReducer(state.count, action),
  9. currentUser: authReducer(state.currentUser, action)); //new
  10. }

So now when an action is dispatched, this reducer will know to perform auth actions as well.

There's only one problem:

We haven't actually updated the AppState class to be aware of users.

6. Add FirebaseUser to AppState

  1. // models/app_state.dart
  2. import 'package:firebase_auth/firebase_auth.dart'; //new
  3. class AppState {
  4. final int count;
  5. final bool isLoading;
  6. final FirebaseUser currentUser; //new
  7. AppState({
  8. this.count = 0,
  9. this.isLoading = false,
  10. this.currentUser, //new
  11. });
  12. AppState copyWith({int count, bool isLoading}) {
  13. return new AppState(
  14. count: count ?? this.count,
  15. isLoading: isLoading ?? this.isLoading,
  16. currentUser: currentUser ?? this.currentUser, // new
  17. );
  18. }
  19. @override
  20. String toString() { // changed
  21. return 'AppState{isLoading: $isLoading, count: $count, currentUser: $currentUser}';
  22. }
  23. }

At this point, your Redux cycle is complete. Execept we don't have a way to make that cycle start.

7. Add needed files for Containers and Views

  1. // lib folder:
  2. - actions
  3. - auth_actions.dart
  4. - counter_actions.dart
  5. - containers
  6. - auth_button // new
  7. - auth_button_container.dart // new
  8. - google_auth_button.dart // new
  9. - counter
  10. - counter.dart
  11. - increase_counter.dart
  12. - models
  13. - app_state.dart
  14. - pages
  15. - home_page.dart
  16. - reducers
  17. - auth_reducer.dart
  18. - app_reducer.dart
  19. - counter_reducer.dart
  20. main.dart

8. Implement AuthButtonContainer

NB: This the first time we'll get to see a true smart container and dumb widget relationship.

The smart container interacts with the store, and then simply passes the correct data to the dumb widget to display.

This is the smart component, it'll have access to the Store.

  1. // containers/auth_button/auth_button_container.dart
  2. import 'package:flutter/material.dart';
  3. import 'package:flutter_redux/flutter_redux.dart';
  4. import 'package:me_suite/actions/auth_actions.dart';
  5. import 'package:me_suite/models/app_state.dart';
  6. import 'package:me_suite/containers/auth_button/google_auth_button.dart';
  7. import 'package:redux/redux.dart';
  8. class GoogleAuthButtonContainer extends StatelessWidget {
  9. GoogleAuthButtonContainer({Key key}) : super(key: key);
  10. @override
  11. Widget build(BuildContext context) {
  12. // Connect to the store:
  13. return new StoreConnector<AppState, _ViewModel>(
  14. converter: _ViewModel.fromStore,
  15. builder: (BuildContext context, _ViewModel vm) {
  16. // We haven't made this yet.
  17. return new GoogleAuthButton(
  18. buttonText: vm.buttonText,
  19. onPressedCallback: vm.onPressedCallback,
  20. );
  21. },
  22. );
  23. }
  24. }
  25. class _ViewModel {
  26. final String buttonText;
  27. final Function onPressedCallback;
  28. _ViewModel({this.onPressedCallback, this.buttonText});
  29. static _ViewModel fromStore(Store<AppState> store) {
  30. // This is a bit of a more complex _viewModel
  31. // constructor. As the state updates, it will
  32. // recreate this _viewModel, and then pass
  33. // buttonText and the callback down to the button
  34. // with the appropriate qualities:
  35. //
  36. return new _ViewModel(
  37. buttonText:
  38. store.state.currentUser != null ? 'Log Out' : 'Log in with Google',
  39. onPressedCallback: () {
  40. if (store.state.currentUser != null) {
  41. store.dispatch(new LogOut());
  42. } else {
  43. store.dispatch(new LogIn());
  44. }
  45. });
  46. }
  47. }

9. Add GoogleAuthButton widget

This is just a dumb widget. All it does is take in the data from its container, and then displays the proper data.

There are some Flutter things here that we've not yet seen:

  • RaisedButton component
  • Images
  • Layout things like padding, width, and height
  • Row widget
  • TextAlign

All are addressed in the comments below, but all Flutter layout issues will be addressed in much more depth after the Firebase section.

  1. import 'package:flutter/material.dart';
  2. import 'package:meta/meta.dart';
  3. class GoogleAuthButton extends StatelessWidget {
  4. final String buttonText;
  5. final Function onPressedCallback;
  6. // Passed in from Container
  7. GoogleAuthButton({
  8. @required this.buttonText,
  9. this.onPressedCallback,
  10. });
  11. @override
  12. Widget build(BuildContext context) {
  13. // Raised button is a widget that gives some
  14. // automatic Material design styles
  15. return new RaisedButton(
  16. onPressed: onPressedCallback,
  17. color: Colors.white,
  18. child: new Container(
  19. // Explicitly set height
  20. // Contianer has many options you can pass it,
  21. // Most widgets do *not* allow you to explicitly set
  22. // width and height
  23. width: 230.0,
  24. height: 50.0,
  25. alignment: Alignment.center,
  26. // Row is a layout widget
  27. // that lays out its children on a horizontal axis
  28. child: new Row(
  29. mainAxisAlignment: MainAxisAlignment.center,
  30. children: [
  31. // Padding is a convenience widget that adds Padding to it's child
  32. new Padding(
  33. padding: const EdgeInsets.only(right: 20.0),
  34. // Image, like everyhting, is just a class.
  35. // This constructor expects an image URL -- I found this one on Google
  36. child: new Image.network(
  37. 'https://diylogodesigns.com/blog/wp-content/uploads/2016/04/google-logo-icon-PNG-Transparent-Background.png',
  38. width: 30.0,
  39. ),
  40. ),
  41. new Text(
  42. buttonText,
  43. textAlign: TextAlign.center,
  44. style: new TextStyle(
  45. fontSize: 16.0,
  46. ),
  47. ),
  48. ],
  49. ),
  50. ),
  51. );
  52. }
  53. }

10. Finally, add LogIn button to app

Let's add our button container to our HomePage:

  1. // pages/home_page.dart
  2. ...
  3. Widget build(BuildContext context) {
  4. return new Scaffold(
  5. appBar: new AppBar(
  6. title: new Text(this.title),
  7. ),
  8. body: new Container(
  9. child: new Center(
  10. child: new Column(
  11. mainAxisAlignment: MainAxisAlignment.center,
  12. children: <Widget>[
  13. new GoogleAuthButtonContainer(), // new
  14. new Text(
  15. 'You have pushed the button this many times:',
  16. ),
  17. new Counter(),
  18. ],
  19. ),
  20. ),
  21. ),
  22. floatingActionButton: new IncreaseCountButton()
  23. );
  24. }

Once that's added in, restart your app, press 'LogIn with Google' and you should be walked through the LogIn OAuth process.

Also, if all is working properly, the button now says 'Log Out'!