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

Created a Hilt example with a view model scoped custom component #503

Merged
merged 11 commits into from
Feb 2, 2021

Conversation

gpeal
Copy link
Collaborator

@gpeal gpeal commented Jan 21, 2021

This PR is a heavily modified #495

MavericksViewModels no longer extend Jetpack ViewModels. However, we want to make sure that we have a story for how to use it with Hilt and its new @ViewModelScoped scope.

I couldn't figure out how to integrate Mavericks with its corresponding ViewModelComponent. However, I was able to create the same setup with a custom component instead. It achieves the same thing and I can't think of any downsides.

@elihart If we land this, I'll put up another PR with more docs.

#502

@elihart @denis-bezrukov @rohandhruva @marukami

Copy link
Contributor

@elihart elihart left a comment

Choose a reason for hiding this comment

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

@gpeal thanks for jumping on this so quickly, overall I think it's really promising.

Main thoughts:

  • are you thinking that we would publish these utils as an optional module, or would people need to copy this in manually?
  • if future extensions like this are added for ViewModel might we not be able to support them without having our mavericksviewmodel subclass jetpack viewmodel (that is, do we still want to move forward with our custom viewmodel class?)
  • I started thinking of ideas to remove some boilerplate from this, curious what else we can do


class HiltMavericksViewModelFactory<VM : MavericksViewModel<S>, S : MavericksState>(
private val viewModelClass: Class<out MavericksViewModel<S>>
) : MavericksViewModelFactory<VM, S> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Might someone want to override initialState too? it seems like that could be a reasonable thing to do. To easily allow this we could have a nullable lambda argument to hiltMavericksViewModelFactory to provide the state

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@elihart You can delegate the factory interface to the function above and still override createState


@AssistedFactory
interface Factory : AssistedViewModelFactory<HelloHiltViewModel, HelloHiltState> {
override fun create(state: HelloHiltState): HelloHiltViewModel
Copy link
Contributor

Choose a reason for hiding this comment

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

instead of needing to override this could be make the parent interface generic, ie AssistedViewModelFactory<*,*>. We convert to that when we multi bind anyway, so does having it be typed here matter? just thinking of ways to simplify the setup

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't think so because the return type has to match the @AssistedInject constructor

override fun create(state: HelloHiltState): HelloHiltViewModel
}

companion object : MavericksViewModelFactory<HelloHiltViewModel, HelloHiltState> by hiltMavericksViewModelFactory()
Copy link
Contributor

Choose a reason for hiding this comment

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

what might be nifty is if we could remove this boilerplate somehow. One idea off the top of my head is to have an empty interface the viewmodel could implement as a flag to mavericks to essentially force this behavior without needing to declare the object

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@elihart Want to fork this and try that out? :)

@gpeal
Copy link
Collaborator Author

gpeal commented Jan 22, 2021

@elihart to your point of not extending ViewModel, I am not convinced either way to be honest. What do you think?

Comment on lines +53 to +60
@MavericksViewModelScoped
@DefineComponent(parent = SingletonComponent::class)
interface MavericksViewModelComponent

@DefineComponent.Builder
interface MavericksViewModelComponentBuilder {
fun build(): MavericksViewModelComponent
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do you need a Component in addition to a custom EntryPoint?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@marukami That's the whole purpose of this PR. It allows you to have a dagger component scoped to your ViewModel so you can have objects that match its lifecycle and also have objects that are shared/scoped within that as well.

@gpeal
Copy link
Collaborator Author

gpeal commented Jan 23, 2021

@elihart @rohandhruva it looks like Hilt doesn't support AssistedInject either. In an experiment, I made MavericksViewModel extend ViewModel again, updated the factory and annotated the ViewModel and got:

ViewModel constructor should be annotated with @Inject instead of @AssistedInject.

@rohandhruva
Copy link

@gpeal , thank you for this -- it looks great!

Looks like there's an open issue to address @AssistedInject in view models: google/dagger#2287

I'm not sure if assisted inject is the right fit for a Mavericks View Model which already has support for custom factories. My main motivation for reconsidering the ViewModel hierarchy was the ability to use the new @ViewModelComponent, but it would seem that the custom component you introduced achieves almost the same purpose.

The only difference is that a custom component does not easily flow in the hierarchy of hilt's models, but I think that's also achievable with some generated component manager tricks.

@gpeal
Copy link
Collaborator Author

gpeal commented Jan 26, 2021

@rohandhruva What do you mean it doesn't flow in the hierarchy of Hilt's components? It is a child of SingletonCompoennt. ViewModelComponent is a child of ActivityRetainedComponent but for all intents and purposes, they should behave the same.

@rohandhruva
Copy link

If I understand correctly, we lose on the ability to distinguish between dependencies shared across multiple injection in the same viewmodel, versus across all viewmodels, that is described here.

That said, thinking of a use case where you want an object which is scoped across all mavericks viewmodels, but not a singleton, seems very contrived :) So I agree, there's no practical difference.

@gpeal
Copy link
Collaborator Author

gpeal commented Jan 27, 2021

@rohandhruva I don't understand how there is any difference at all in the behavior of @ViewModeScoped and @MavericksViewModelScoped (contrived or not) besides it being a child of SingletonComponent vs ActivityRetainedComponent.

@rohandhruva
Copy link

On a second read, I realized that both @Singleton and @ActivityRetainedScope have the same exact effect here, so you're right -- there's no difference at all!

@gpeal gpeal merged commit e16c8d3 into master Feb 2, 2021
@gpeal gpeal deleted the gpeal/hilt-view-model-scope branch February 2, 2021 02:53
@jakoss
Copy link
Contributor

jakoss commented Feb 17, 2021

How about a new artifact mvrx-hilt that will expose the classes from the com.airbnb.mvrx.hellohilt.di package? I think that might be helpful

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.

5 participants