Can't use Dagger to inject instances of `PublicClientApplication` into activities or fragments
See original GitHub issueIs your feature request related to a problem? Please describe. I apologize for being so late with this feedback. I was previously using v0.2.2 and was held back because of the time required to update to using the new async methods we are required to call if on the main thread. Plus v0.2.2 was working totally fine aside from some very rare crashes.
I also realize that the synchronous methods for worker threads which throw exceptions if called on main thread was one of my requests (#544). I stand by that suggestion though and appreciate you adding them 😄.
What I didn’t realize til I updated was that there are now no public constructors for the IPublicClientApplication
classes and their factory methods must also be called off of the main thread.
So here’s where my frustration begins, I previously used Dagger to build instances of PublicClientApplication
which concentrated the configuration into a single place, and the client instance could simply be injected into fragments where I needed it. With the latest versions, this crashes since the object graph is created by dagger on the main thread, or at least the provider in the graph is invoked on the main thread when activities or fragments are injected in onCreate
.
Now I need to enter “callback hell” in fragments where I need an instance of IPublicClientApplication
, I have to use a callback to get it. If I need to call acquireToken
, I have to get the client instance async with a callback, then also get the token (again with a callback):
val params = AcquireTokenParameters.Builder()
.withCallback(object : AuthenticationCallback {
override fun onSuccess(authenticationResult: IAuthenticationResult) { }
override fun onCancel() = Timber.w("Cancelled")
override fun onError(exception: MsalException) = Timber.e(exception, "Failed to get token")
})
.build()
PublicClientApplication.createSingleAccountPublicClientApplication(
applicationContext,
R.raw.msal_config,
object : IPublicClientApplication.ISingleAccountApplicationCreatedListener {
override fun onCreated(application: ISingleAccountPublicClientApplication) {
application.acquireToken(params)
}
override fun onError(exception: MsalException) {
Timber.e(exception, "Failed to initialize PublicClientApplication")
}
}
)
The README suggests storing the IPublicClientApplication
instance in a field on the class once retrieved, but this is prone to race conditions, especially in java where nullability might not be enforced.
Creating the client requires reading a JSON file to load the configuration, which if performed on the main thread would trigger strict mode violations, or cause jank so I understand why this is also asynchronous.
Describe the solution you’d like
I’d like the configuration loading to be performed lazily when needed. Then I can create a client instance with dagger and inject it easily. In this scenario any errors encountered configuring the client instance would be thrown by the synchronous acquireToken()
methods or to the onError()
method of the AuthenticationCallback
. This would flatten the callback pyramid and make the API generally easier to use. Dagger or some other method could be used to save the client instance as a singleton as well.
Issue Analytics
- State:
- Created 3 years ago
- Reactions:1
- Comments:7 (3 by maintainers)
Top GitHub Comments
Greetings! I don’t mean to resurrect this old issue. However, I thought I’d share my solution that builds upon some of the suggestions from this discussion. My intention was to support the more “modern” ways of working on Android (Dagger/Hilt, AAC VMs, Kotlin coroutines). The sample can be found here: https://github.com/v-po/msal-sample
It’s still very much a work in progress and I would greatly appreciate any feedback on it.
@joshfriend @MariusVolkhart @4gus71n - apologies for the ping, but just in case you might be interested ☺️
@MariusVolkhart while that suggestion is certainly an improvement over the current method, I would really like the library to handle as much of the background thread stuff as possible. Having the client app class lazy configure itself, then passing configuration errors to the callback in
AuthenticationCallback
seems like an alright solution to me.Lazy
would still require me to use an asynctask or something.