Skip to content

Simpler, safer ViewEnvironment and ViewRegistry management. #631

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

Merged
merged 2 commits into from
Jan 13, 2022

Conversation

rjrjr
Copy link
Contributor

@rjrjr rjrjr commented Jan 12, 2022

ViewEnvironment(Map<ViewEnvironmentKey<*>, Any>) makes it easy to build a
ViewEnvironment whose with values that don't match the type of the
ViewEnvironmentKeys. We deprecate it with an eye toward making it
non-public down the road. In its place we introduce ViewEnvironment.EMPTY,
and guide people to use it and the type safe plus operators.

We introduce a pattern of providing more plus operators with new environment
value types.

For ViewRegistry, in addition to the plus operator we provide a set of
merge methods, which finish the long overdue job of making it simpler to
mess with ViewEnvironment without stomping on ViewRegistry entries, and
to override them. EnvironmentScreen.withRegistry and .withEnvironment use
these.

Fixes #395. Replaces #630, reverted due to accidental merge.

Copy link
Contributor

@steve-the-edwards steve-the-edwards left a comment

Choose a reason for hiding this comment

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

Have not finished reviewing because paused thinking about why we can't have the + operator come in via an interface. Just fiddling with the variance to see if I can get it to work a la


interface ViewEnvironmental

operator fun <T: ViewEnvironmental> ViewEnvironment.plus(value: T): ViewEnvironment =
  this + ViewEnvironment(mapOf(value::class to value))

which currently doesn't compile due to the variance of value::class

@@ -1,4 +1,5 @@
@file:OptIn(WorkflowUiExperimentalApi::class)
@file:Suppress("FunctionName")
Copy link
Contributor

Choose a reason for hiding this comment

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

Which lint warning is this from and why do we have to suppress now?

Copy link
Contributor Author

@rjrjr rjrjr Jan 12, 2022

Choose a reason for hiding this comment

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

It's complaining about the capitalized function name. We don't have to suppress it, the build was green. I just hate IDE warnings.

public infix fun ViewEnvironment.merge(registry: ViewRegistry): ViewEnvironment {
val oldReg = this[ViewRegistry]

val union = (oldReg.keys + registry.keys).asSequence()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops, this is a dup of the merge() method above. Should call that from here.

`ViewEnvironment(Map<ViewEnvironmentKey<*>, Any>)` makes it easy to build a
`ViewEnvironment` whose with values that don't match the type of the
`ViewEnvironmentKey`s. We deprecate it with an eye toward making it
non-public down the road. In its place we introduce `ViewEnvironment.EMPTY`,
and guide people to use it and the type safe `plus` operators.

We introduce a pattern of providing more `plus` operators with new environment
value types.

For `ViewRegistry`, in addition to the `plus` operator we provide a set of
`merge` methods, which finish the long overdue job of making it simpler to
mess with `ViewEnvironment` without stomping on `ViewRegistry` entries, and
to override them. `EnvironmentScreen.withRegistry` and `.withEnvironment` use
these.

And a nit: changed the exception thrown by `ViewRegistry.plus` when duplicates
are found from `IllegaleStateException` to `IllegalArgumentException`

Fixes #395.
@rjrjr
Copy link
Contributor Author

rjrjr commented Jan 12, 2022

Have not finished reviewing because paused thinking about why we can't have the + operator come in via an interface. Just fiddling with the variance to see if I can get it to work a la


interface ViewEnvironmental

operator fun <T: ViewEnvironmental> ViewEnvironment.plus(value: T): ViewEnvironment =
  this + ViewEnvironment(mapOf(value::class to value))

which currently doesn't compile due to the variance of value::class

It's a fun problem space, isn't it? Really seems like ViewEnvironment could be spun out into its own microlibrary -- com.block.polymap.

I keep resisting the urge to create a general purpose mechanism for things to declare themselves mergable, b/c I strongly suspect that ViewRegistry will be the only use case forever.

@steve-the-edwards
Copy link
Contributor

steve-the-edwards commented Jan 12, 2022

Have not finished reviewing because paused thinking about why we can't have the + operator come in via an interface. ...

Blarg we'd be sacrificing one convenience/legibility helper for another. I think i'm rocking the boat too much here, but the idea would be to take your pattern of making the companion object the ViewEnvironmentKey (nice because it has only one initialization at class init time) and instead move it to an interface where we could then enforce the operations etc.

interface ViewEnvironmental<T: Any> {
  val envKey: ViewEnvironmentKey<T>
}

operator fun <T: ViewEnvironmental<T>> ViewEnvironment.plus(value: T): ViewEnvironment =
  this + ViewEnvironment(mapOf(value.envKey to value))

public enum class BackStackConfig: ViewEnvironmental<BackStackConfig> {
  None,
  First,
  Other;

  override val envKey: ViewEnvironmentKey<BackStackConfig>
   get() = object : ViewEnvironmentKey<BackStackConfig>(BackStackConfig::class) {
    override val default: BackStackConfig = None
  }
}

I think i'm trying to get back to the ViewWrapper that I (unhelpfully) nerd-sniped you into with the failed #606 .

Ok will stop trying to make this harder than it is 😊

@rjrjr rjrjr force-pushed the ray/viewenv-safety branch from 48b3f67 to 55fcc3b Compare January 12, 2022 23:02
@rjrjr
Copy link
Contributor Author

rjrjr commented Jan 12, 2022

Latest force-push makes the tests green.

TicTacToeEspressoTest#showRenderingTagStaysFresh was failing because it inadvertently exposed newly wasteful allocations -- when we started calling the new merge functions on every render pass, we were creating new instances of CompositeViewRegistry for no reason. I've added / restored some checks to return the existing ViewEnvironment or ViewRegistry when merging with an empty one.

I'll add some explicit coverage for that, seems worth being formal about it.

@rjrjr
Copy link
Contributor Author

rjrjr commented Jan 12, 2022

@steve-the-edwards PTAL, I've added a commit that adds the missing test coverage for that optimization, and adds a few more such if (this.isEmpty()) return that cases.

Take care to use the existing instances when combining with empty ones, and add tests to preserve that optimization.
@rjrjr rjrjr force-pushed the ray/viewenv-safety branch from e2b971c to 8d5da68 Compare January 12, 2022 23:55
@WorkflowUiExperimentalApi
public operator fun ViewRegistry.plus(binding: Entry<*>): ViewRegistry =
this + ViewRegistry(binding)
public infix fun ViewEnvironment.merge(registry: ViewRegistry): ViewEnvironment {
Copy link
Contributor

Choose a reason for hiding this comment

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

Beware I think once we start having mixed typed operators things can sometimes get confusing - this is the case for CoroutineContext which maps scope, dispatcher, name etc. in a very similar way to this. 90% of the time things just 'work' and work in a quick and satisfying way, but sometimes when reasoning about it it can get confusing how things overlap.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I was a bit concerned about that. But when I applied it across our app I saw a lot of boilerplate melt away, and didn't run into any problems.

I really wish the IDE was better about offering to import the .plus method, though. Always have to paste that import in myself.

@rjrjr rjrjr merged commit e35df60 into ray/ui-update Jan 13, 2022
@rjrjr rjrjr deleted the ray/viewenv-safety branch January 13, 2022 18:55
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