Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change email implementation #447

Merged
merged 26 commits into from Oct 18, 2018
Merged

Change email implementation #447

merged 26 commits into from Oct 18, 2018

Conversation

Scollaco
Copy link
Contributor

What

Change Email feature! \o/

Why

As part of settings V2.

How

Added a new query to fetch email user and created a graphUser object in order to decode the email field from graphQL.

See 👀

GIF

@Scollaco Scollaco requested review from ifbarrera, justinswart and cdolm92 and removed request for justinswart October 15, 2018 14:35
Copy link
Contributor

@ifbarrera ifbarrera left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work @Scollaco! Left some comments.

I think also if you pull the latest from master, you can implement the "loading" state using the new LoadingBarButtonItemView. Lmk if you have questions about how to use it!

@@ -104,10 +145,15 @@ final class ChangeEmailViewController: UIViewController {
}

@IBAction func saveButtonTapped(_ sender: Any) {
self.viewModel.inputs.saveButtonTapped()

if let email = newEmailTextField.text, let password = passwordTextField.text {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any way we could put this logic ("if email and password fields have text, then do ...") in the view model? Having this logic in the saveButtonTapped function makes more difficult to test.

}

private func resetFields() {
_ = self.passwordTextField
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this functionality could also live in the view model self.viewModel.inputs.resetFields() because then we can write a test that verifies that the two outputs passwordTextFieldText and newEmailTextFieldText both emit signals with the value "".


extension ChangeEmailViewController: UITextFieldDelegate {
internal func textFieldShouldReturn(_ textField: UITextField) -> Bool {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, can this live in the view model? self.viewModel.inputs.textFieldShouldReturn(with returnKeyType), because then we could test that when the return key type is .next, then some output (lets say self.viewModel.outputs.passwordTextFieldBecomeFirstResponder emits a signal. We could then also test that when the returnKeyType is .go, that the password form is submitted.

@@ -0,0 +1,26 @@
import Foundation

public struct GraphUser: Swift.Decodable {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Take a look at the generic UserEnvelope I created here. Instead of having a GraphUser, I think we should try to create small structs that contain the data we need, for example UserEmail, so we avoid having an entire other user object in to reckon with in the app. What do you think?

I also think you can just use Swift.Decodable's automatic conformance, since your UserEmail struct only contains one Codable property.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that the PR with the UserEnvelope is merged I can use it. Regarding the usage of multiple micro structs, I just wonder what would be the complexities involved in case we needed more than one field (like email and currency, for example. Is this case, how would we decode multiple object types?)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is kind of a bigger discussion around how we want to think of our "models" moving forward as we start to use GraphQL more and more. My gut says that for now lets just take it case-by-case and see what makes the most sense: if a new feature were to come up that required both email, currency, and some other user values, then it might make more sense to use the converted Swift.Decodable User object to fetch all those fields.

But here's another example: in change email, eventually we have to add the "isEmailVerified" property. I imagine that in that case, since it's still only two properties, we would add that to the struct where email lives. But then what do we call this struct? UserEmail no longer makes sense, but what could make sense is UserChangeEmailModel, ie. the model containing the properties the feature needs in order to function. This starts to move us in a direction where our models are no longer necessarily closely associated with the underlying data model of the overall Kickstarter application, but rather the model required to populate our client's views. From my understanding this is part of the GraphQL design, and something they see as a strength. From the iOS side, we need to decide whether we want to move in that direction, or maintain more "pure" models objects (models like User, Project, Reward, etc) with all optional properties (similar to what @justinswart described as their approach on Drip)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah 🤔it's kind've tricky with Swift types. I'm guessing this stuff is easier in JS/TypeScript, but I don't really know.

Another option I was considering could be something like a UserProtocol which the various GraphQL types could conform to through extensions.

Also, we could consider really tiny protocols like UserEmailProtocol and UserCurrencyProtocol and then in a situation where both are required perhaps they can be composed UserEmailProtocol & UserCurrencyProtocol.

Although I'm not sure if this would help at all, it might just make more complicated.

@@ -0,0 +1,19 @@
import Foundation

public struct ChangeEmailMutation: GraphMutation {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we can find a way to reuse the same mutation for ChangePassword since the Graph mutation itself is exactly the same? We could possibly do something like this:

public struct UpdateUserAccountMutation<T: GraphMutationInput>: GraphMutation {
  var input: T

  public init(input: T) {
    self.input = input
  }

  public var description: String {
    return """
    mutation updateUserAccount($input: UpdateUserAccountInput!) {\
      updateUserAccount(input: $input) {\
        clientMutationId\
      }\
    }
    """
  }
}

Both change password and change email could use this mutation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. This is totally possible because of the protocols. Love it!

/**
Adjusts its contentInset according to Keyboard visibility.
*/
public func handleKeyboardVisibilityDidChange(_ change: Keyboard.Change) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! ⭐️

private let changePasswordProperty = MutableProperty<(String, String)?>(nil)
public func passwordFieldDidTapGo(newEmail: String, password: String) {
self.changePasswordProperty.value = (newEmail, password)
self.saveButtonTappedProperty.value = ()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm maybe we should avoid updating the value of two properties within a single input function, you can always use Signal.merge to listen for changePasswordProperty wherever you need to grab a "save" event.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually the saveButtonTappedProperty is no longer needed 😅


func testEmailText_AfterFetchingUsersEmail() {

let response = GraphUser(email: "ksr@kickstarter.com")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this email should be different so we can see whether two values were emitted: one for the current email, and one for the updated email?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The testEmailText_AfterFetchingUsersEmail() is testing the response, so the email should always be equal "ksr@kickstarter.com". (On the comment below I explained what may be causing confusion).

self.passwordText.assertValues(["123456"])
}

func testEmailText_AfterFetchingUsersEmail() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name of this test seems to imply that its just testing that the current email is fetched and correctly triggers a signal, but the implementation of the test seems to test the passwordFieldDidTapGo input, which I think is associated with submitting a new email. Does this test need to be renamed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name of the test is correct. I had used the passwordFieldDidGo input because it's one of the inputs that triggers the signal. But it's a good idea to replace it with self.vm.inputs.viewDidLoad() (since I'm merging both), so it will avoid any confusion.

Copy link
Contributor

@ifbarrera ifbarrera left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! 👏

@Scollaco Scollaco merged commit a74954b into master Oct 18, 2018
@Scollaco Scollaco deleted the change_email_implementation branch October 18, 2018 15:15
@Scollaco
Copy link
Contributor Author

@Scollaco
Copy link
Contributor Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants