Android custom view lifecycle with dependency injection as a bonus

Navid Ghahremani
4 min readMar 27, 2020

--

Today, I am going to show you how to add the activity lifecycle to a custom view alongside supporting dependency injection.

Lifecycle is important to pause/resume data retrieving or any calculation while the activity is active/inactive. Views have their own lifecycle and it has lots of hassle to bind it to activity lifecycle, now, imagine you want to use Dagger2 to inject your ViewModel in your view which needs the Activity lifecycle that you do not have in a custom view.

OK, with the introduction above, now we know we need to set up Dagger2 for our custom view and bind the activity lifecycle to it.

What I love about Kotlin is the easy build-in delegate pattern using keyword by. The reason that I am telling this is, we will use this keyword multiple places and you can see how it makes your code easier to read and understand.

OK, let jump to the implementation. Just need to mention that in all example I remove package xxxx to make it easier to copy-paste.

We need an interface for our lifecycle methods and another one for our ViewModel initialization.

ViewModelAccessor.kt

Note: ViewModelUtil is a custom implementation of view model initiator for Dagger2. Here is a great article about it.

ViewModelUtil.kt

You do not need this class necessary but having it, prevent you to provide ViewModelFactory every time you need to get your ViewModel

ComponentAccessor.kt

The interface that we need to implement in our custom view.

Let’s dive deep and see what above methods do one by one:

Detached: Will be called whenever the view is destroyed or removed from the parent (onDetachedFromWindow). We need this method to dispose all listeners and mark the lifecycle as destroyed.

Attached: Will be called whenever the view is attached to the parent (onAttachedToWindow). We will add our listeners here and mark the lifecycle as started.

Disposable.track: As you might guess, we want to track our Disposables and dispose them in detached method.

getLifeCycle: No explanation needed. This is a method from LifeCycleOwner interface that we need to override it and return our custom lifecycle.

inject: And finally the most important part, our beloved injection. To make this method work, we need some definitions in our Dagger2 module which will be covered later in this article.

Now, it is time to implement our interface.

ComponentAccessorImpl.kt

We use this implementation indirectly with build-in Kotlin delegate pattern to implement boilerplate of ComponentAssesor.kt interface

Some clarification besides the interface definition.

@Suppress("UNUSED") : The reason that we need this is, as we are not going to extend this class directly and we just use it with by keyword IDE and lint complain about it, so we make sure it does not happen.

lifeCycleRegistry: This is the most important part of this implementation, as I mentioned above I love Kotlin and I love Kotlinlazy initialization even more. We define our custom lifecycle here and we will bind it to our activity lifecycle. As you can see, we pass this(LifecycleOwner) to LifecycleRegistry constructor because we want to make it listen to our custom lifecycle changes. We pass the LifecycleOwner to our ViewModel observer to make it aware of the lifecycle.

contextLifecycle: It is self-explanatory. We need this variable to make sure we are removing all observers whenever the activity is paused or is destroyed.

application: We need this variable to enable the injection in our custom view.

@OnLifecycleEvent annotation: Annotated methods with @LifecycleEvent annotation are the main part of the implementation, as we extended LifecycleObserver interface in our ComponentAccessor interface, we are making the android aware of our lifecycle listeners, so whenever android is firing lifecycle events we can catch those in annotated methods for relevant events(ON_CREATE,ON_RESUME,...) and then we update our lifecycle state.

@SuppressLint("MissingSuperCall") : As we do not use this class directly, lint might give error related to not calling super.

Note: LifecycleOwnerNotFoundException is just a simple exception to make it easier to catch the relevant errors.

With all mentioned above, now it is the time to create our base custom view. Here is the base layout that we can extend it later to create our UI.

CardViewWithLifecycle.kt

It is straightforward, as you see we are using delegate pattern to implement our interface ComponentAccessor .

OK, it is almost done, just two small things remain.

LovelyComponent.kt

I do not think it needs an explanation. The only thing that I need to mention is, do not mix the constructors. This article dives deep in the problem by causing mixing the constructors.

And finally, you need to define your custom component in Dagger2 module to make inject(this) work.

Done. Now you have a custom view with activity lifecycle and dependency injection.

Conclusion:

I hear some places talking about using dependency injection is not recommended in CustomView but I had specific needs for my component and I wanted to have my custom view to preventing duplication or binding to fragment or activity. So I had to research a lot and find my way to implement it. I have no performance issues so far, and it works like a charm.

Also, here is the issue raised in Dagger2 GitHub related to having an injection in view. https://github.com/google/dagger/issues/720

I hope this article was helpful, if it was, please clap it and leave me a comment and let me know your thoughts.

--

--