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)
}
}
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