Search code examples
androidkotlinsocketssocket.iodagger-hilt

How can I use latest accessToken in Hilt with SocketIO?


I am using Hilt and implementing socket.

Here's the repository Module for creating repos.

@Module
@InstallIn(SingletonComponent::class)
object RepositoryModule {
    @Provides
    fun provideSocketRepository(socket: Socket): SocketRepository {
        return SocketRepository(socket)
    }
}

And here's ServiceModule to inject Socket to SocketRepository.

@Module
@InstallIn(SingletonComponent::class)
object ServiceModule {
    @Provides
    fun provideSocket(): Socket {
        logd("socket: accessToken: ${Common.accessToken}")
        val options = IO.Options().apply {
            timeout = 30000
            reconnection = true
            reconnectionDelay = 1000
            reconnectionAttempts = 300
            transports = arrayOf(WebSocket.NAME)
            extraHeaders = mapOf(
                "Authorization" to listOf("Bearer ${Common.accessToken}"),
                "Content-Type" to listOf("application/json"),
                "identity" to listOf("app")
            )
        }
        return IO.socket(UrlType.BACK_OFFICE_SERVER.getUrl(), options)
    }
}

The problem is

  1. Common.accessToken is global variable which needs refactoring and also it's updated after sign in. However, Socket is needed globally for entire app process. So, the timing is after injection. How can it get the latest accessToken using hilt?

  2. I should connect & disconnect Socket programmatically at some points. If I use hilt it will keep injecting to the repo class. Is it Okay the instance to be alive when I try to disconnect it? for example, socket.disconnect without socket = null and then socket.connect to reconnect socket when it needs to be connected.


Solution

  • In your case, Socket shouldn't be injected, instead it is wrapped in a Manager class which will handle the logic and being injected into Repositories.

    You can have an singleton AuthManager class which controls the login and token flow (you can define the module yourself):

    class AuthManager @Inject constructor() {
        val token = MutableStateFlow<String?>(null)
    
        suspend fun signIn() {
            // Do sign in stuff, then update your token
            token.emit(newToken)
        }
    }
    

    Then your SocketManager class has AuthManager as dependencies:

    class SocketManager @Inject constructor(authManager: AuthManager) {
        val socket = MutableStateFlow<Socket?>(null)
    
        init {
            authManager.token.collect {
                // Token updated, recreated your socket here and emit new socket to collector
                socket.emit or socket.trySend
            }
        }
    }
    

    SocketManager is Singleton too (you can define the Module yourself)

    Lastly, anywhere you want to use the socket, like in Activity, inject SocketManager and collect the socket as needed:

    @AndroidEntryPoint
    class MyActivity: AppCompatActivity {
        @Inject
        lateinit var socketManager: SocketManager
    
        fun onCreate() {
            lifecycleScope.launch {
                repeatOnLifecycle(Lifecycle.State.STARTED) {
                    socketManager.socket.collect {
                        // Use your socket here, socket will be changed after token changed and this code block is called
                    }
                }
            }
        }
    }