Hi, I have a chicken-egg problem and I wonder if t...
# multiplatform
m
Hi, I have a chicken-egg problem and I wonder if there is a best practice to solve this issue. The situation is: I have a kotlin compose multiplatform project, using koin as a DI framework. The appilcation uses Repositories which get a ktor httpClient injected which communicates to a server. The url to the server is currently hardcoded, but obviously I want the user to specify the url (and be able to change the url). Therefor I am now implementing the dataStore and adding it to the koin module for the application. However, now I have a bootstrap problem in that upon startup I do not yet have a preference so I should, in this case, ask the user to provide the url to the server. If the url is provided the application should use this configuration. But now durinig startup this fails because the koin context cannot be started because there is no url configured. What is the best approach to address this problem? Some options I thought of are: • First start the configuration without Koin and create a dataStore instance outside of koin and check if the url preference is available, if not let the user configure the url and then somehow trigger a restart of the application. • Do not let the repositories depend on the httpClient directly, but instead let them depend on the dataStore and create the httpClient in the repository (lazy) when needed and then use the preference value. The application can then check if the preference is set just with the normal coin context already available. • ?? any other options? Any advice would be nice
y
Move the host to the request processing. Don't set a base URL. Then it can be fetched with a suspending call (cached) if needed.
g
I had similar issue with user login. At app start I dont have token but when user logs-in then httpClient should recreate or somehow get the token. The way I did was to create a wrapper over httpClient, like so
Copy code
@Singleton
class CCHttpClient(
    @Named(IO) private val ioDispatcher: CoroutineDispatcher
) {
    private var client: HttpClient = defaultClient()

    fun setBearerToken(token: String) {
        client = client.config {
            defaultRequest {
                headers {
                    if (contains(HttpHeaders.Authorization)) {
                        remove(HttpHeaders.Authorization)
                    }
                    append(HttpHeaders.Authorization, "Bearer $token")
                }
            }
        }
    }

    fun removeBearerTokens() {
        client = client.config {
            defaultRequest {
                headers {
                    if (contains(HttpHeaders.Authorization)) {
                        remove(HttpHeaders.Authorization)
                    }
                }
            }
        }
    }

    suspend fun post(
        url: String,
        body: Any,
        useDefaultErrorHandler: Boolean = true
    ): HttpResponse = withContext(ioDispatcher) {
        <http://client.post|client.post>(url) {
            setBody(body)
        }.checkSuccessIf(useDefaultErrorHandler)
    }
}
Maybe you could do something similar? so instead of creating httpClient directly, create some manager class. when user enters the url then call .setUrl function on that class and make all requests go thru that class.
s
Instead of injecting an HttpClient, inject a Factory of an HttpClient (and this Factory takes a URL for the server endpoint):
(URI) -> HttpClient
instead of plain
HttpClient
m
Thx, I see some good ideas. I am going to look at this again and see what fits best with these new insights.