I have an endpoint
https://api.openweathermap.org/data/2.5/weather?q=sukkur,pk&appid=5*****************c
I want to use retrofit to make call to this end point
interface WeatherApiService {
@GET(K.CURRENT_WEATHER_URL)
suspend fun getCurrentWeather(
@Query(ApiParameters.Q) city: String,
@Query(ApiParameters.APP_ID) appId: String = K.API_KEY,
@Query(ApiParameters.UNITS) units: String = K.METRIC
): CurrentCondition
}
and I have
interface CurrentConditionRepository {
suspend fun getCurrentCondition(city: String): Flow<Response<CurrentConditionDto>>
}
and
class CurrentConditionRepositoryImpl @Inject constructor(
private val apiService: WeatherApiService
): CurrentConditionRepository {
override suspend fun getCurrentCondition(
city: String
): Flow<Response<CurrentConditionDto>> = flow {
// Loading state
emit(Response.Loading)
// Get current condition data from API
val currentConditionData = apiService.getCurrentWeather(city)
// Convert current condition data to DTO
val currentConditionDto = currentConditionData.toDto()
// Success state
emit(Response.Success(currentConditionDto))
}.catch { e ->
// Error state
e.printStackTrace()
emit(Response.Error(e.message.orEmpty()))
}
}
I have
class GetCurrentConditionUseCase(
private val repository: CurrentConditionRepository
) {
suspend operator fun invoke(city: String) = repository.getCurrentCondition(city)
}
in the end in view model I have
@HiltViewModel
class HomeViewModel @Inject constructor(
private val getCurrentConditionUseCase: GetCurrentConditionUseCase
): ViewModel(){
var getCurrentConditionWithState: StateFlow<Response<CurrentConditionDto>> = MutableStateFlow(Response.Loading)
fun getCurrentCondition(city: String){
viewModelScope.launch {
Log.e("HomeViewModel", "getCurrentCondition()")
getCurrentConditionWithState = getCurrentConditionUseCase.invoke(city)
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(),
initialValue = Response.Loading
)
}
}
}
}
all the Hilt DI is
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
private val json = Json {
coerceInputValues = true
ignoreUnknownKeys = true
}
@Provides
@Singleton
fun provideApi(
builder: Retrofit.Builder,
): WeatherApiService {
return builder
.build()
.create(WeatherApiService::class.java)
}
@Provides
@Singleton
fun provideRetrofitBuilder(): Retrofit.Builder {
return Retrofit.Builder().baseUrl(K.API_BASE_URL)
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
}
@Provides
@Singleton
fun providesCurrentConditionRepository(
apiService: WeatherApiService
): CurrentConditionRepository {
return CurrentConditionRepositoryImpl(apiService)
}
@Provides
@Singleton
fun providesCurrentConditionUseCase(
repository: CurrentConditionRepository
): GetCurrentConditionUseCase {
return GetCurrentConditionUseCase(repository)
}
}
when I use all that in
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
private val viewModel: HomeViewModel by viewModels()
private val TAG = "MainActivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
NimbleWeatherTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
) {
CurrentConditionScreen(viewModel, Modifier.padding(innerPadding))
}
}
}
}
}
}
@Composable
fun CurrentConditionScreen(viewModel: HomeViewModel, modifier: Modifier = Modifier) {
val currentConditionState by viewModel.getCurrentConditionWithState.collectAsState(initial = Response.Loading)
Column(modifier = modifier) {
Button(onClick = { viewModel.getCurrentCondition("sukkur") }) {
Text(text = "Search for Sukkur")
}
// Display the current condition based on the state
when (currentConditionState) {
is Response.Loading -> Text("Loading...")
is Response.Success -> {
val currentCondition = (currentConditionState as Response.Success<CurrentConditionDto>).data
Log.e("CURRENT_CONDITION", currentCondition.name)
Text("Current Condition: ${currentCondition.name}")
}
is Response.Error -> {
val error = (currentConditionState as Response.Error).message
Text("Error: $error")
}
}
}
}
only Loading is shown and no any network call is made
I have shared the project here on GitHub
Kindly have a look and find the issue please.
Your getCurrentCondition
is essentially a one-shot operation and is supposed to return a single value, not a Flow
. Considering that, a possible implementation might look like this:
HomeViewModel:
private val _currentConditionState =
MutableStateFlow<Response<CurrentConditionDto>>(Response.Loading)
val getCurrentConditionWithState: StateFlow<Response<CurrentConditionDto>> =
_currentConditionState
fun getCurrentCondition(city: String) {
viewModelScope.launch {
_currentConditionState.value = Response.Loading
_currentConditionState.value = getCurrentConditionUseCase.invoke(city)
}
}
CurrentConditionRepository:
interface CurrentConditionRepository {
suspend fun getCurrentCondition(city: String): Response<CurrentConditionDto>
}
CurrentConditionRepositoryImpl.getCurrentCondition:
override suspend fun getCurrentCondition(
city: String
): Response<CurrentConditionDto> {
// Get current condition data from API
val currentConditionData = try {
apiService.getCurrentWeather(city)
} catch (e: Exception) {
return Response.Error(e.message.orEmpty())
}
// Convert current condition data to DTO
val currentConditionDto = currentConditionData.toDto()
// Success state
return Response.Success(currentConditionDto)
}