
So I migrated to Hilt.
It was supposed to clean up my dependency injection mess, reduce boilerplate, and make onboarding easier for the team. And to be fair, it did all that.
But then I noticed something weird:
My app’s cold start was noticeably… not cold. It was straight-up sluggish.
I checked my code.
I blamed the backend.
I restarted Android Studio (twice, just to be sure).
And then I did the one thing every dev dreads — I opened the profiler.
Turns out, Hilt was doing what it does best: initializing everything I ever told it to. Unfortunately, I was the one who told it to do way too much.
The Problem: Death by Eager Injection
Here’s what went down.
I had moved a bunch of dependencies — network clients, repositories, Room DB access, analytics loggers — into @SingletonComponent because, well, that’s what all the tutorials do.
What I didn’t realize was that Hilt would eagerly create those singletons… right at app launch. Before my splash screen animation even had a chance to flex.
Basically, my Application.onCreate() was turning into a bottleneck.
How I Fixed It (After a Brief Panic)
Once I accepted that it wasn’t Mercury retrograde or a rogue Compose recomposition, I sat down and refactored. Here’s what worked for me:
1. Use Provider<T> or @Lazy for Heavy Dependencies
If something isn’t needed immediately, don’t inject it immediately.
@Inject lateinit var heavyService: Provider<MyHeavyService>
This way, it only gets created when you actually call get(). No need to fire up the entire world during app launch.
2. Don’t Inject Everything into SingletonComponent
This one was a wake-up call.
I was injecting even feature-specific logic into the global scope. That meant things used only in the onboarding flow were being created at startup.
Instead, I started using:
ActivityRetainedComponent for ViewModelsActivityComponent orÂFragmentComponent for screen-specific logic
Scoped things more reasonably. Cold start got faster almost immediately.
3. Delay Anything Non-Essential
Do you really need crash reporting, remote config, analytics, A/B testing, and half a dozen SDKs running in the first 0.5 seconds?
I didn’t.
So I pushed all of that into a background coroutine with a delayed initializer:
CoroutineScope(Dispatchers.Default).launch {
Analytics.init()
RemoteConfig.sync()
OtherStuff.wakeUp()
}
User sees the UI instantly. Heavy stuff runs in the background. Win-win.
Job Offers
4. Keep Your @Provides Methods Clean
My @Provides methods had started turning into mini factories with their own business logic. Not proud of it.
So I moved complex initialization into helper classes or config builders and kept the Hilt bindings lean and predictable.
If your @Provides block takes more than a few lines or includes .build() three times… it might be doing too much.
5. Measure. Don’t Guess.
The profiler doesn’t lie.
I added log markers to track how long onCreate() was taking, and used Android Studio’s Profiler to see what was being initialized too early.
Once I had the data, the refactors became obvious — and justifiable to the team.
What I Learned
- Hilt isn’t the problem — overusingÂ
SingletonComponent and injecting too much too soon is. - Delaying non-critical dependencies can drastically improve startup performance.
- Proper scoping isn’t just good architecture — it’s also good UX.
- Lazy injection is your friend. So is the profiler.
If you’re planning to migrate to Hilt, or already have, take this as a reminder: powerful tools need thoughtful usage.
And if your app feels slower after “cleaning up” with Hilt…
maybe it’s not your app that’s messy — maybe it’s your startup strategy.
Follow me for more Android dev lessons, real-world mistakes, and hard-won fixes that didn’t come from ChatGPT .
This article was previously published on proandroiddev.com.



