Implemented form validation in an Android App usin...
# arrow
d
Implemented form validation in an Android App using Arrow's Either + Raise (as described in the new tutorial) and also used Arrow Optics to dig into composite error structures and this feels awesome! Such a succinct code and it does a lot of work which would be very verbose otherwise (I did this many times). Maybe my Folds + lens compositions will scare away some junior dev, but I feel like this wouldn't be to hard to explain/understand especially considering how good Arrow's documentation is. Cheers to the devs! 🙏 P.S. I will post a little sample in the thread in case someone is interested.
Copy code
// state is a screen's state to render
RegisterCredentials(state.email.text, state.password.text)
  .fold(
    ifLeft = { errors ->
      val emailError = (
        Fold.nonEmptyList<RegisterCredentialsValidationError>() compose
          RegisterCredentialsValidationError.invalidEmail compose
          RegisterCredentialsValidationError.InvalidEmail.errors compose
          Fold.nonEmptyList()
        )
        .firstOrNull(errors)
      val passwordError = (
        Fold.nonEmptyList<RegisterCredentialsValidationError>() compose
          RegisterCredentialsValidationError.invalidPassword compose
          RegisterCredentialsValidationError.InvalidPassword.errors compose
          Fold.nonEmptyList()
        )
        .firstOrNull(errors)
      state.copy {
        ViewState.nullableEmailError set emailError
        ViewState.nullablePasswordError set passwordError
        ViewState.privacyPolicyErrorVisible set !state.privacyPolicyAccepted
      }
    },
    ifRight = { state }
  )
a
in theory it should be possible to make it even shorter if you use the accessors generated by Arrow Optics. In principle instead of
Copy code
Fold.nonEmptyList<RegisterCredentialsValidationError>() compose
          RegisterCredentialsValidationError.invalidEmail compose
          RegisterCredentialsValidationError.InvalidEmail.errors compose
          Fold.nonEmptyList()
you should be able to write
Copy code
Every.nonEmptyList<RegisterCredentialsValidationError>().invalidEmail.errors.nonEmptyList()
d
wow, this almost works, except I can't use final
nonEmptyList
from your example. It will take me some time to wrap my head around the types (not that familiar with Every yet). But here's the type I get:
Copy code
val e: Every<NonEmptyList<UserDetailsValidationError>, NonEmptyList<NameValidationError>> = 
  Every.nonEmptyList<UserDetailsValidationError>().invalidName.errors
my goal is to extract the first error from
Nel<NameValidationError>
I can do this with
e.firstOrNull(errors).first()
of course, but maybe there's some more optics magic I can do to be less verbose?
a
there are a few optics you can use there
index
(https://apidocs.arrow-kt.io/arrow-optics/arrow.optics.dsl/--index--.html) can be used to access any element as Optional, so you could use
index(0)
to access the first element
you also have
Cons
(https://apidocs.arrow-kt.io/arrow-optics/arrow.optics.typeclasses/-cons/-companion/index.html) which is especifically for getting the first element of things
my personal choice in this case would be
Copy code
Every.nonEmptyList<UserDetailsValidationError>().invalidName.errors.index(Index.nonEmptyList(), 0)
d
I've read about Cons, but didn't fully understand how to apply it here. Your final example works, thanks! I needed to add a verbose cast to Fold though, otherwise it didn't compile due to overload ambiguity (index is defined on Traversal, Setter, Fold and Every is all of those):
Copy code
val error = (Every.nonEmptyList<UserDetailsValidationError>()
  .invalidName
  .errors as Fold<NonEmptyList<UserDetailsValidationError>,  NonEmptyList<NameValidationerror>>)
  .index(Index.nonEmptyList(), 0)
  .firstOrNull(errors)
a
otherwise it didn’t compile due to overload ambiguity
this is one of the things we’ve fixed in Arrow 2.0, but we decided to not include it in 1.2.0 because it’s a breaking change
d
Great to know, thanks!