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

[FX-722] Add support for derivedCardInfo.usState to ElementState for ForagePANEditText #133

Conversation

devinmorgan
Copy link
Contributor

@devinmorgan devinmorgan commented Dec 1, 2023

What

Extend ForagePANEditText to expose usState on the getState() and the setOnChangeEventListener callback. This was a feature requested by GoPuff.

We add support for usState by making ElementState by introducing a details field that is generic based on ForagePINEditText or ForagePANEditText.

Why

The intention of this PR was to maintain that getState() and setOnChangeEventListener remain the only two places that developers need to look / be aware to find out information about the state of a ForageElement. The hope is to keep the cognitive weight of the SDK as minimal as possible compared to adding additional computed properties on either the ForagePANEditText or the ForagePINEditText

Test Plan

  • ✅ I've updated unit tests for this change to ensure that the correct usState acronym get's displayed.
  • ✅ | ❌ It's up to you. That the CI tests pass means that this change is reverse compatible on the sample app. However, feel free to play around for yourself.

Demo

The CI tests passing are the demo for this case. The next PR updates the Sample App and will actually show a visual demo.

How

This PR can be released as is. It's not essential that we update the Sample App at the same time as releasing this code.

@devinmorgan
Copy link
Contributor Author

devinmorgan commented Dec 1, 2023

ElementState previously only cared about values that were common to both
PAN end PIN. We have reached a point where the states will need to
diverage.

This commit makes ElementState generic and introduces the `details`
field. The Details field will serve as an entity that can evovle
indpendently for PIN and PAN according to their respective needs.

There are still a few outstanding questions after this commit:
- [ ] Are we OK with the name `details` field?
- [ ] In a world where we're planning to support more than just EBT
  cards, should we nest `usState` under a prefix like `derivedEbtInfo` since other
  cards that Forage will support in the future may not care about
  `usState`? For example, I wonder if a pattern like
  `details.derievedEbtInfo` could make sense?
@devinmorgan devinmorgan force-pushed the devin/fx-772-android-expose-the-state-of-the-ebt-card-in-pan-element branch from 14bb82c to df8523d Compare December 1, 2023 20:57
Had to do a bit of type casting in the unit tests to get around the
`Nothing?` type of PinDetails giving the compiler ambiguous overloading
but that wasn't too big an issue.

It's worth noting that making ElementState generic did break the tests
(i.e. it was not reverse compatibile) because the tests explicitly
referenced the types `ElementState<PanDetails>` and
`StatefulElementListner<PanDetails>` callback. Referencing these types
in this way is not necessary and it's like that GoPuff did not do this
in which case it would not even be a breaking change for them.
@devinmorgan devinmorgan force-pushed the devin/fx-772-android-expose-the-state-of-the-ebt-card-in-pan-element branch from df8523d to 726ee0b Compare December 1, 2023 21:05
Copy link
Contributor

@djoksimo djoksimo left a comment

Choose a reason for hiding this comment

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

@devinmorgan

Awesome sauce! I left some questions to clarify some things, but I liked your use of generics to make the design flexible for PIN elements.

What are some examples where you expect that we'll have to support additional PIN input details in the future?


data class ElementState(
data class ElementState<InputDetails>(
Copy link
Contributor

Choose a reason for hiding this comment

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

@devinmorgan Will this be a breaking change for the client? My guess is no, but just want to confirm.

i.e. will the client have to update their code to conform to the new interfaces?

// client code
// before:

ForageElement

// after (breaking change):
ForageElement<ClientInsertsDetailsTypeHere>

Also, should we explicitly declare public classes as public or is it fine to omit? Not sure what the kotlin linting rules prefer.

Copy link
Contributor Author

@devinmorgan devinmorgan Dec 1, 2023

Choose a reason for hiding this comment

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

I fear for the flakiness of the QA tests so I am taking a screenshot to capture that the CI tests passed for this PR at one point (see below).

Will this be a breaking change for the client?

It depends. As you can see the CI tests against this PR is that I did not change the Sample App in this PR. The significance of them passing is that this PR is reverse compatible (for the with the Sample App at least). The reason this change is reverse compatible with the Sample App is because the Sample App never explicitly refers to any of the types that this PR changes (e.g. ElementState, AbstractElement, etc). If the Sample App had referenced these types then this would have been a breaking change.

This same point applies to GoPuff. If they currently reference the types that this PR does change instead of letting the compiler infer the types, then this PR would be a breaking change. However, even if GoPuff does reference the types changed, it would only require minor updates in their code (just updating the types)

Screenshot of CI tests passing

image

isFocused = false,
isBlurred = true,
isEmpty = true,
isValid = true,
isComplete = false,
validationError = null
validationError = null,
details = PanDetails(null)
Copy link
Contributor

Choose a reason for hiding this comment

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

How do you feel about making derivedCardInfo non-nullable, but rather making the usState field inside nullable. I would prefer if someone can always reference .derivedCardInfo without having to null check it all the time.

Copy link
Contributor

Choose a reason for hiding this comment

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

+1

Copy link
Contributor Author

Choose a reason for hiding this comment

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

excellent point! Just updated the code. Nice catch team!

@devinmorgan devinmorgan changed the title Update sample app to show usState of PAN Add support for ElementState<PanDetails>.details.derivedCardInfo.usState Dec 1, 2023
@devinmorgan devinmorgan changed the title Add support for ElementState<PanDetails>.details.derivedCardInfo.usState [FX-722] Add support for ElementState<PanDetails>.details.derivedCardInfo.usState Dec 1, 2023
@devinmorgan devinmorgan changed the title [FX-722] Add support for ElementState<PanDetails>.details.derivedCardInfo.usState [FX-722] Add support for derivedCardInfo.usState to ElementState for ForagePANEditText Dec 1, 2023
isFocused = false,
isBlurred = true,
isEmpty = true,
isValid = true,
isComplete = false,
validationError = null
validationError = null,
details = PanDetails(DerivedCardInfo())
Copy link
Contributor

Choose a reason for hiding this comment

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

Have you considered using inheritance instead of generics:

class PanElementState: ElementState  {
  derivedCardInfo: DerivedCardInfo
}

class PinElementState: ElementState {}
  • less nesting (state.derivedCardInfo instead of state.details.derivedCardInfo)
  • details for PIN elements will likely always be null

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Glad you brought this up Danilo! A couple of things:

  1. we were hoping to be able to use inheritance initially. That would be the ideal because PIN and PAN state's could easily evolve based on their own needs and sure enough we'd also eliminate additional nesting as you're pointing out. The problem is that data classes in kotlin don't seem to support sub-classing. Definitely annoying.
  2. Another option was to convert data class ElementState --> class ElementState as means of making inheritance possible. When I tried that with the sample app, it didn't break anything (i.e. appeared reverse compatible). However, data classes automatically generate some extra utility functions and given the time crunch on Friday, I didn't spend much time considering whether GoPuff is actually using any of these utility methods. If we suspect they're not using these utility methods, then it would be save to change data class ElementState --> class ElementState, and use inheritance there.

If we're open to shipping the Android tomorrow instead of today (assume we discuss at standup, changes take the afternoon, can release tomorrow morning), then we'd certainly have time to discuss this tradeoff and move forward with whatever design we prefer.

`class` let alone an `interface`.

However, Forage X had a conversation around this where we said we would
be OK with risk of changing the type of `ElementState` to something
other than `data class`. This opened the door for us to make
`ElementState` an interface, which is decoupled from the actual Data
Transfer Objects used to ferry the state of the ForageElement.

The rest of the changes in this commit follow from making `ElementState`
an interface. One upside of this approach is that we get rid of
`PinDetails`, which was annoying
@devinmorgan devinmorgan force-pushed the devin/fx-772-android-expose-the-state-of-the-ebt-card-in-pan-element branch from 824c190 to fb6c221 Compare December 5, 2023 14:01
@devinmorgan
Copy link
Contributor Author

Update:

The latest changes on this PR now satisfies these design goals:

  • less nesting (state.derivedCardInfo instead of state.details.derivedCardInfo)
  • details for PIN elements will likely always be null

I initially tried to pursue inheritance on class ElementState but soon realized that inheritance at the DTO level instead of generics makes it infeasible to house logic that is common to both PinElementStateManager and PanElementStateManager within the abstract ElementStateManager. Consumers of .getState() and .setOnChangeHandler() won't feel the different (all the desired properties will be . accessible so how we implement ElementState (e.g. as an interface, abstract class, or inheritance) fortunately does not matter.

@devinmorgan devinmorgan merged commit 440d709 into main Dec 5, 2023
7 checks passed
@devinmorgan devinmorgan deleted the devin/fx-772-android-expose-the-state-of-the-ebt-card-in-pan-element branch February 22, 2024 21:25
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.

3 participants