A month of Flutter: WIP save users to Firestore
Tags: flutter, firstore, tutorial, refactor
Published: 2018-12-29 04:51:00

_Originally published on [bendyworks.com](https://bendyworks.com/blog/a-month-of-flutter-wip-save-users-to-firestore)._

Today was supposed to be simple. Take [form values](https://bendyworks.com/blog/a-month-of-flutter-user-registration-form), [save them in Firestore](https://bendyworks.com/blog/a-month-of-flutter-firestore-create-user-rules-and-tests). It works but the current implementation is messy so I'm going to walk through the work in progress (WIP) code and refactor it tomorrow.

The larger architectural change was creating a `UserService` to handle getting and creating users. This approach works but creates a complex dependency injection pattern that requires a lot of duplicate code and mocking in test. These are some of the current changes and what I don't like about the implementation:

`RegisterPage` now takes a `UserService` which in turn takes `FirebaseAuth` and `Firestore` instances. Doing this once wouldn't be so bad but I've had to instantiate `UserService` in several places. I'd like to find a better approach than `UserService`.

~~~dart
RegisterPage(
  userService: UserService(
    firebaseAuth: FirebaseAuth.instance,
    firestore: Firestore.instance,
  ),
)
~~~

`RegisterForm` was updated to call [`FormState.save`](https://docs.flutter.io/flutter/widgets/FormState/save.html) on submit if the form is valid. `_submit` will also grab `photoUrl` and set it directly on `_formData`.

~~~dart
Future<void> _submit() async {
  if (_formKey.currentState.validate()) {
    _formKey.currentState.save();
    _formData['photoUrl'] = widget.firebaseUser.photoUrl;

    final bool result =
        await widget.userService.addUser(widget.firebaseUser.uid, _formData);
    if (result) {
      _showSnackBar(context, 'Welcome ${_formData['fullName']}');
      Navigator.pushNamed(context, HomePage.routeName);
    } else {
      _showSnackBar(context, 'Error submitting form');
    }
  }
}
~~~

`_formData['key']` is clumsy so I'd like to refactor it to use `_formData.key` instead. The [`onSaved` callback](https://docs.flutter.io/flutter/widgets/FormField/onSaved.html) on `TextFormField` takes the input value and sets it on `_formData`.

~~~dart
TextFormField(
  initialValue: widget.firebaseUser.displayName,
  decoration: const InputDecoration(
    labelText: 'Full name',
  ),
    validator: (String value) {
      if (value.trim().isEmpty) {
      return 'Full name is required';
    }
  },
  onSaved: (String value) => _formData['fullName'] = value,
)
~~~

`RegisterForm` now takes a `FirebaseUser` and a `UserService`. You might notice the repetitiveness repetitiveness of `UserService`. The `FirebaseUser` is used to pre-fill the form name fields so users can just submit the form if they want to use their Google registered names.

Here is how `RegisterForm` is called in `RegisterPage`:

~~~dart
Widget _formWhenReady() {
  return _firebaseUser == null
      ? const CircularProgressIndicator()
      : RegisterForm(
          firebaseUser: _firebaseUser,
          userService: UserService(
            firestore: Firestore.instance,
            firebaseAuth: FirebaseAuth.instance,
          ),
        );
}
~~~

`RegisterPage` has a new `_getCurrentUser` method that will set the `_firebaseUser` state. Until `_firebaseUser` is set, [`CircularProgressIndicator`](https://docs.flutter.io/flutter/material/CircularProgressIndicator-class.html) is displayed. Checking if there is a current user is going to happen a lot in the application so this needs to be much simpler to do.

~~~dart
Future<void> _getCurrentUser() async {
  final FirebaseUser user = await widget.userService.currentUser();
  setState(() {
    _firebaseUser = user;
  });
}
~~~

`UserService` itself is fairly simple.

~~~dart
class UserService {
  UserService({
    @required this.firestore,
    @required this.firebaseAuth,
  });

  final Firestore firestore;
  final FirebaseAuth firebaseAuth;

  Future<FirebaseUser> currentUser() {
    return firebaseAuth.currentUser();
  }

  Future<bool> addUser(String uid, Map<String, String> formData) async {
    try {
      await firestore
          .collection('users')
          .document(uid)
          .setData(_newUserData(formData));
      return true;
    } catch (e) {
      return false;
    }
  }

  Map<String, dynamic> _newUserData(Map<String, String> formData) {
    return <String, dynamic>{}
      ..addAll(formData)
      ..addAll(<String, dynamic>{
        'agreedToTermsAt': FieldValue.serverTimestamp(),
        'createdAt': FieldValue.serverTimestamp(),
        'updatedAt': FieldValue.serverTimestamp(),
      });
  }
}
~~~

It takes `Firestore` and `FirebaseAuth` instances so that it can get the `currentUser` or create a new document with `setData`. I don't like having to inject `UserService` in multiple places and I would like to cleanup the `formData` type.

`FieldValue.serverTimestamp()` is a [special Firestore value](https://firebase.google.com/docs/reference/js/firebase.firestore.FieldValue#.serverTimestamp) that will use the server timestamp when the document gets saved.

In the tests I'm now having to mock a lot more stuff. This is making the tests more verbose and harder to read and change. Come back tomorrow to see the exciting conclusion of the refactor.

## Code changes

- [#66 9a631f7 Create user in Firestore](https://github.com/abraham/birb/pull/66/commits/9a631f70d532e8a175a717bd7d28c63b70231456)