We’ve recently cleaned up our authentication token...
# naming
s
We’ve recently cleaned up our authentication token logic and now got a centralized place (let’s call this AuthenticationService) where we know we’re here logging in, hence storing a token and refresh token, and a logout method which clears these tokens. In our “log out” case, we got a log out usecase, which basically goes ahead and manually clears some caches and stuff, and then finalizes by calling this log out function which finally clears the tokens. We call that log out function manually when the user simply clicks on a button. Now here comes the situation, where we also want to perform this log out logic when they are logged out through other means, like the tokens expire and so on. So I was thinking maybe this is a place to invert how this works, and instead of the log out usecase doing everything and then clears the tokens, it should instead call this AuthenticationService, which will clear the token and then after it’s done it should trigger an event of “Hey I am now logged out”. The idea is that then, other modules who don’t need to to know too much about these modules, just need to be aware of some interface which may be named like
Copy code
interface AuthenticationListener {
  suspend fun loggedIn()
  suspend fun loggedOut()
}
This will be in a common module without more dependencies, and whichever module is interested can provide implementations of this interface to do stuff that it may need to do on login or logout. Then there should be some class which is called ??? Let’s just say
AuthenticationEventDispatcher
for now
Copy code
class AuthenticationEventDispatcher(
  authenticationListeners: Set<AuthenticationListener>, // injected
  applicationScope: ApplicationScope,
  coroutineContext: CoroutineContext,
) {
  private val authEvents = Channel<AuthenticationEvent>(Channel.UNLIMITED)
  init {
    applicationScope.launch(coroutineContext) {
      authEvents.consumeAsFlow()
        .collect { event ->
          authenticationListeners.map { authenticationListener ->
            async { when (event) { LOGGED_IN -> authenticationListener.loggedIn(); LOGGED_OUT -> authenticationListener.loggedOut() } } 
          }.awaitAll()
        }
    }
  }
  fun loggedIn() { authEvents.trySend(LOGGED_IN) }
  fun loggedOut() { authEvents.trySend(LOGGED_OUT) }
  private enum class AuthenticationEvent { LOGGED_IN, LOGGED_OUT }
}
Which should just take these events and inform all the AuthenticationListener instances to do whatever it is that they’re gonna do (One example is that I want to get a member ID when logging in from the backend and save it, but this only works after having logged in first) So with all this context, how would you better name “AuthenticationEventDispatcher” or even the “AuthenticationListener” which I am also not happy about?
e
Couldn't you just have a single top-level state flow?
Copy code
enum class AuthenticationEvent { LOGGED_IN, LOGGED_OUT }

val authState = MutableStateFlow(LOGGED_OUT);
consumers collect directly on the state flow? Names seem fine to me 🙂 Would perhaps consider naming the listener methods
onX()
to signal that it's triggered by an event. Did you try asking ChatGPT btw? Given all that context it might give some good suggestions 😄
s
Hmmmm a global MutableSharedFlow with replay = 0 to avoid re-emitting old events huh like
MutableSharedFlow<Event>(replay = 0, extraBufferCapacity = Int.MAX_VALUE)
That’d mean that this global MutableStateFlow would need to be on the “leaf” module though, while now my dispatcher can stay in the :app module and another super straightforward module can exist which simply contains this AuthenticationListener interface and all of my modules can simply depend on that module if the need to do anything with these events. I think I prefer it that way?
Btw ChatGPT says:
Copy code
You can name "AuthenticationEventDispatcher" as "AuthEventBroadcaster" to indicate its role in broadcasting authentication events. "AuthenticationListener" can be named "AuthEventObserver" to indicate its role in observing and responding to authentication events.
Broadcaster is a name I didn’t consider, I think I like that one. But I may keep the …Listener suffix, as it reminds me of other such listeners like from OkHttp. Also I think I will also go with AuthEvent… at the start. Damn this is the first time I try it and it helps 😂 Maybe I should try it more often.
d
This sounds like the Plugin Pattern.
This pattern is awesome for these kinds of things.
s
Well, what do you know, this is exactly what I've done too 😅 It's here https://github.com/HedvigInsurance/android/tree/develop/auth%2Fauth-event-core and the interested modules just depend on it and need to make sure to bind their instance using Koin, since we use Koin, like this https://github.com/HedvigInsurance/android/blob/develop/notification/firebase/src/main/kotlin/com/hedvig/android/notification/firebase/di/FirebaseNotificationModule.kt#L29-L31
d
I am trying this pattern out in a very questionable way to see if it’s at all scalable. But wanted to plugin entire UI feature flows into my main app. I am NOT RECOMMENDING this…it’s purely for experimental purposes right now.
Plugin it in like so.