androidandroid-jetpackdagger-hilt

How to Update BASE URL in Dagger Hilt


I am working on passing the BASE URL to the Network Module for making API calls. When the user selects a country in the app, I update the BASE URL accordingly. On the second screen, I want to use this updated URL to make an API call and fetch detailed information to display.

I have code at 3 different place. I create base URL in Activity and with help of Activity + Viewmodel I make an API call to get response.

The BASE URL is going empty string in Network Module

Here I am grabbing API

@AndroidEntryPoint
class SchoolActivity: AppCompatActivity() {

    private lateinit var schoolBinding: ActivitySchoolBinding
    private lateinit var networkListener: NetworkListener
    private val schoolViewModel: SchoolViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        schoolBinding = ActivitySchoolBinding.inflate(layoutInflater)
        setContentView(schoolBinding.root)

        schoolViewModel.readBackOnline.observe(this) {
            schoolViewModel.backOnline = it
        }

        lifecycleScope.launch(Dispatchers.IO) {
            schoolViewModel.countryCodeFlow.collect{countryCode ->
                val baseUrl = when (countryCode) {
                    "uk" -> if (BuildConfig.BUILD_TYPE == "debug") SCH_BASE_UK_DEBUG else SCH_BASE_UK_PROD
                    "usa" -> if (BuildConfig.BUILD_TYPE == "debug") SCH_BASE_USA_DEBUG else SCH_BASE_USA_PROD
                    else -> SCH_BASE_UK_DEBUG
                }.toString()
                schoolViewModel.updateBaseURL(baseUrl)

                println("The Base URL ${schoolViewModel.getBaseURL()}")
            }
        }

        lifecycleScope.launch {
            lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED){
                networkListener = NetworkListener()
                networkListener.checkNetworkAvailability(this@SchoolActivity).collect{ status ->
                    Log.d("Network Listener : ", "Listener --- $status")
                    schoolViewModel.networkStatus = status
                    schoolViewModel.showNetworkStatus()
                    requestSchoolApi()
                }
            }
        }
    }

    private fun requestSchoolApi() {
        lifecycleScope.launch {
            requestApiData()
        }
    }

    private fun requestApiData() {
        Log.d("School Activity ", "Get School API call")
        schoolViewModel.getSchool()
        schoolViewModel.schoolDetailResponse.observe(this){ response ->
            println("Response $response")
        }
    }

}

Here I make suspend call to API formation using Dagger Hilt (Viewmodel code)

package uk.co

import android.app.Application
import android.widget.Toast
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import retrofit2.Response
import uk.co.parentapps.connect.data.SchoolRepository
import uk.co.parentapps.connect.data.SchoolStoreRepository
import uk.co.parentapps.connect.model.School
import uk.co.parentapps.connect.util.ApiConfig
import uk.co.parentapps.connect.util.NetworkResult
import javax.inject.Inject

@HiltViewModel
class SchoolViewModel @Inject constructor(
    private val schoolStoreRepository: SchoolStoreRepository,
    private val schoolRepository: SchoolRepository,
    private val apiConfig: ApiConfig,
    application: Application): AndroidViewModel(application) {

    var networkStatus = false
    var backOnline = false

    val readBackOnline = schoolStoreRepository.readBackOnline.asLiveData()

    val countryCodeFlow: Flow<String> = schoolStoreRepository.readCountryCode.map { it as String }

    var schoolDetailResponse: MutableLiveData<NetworkResult<School>?> = MutableLiveData()

    fun updateBaseURL(appUrl: String){
        apiConfig.setAppBaseUrl(appUrl)
    }

    fun getBaseURL(): String {
        return apiConfig.baseUrl
    }

    fun getSchool() = viewModelScope.launch {
        getSafeSchoolCall()
    }

    private suspend fun getSafeSchoolCall() {
        if (backOnline){
            try{
                val schoolResponse = schoolRepository.remoteStore.getSchools()
                schoolDetailResponse.value = handleSchoolResponse(schoolResponse)
            }catch (e: Exception){
                schoolDetailResponse.value = NetworkResult.Error("No School Found")
            }
        }else{
            schoolDetailResponse.value = NetworkResult.Error("No Internet Connection")
        }
    }

    private fun handleSchoolResponse(response: Response<School>): NetworkResult<School>? {
        when{
            response.message().toString().contains("timeout") -> {
                return NetworkResult.Error("Timeout")
            }
            response.code() == 500 -> {
                return NetworkResult.Error("Internal Server Error")
            }
            response.body()!!.schoolData.isEmpty() -> {
                return NetworkResult.Error("School Not Found")
            }
            response.isSuccessful -> {
                val schoolResult = response.body()
                return NetworkResult.Success(schoolResult!!)
            }
            else -> {
                return NetworkResult.Error(response.message())
            }
        }
    }

    private fun saveBackOnline(backOnline: Boolean) = viewModelScope.launch(Dispatchers.IO) {
        schoolStoreRepository.saveBackOnline(backOnline)
    }

    fun showNetworkStatus() {
        if (!networkStatus) {
            Toast.makeText(getApplication(), "No Internet Connection.", Toast.LENGTH_LONG).show()
            saveBackOnline(true)
        } else {
            if (backOnline) {
                Toast.makeText(getApplication(), "We're back online.", Toast.LENGTH_LONG).show()
                saveBackOnline(false)
            }
        }
    }
}

Here goes Network Modeule and BASE URL Change Config code

class ApiConfig {
    var baseUrl: String = ""

    fun setAppBaseUrl(url: String) {
        baseUrl = url
    }
}
package uk.co

import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import uk.co.parentapps.connect.data.api.SchoolApi
import uk.co.parentapps.connect.util.ApiConfig
import java.util.concurrent.TimeUnit
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
class NetworkModule {

    @Singleton
    @Provides
    fun provideHttpClient(): OkHttpClient {
        return OkHttpClient.Builder()
            .readTimeout(30, TimeUnit.SECONDS)
            .connectTimeout(30, TimeUnit.SECONDS)
            .build()
    }

    @Singleton
    @Provides
    fun provideConverterFactory(): GsonConverterFactory {
        return GsonConverterFactory.create()
    }

    @Singleton
    @Provides
    fun provideApiConfig(): ApiConfig {
        return ApiConfig() // Provide the ApiConfig instance
    }

    @Singleton
    @Provides
    fun providesRetrofitInstance(
        okHttpClient: OkHttpClient,
        gsonConverterFactory: GsonConverterFactory,
        apiConfig: ApiConfig
    ): Retrofit {
        return Retrofit.Builder()
            .baseUrl(apiConfig.baseUrl)
            .client(okHttpClient)
            .addConverterFactory(gsonConverterFactory)
            .build()
    }

    @Singleton
    @Provides
    fun provideApiService(retrofit: Retrofit): SchoolApi {
        return retrofit.create(SchoolApi::class.java)
    }
}

Solution

  • Your base url has already been created here.

    @Singleton
    @Provides
    fun providesRetrofitInstance(
        okHttpClient: OkHttpClient,
        gsonConverterFactory: GsonConverterFactory,
        apiConfig: ApiConfig
    ): Retrofit {
        return Retrofit.Builder()
            .baseUrl(apiConfig.baseUrl)
            .client(okHttpClient)
            .addConverterFactory(gsonConverterFactory)
            .build()
    }
    

    with the value "", any call to

    fun setAppBaseUrl(url: String) {
        baseUrl = url
    }
    

    is useless since the retrofit instance has already been created. you need to create another retrofit instance with the updated url.

    Remove the retrofit singleton since that does not apply and create the retrofit object in your repository where you make your network calls.

    something like

    MyRepository(private val okhttpClient: OkhttpClient, private val gsonConverterFactory: GsonConverterFactory){
        private var retrofit: Retrofit
        init{
            createRetrofitObject("https://myInitialBaseUrl")
        }
    
        fun createRetrofitObject(baseUrl: String){
            retrofit = Retrofit.Builder()
            .baseUrl(baseUrl)
            .client(okHttpClient)
            .addConverterFactory(gsonConverterFactory)
            .build()
        }
    }
    

    then anytime you need to change the base url you just call createRetrofitObject passing in the new url