diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..103e00c --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,32 @@ + + + + \ No newline at end of file diff --git a/app/src/main/java/com/plcoding/weatherapp/data/dto/ForecastDataDto.kt b/app/src/main/java/com/plcoding/weatherapp/data/dto/ForecastDataDto.kt new file mode 100644 index 0000000..878086b --- /dev/null +++ b/app/src/main/java/com/plcoding/weatherapp/data/dto/ForecastDataDto.kt @@ -0,0 +1,26 @@ +package com.plcoding.weatherapp.data.dto + +import com.squareup.moshi.Json +import java.time.LocalDateTime +import java.util.Date + +data class ForecastDataDto ( + val time: List, + @field:Json(name = "temperature_2m_max") + val maxTemperatures: List, + @field:Json(name = "temperature_2m_min") + val minTemperatures : List, + @field:Json(name = "weathercode") + val weatherCodes: List, + @field:Json(name = "sunrise") + val sunrise: List, + @field:Json(name = "sunset") + val sunset: List, + @field:Json(name = "rain_sum") + val rainSum: List, + @field:Json(name = "showers_sum") + val showersSum: List, + @field:Json(name = "snowfall_sum") + val snowfallSum: List + +) \ No newline at end of file diff --git a/app/src/main/java/com/plcoding/weatherapp/data/dto/ForecastDto.kt b/app/src/main/java/com/plcoding/weatherapp/data/dto/ForecastDto.kt new file mode 100644 index 0000000..485beee --- /dev/null +++ b/app/src/main/java/com/plcoding/weatherapp/data/dto/ForecastDto.kt @@ -0,0 +1,9 @@ +package com.plcoding.weatherapp.data.dto + +import com.squareup.moshi.Json + +data class ForecastDto ( + @field:Json(name = "daily") + val forecastDataDto: ForecastDataDto + +) \ No newline at end of file diff --git a/app/src/main/java/com/plcoding/weatherapp/data/remote/WeatherDataDto.kt b/app/src/main/java/com/plcoding/weatherapp/data/dto/WeatherDataDto.kt similarity index 91% rename from app/src/main/java/com/plcoding/weatherapp/data/remote/WeatherDataDto.kt rename to app/src/main/java/com/plcoding/weatherapp/data/dto/WeatherDataDto.kt index aa25dfc..351a932 100644 --- a/app/src/main/java/com/plcoding/weatherapp/data/remote/WeatherDataDto.kt +++ b/app/src/main/java/com/plcoding/weatherapp/data/dto/WeatherDataDto.kt @@ -1,4 +1,4 @@ -package com.plcoding.weatherapp.data.remote +package com.plcoding.weatherapp.data.dto import com.squareup.moshi.Json diff --git a/app/src/main/java/com/plcoding/weatherapp/data/remote/WeatherDto.kt b/app/src/main/java/com/plcoding/weatherapp/data/dto/WeatherDto.kt similarity index 74% rename from app/src/main/java/com/plcoding/weatherapp/data/remote/WeatherDto.kt rename to app/src/main/java/com/plcoding/weatherapp/data/dto/WeatherDto.kt index e647e50..ff0c7ec 100644 --- a/app/src/main/java/com/plcoding/weatherapp/data/remote/WeatherDto.kt +++ b/app/src/main/java/com/plcoding/weatherapp/data/dto/WeatherDto.kt @@ -1,4 +1,4 @@ -package com.plcoding.weatherapp.data.remote +package com.plcoding.weatherapp.data.dto import com.squareup.moshi.Json diff --git a/app/src/main/java/com/plcoding/weatherapp/data/mappers/WeatherMappers.kt b/app/src/main/java/com/plcoding/weatherapp/data/mappers/WeatherMappers.kt index cc18e49..5ef4a9e 100644 --- a/app/src/main/java/com/plcoding/weatherapp/data/mappers/WeatherMappers.kt +++ b/app/src/main/java/com/plcoding/weatherapp/data/mappers/WeatherMappers.kt @@ -1,7 +1,9 @@ package com.plcoding.weatherapp.data.mappers -import com.plcoding.weatherapp.data.remote.WeatherDataDto -import com.plcoding.weatherapp.data.remote.WeatherDto +import com.plcoding.weatherapp.data.dto.ForecastDataDto +import com.plcoding.weatherapp.data.dto.ForecastDto +import com.plcoding.weatherapp.data.dto.WeatherDataDto +import com.plcoding.weatherapp.data.dto.WeatherDto import com.plcoding.weatherapp.domain.weather.WeatherData import com.plcoding.weatherapp.domain.weather.WeatherInfo import com.plcoding.weatherapp.domain.weather.WeatherType @@ -49,4 +51,8 @@ fun WeatherDto.toWeatherInfo(): WeatherInfo { weatherDataPerDay = weatherDataMap, currentWeatherData = currentWeatherData ) +} + +fun ForecastDto.toForecastInfo() : ForecastDataDto { + return this.forecastDataDto } \ No newline at end of file diff --git a/app/src/main/java/com/plcoding/weatherapp/data/remote/WeatherApi.kt b/app/src/main/java/com/plcoding/weatherapp/data/remote/WeatherApi.kt index 67ac577..06683c8 100644 --- a/app/src/main/java/com/plcoding/weatherapp/data/remote/WeatherApi.kt +++ b/app/src/main/java/com/plcoding/weatherapp/data/remote/WeatherApi.kt @@ -1,5 +1,8 @@ package com.plcoding.weatherapp.data.remote +import com.plcoding.weatherapp.data.dto.ForecastDto +import com.plcoding.weatherapp.data.dto.WeatherDto +import retrofit2.Response import retrofit2.http.GET import retrofit2.http.Query @@ -9,5 +12,12 @@ interface WeatherApi { suspend fun getWeatherData( @Query("latitude") lat: Double, @Query("longitude") long: Double - ): WeatherDto + ): Response + + @GET("v1/forecast?daily=weather_code,temperature_2m_max,temperature_2m_min,sunrise,sunset,rain_sum,showers_sum,snowfall_sum") + suspend fun getDailyForecast( + @Query("latitude") lat: Double, + @Query("longitude") long: Double + ) : Response + } \ No newline at end of file diff --git a/app/src/main/java/com/plcoding/weatherapp/data/repository/WeatherRepositoryImpl.kt b/app/src/main/java/com/plcoding/weatherapp/data/repository/WeatherRepositoryImpl.kt index 49879dd..ea39086 100644 --- a/app/src/main/java/com/plcoding/weatherapp/data/repository/WeatherRepositoryImpl.kt +++ b/app/src/main/java/com/plcoding/weatherapp/data/repository/WeatherRepositoryImpl.kt @@ -1,27 +1,22 @@ package com.plcoding.weatherapp.data.repository -import com.plcoding.weatherapp.data.mappers.toWeatherInfo +import com.plcoding.weatherapp.data.dto.ForecastDataDto +import com.plcoding.weatherapp.data.dto.ForecastDto import com.plcoding.weatherapp.data.remote.WeatherApi import com.plcoding.weatherapp.domain.repository.WeatherRepository import com.plcoding.weatherapp.domain.util.Resource +import com.plcoding.weatherapp.domain.util.checkResponse +import com.plcoding.weatherapp.domain.util.checkResponse2 import com.plcoding.weatherapp.domain.weather.WeatherInfo import javax.inject.Inject class WeatherRepositoryImpl @Inject constructor( private val api: WeatherApi -): WeatherRepository { +) : WeatherRepository { - override suspend fun getWeatherData(lat: Double, long: Double): Resource { - return try { - Resource.Success( - data = api.getWeatherData( - lat = lat, - long = long - ).toWeatherInfo() - ) - } catch(e: Exception) { - e.printStackTrace() - Resource.Error(e.message ?: "An unknown error occurred.") - } - } + override suspend fun getWeatherData(lat: Double, long: Double): Resource = + checkResponse(api.getWeatherData(lat, long)) + + override suspend fun getForecast(lat: Double, long: Double): Resource = + checkResponse2(api.getDailyForecast(lat,long)) } \ No newline at end of file diff --git a/app/src/main/java/com/plcoding/weatherapp/di/AppModule.kt b/app/src/main/java/com/plcoding/weatherapp/di/AppModule.kt index 6662377..ac35e49 100644 --- a/app/src/main/java/com/plcoding/weatherapp/di/AppModule.kt +++ b/app/src/main/java/com/plcoding/weatherapp/di/AppModule.kt @@ -4,13 +4,19 @@ import android.app.Application import com.google.android.gms.location.FusedLocationProviderClient import com.google.android.gms.location.LocationServices import com.plcoding.weatherapp.data.remote.WeatherApi +import com.plcoding.weatherapp.domain.repository.WeatherRepository +import com.plcoding.weatherapp.domain.usecase.GetForecastUseCase +import com.plcoding.weatherapp.domain.usecase.GetWeatherUseCase import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.converter.moshi.MoshiConverterFactory import retrofit2.create +import java.util.concurrent.TimeUnit import javax.inject.Singleton @Module @@ -19,17 +25,47 @@ object AppModule { @Provides @Singleton - fun provideWeatherApi(): WeatherApi { + fun provideWeatherApi( + httpClient: OkHttpClient.Builder + ): WeatherApi { return Retrofit.Builder() .baseUrl("https://api.open-meteo.com/") .addConverterFactory(MoshiConverterFactory.create()) + .client(httpClient.build()) .build() .create() } + @Provides + @Singleton + fun provideOkHttpClient( + httpLoggingInterceptor: HttpLoggingInterceptor + ): OkHttpClient.Builder { + return OkHttpClient.Builder() + .addInterceptor(httpLoggingInterceptor) + .connectTimeout(30, TimeUnit.SECONDS) // Adjust as needed + .readTimeout(30, TimeUnit.SECONDS) // Adjust as needed + .retryOnConnectionFailure(true) + } + + @Provides + @Singleton + fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor { + return HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY) + } + @Provides @Singleton fun provideFusedLocationProviderClient(app: Application): FusedLocationProviderClient { return LocationServices.getFusedLocationProviderClient(app) } + + @Provides + fun provideGetWeatherUseCase(weatherRepository: WeatherRepository): GetWeatherUseCase = + GetWeatherUseCase(weatherRepository) + + @Provides + fun provideGetForecastWeatherUseCase(weatherRepository: WeatherRepository) : GetForecastUseCase = + GetForecastUseCase(weatherRepository) + } \ No newline at end of file diff --git a/app/src/main/java/com/plcoding/weatherapp/domain/repository/WeatherRepository.kt b/app/src/main/java/com/plcoding/weatherapp/domain/repository/WeatherRepository.kt index ade5766..589bc21 100644 --- a/app/src/main/java/com/plcoding/weatherapp/domain/repository/WeatherRepository.kt +++ b/app/src/main/java/com/plcoding/weatherapp/domain/repository/WeatherRepository.kt @@ -1,8 +1,10 @@ package com.plcoding.weatherapp.domain.repository - +import com.plcoding.weatherapp.data.dto.ForecastDataDto +import com.plcoding.weatherapp.data.dto.ForecastDto import com.plcoding.weatherapp.domain.util.Resource import com.plcoding.weatherapp.domain.weather.WeatherInfo interface WeatherRepository { suspend fun getWeatherData(lat: Double, long: Double): Resource + suspend fun getForecast(lat: Double, long: Double): Resource } \ No newline at end of file diff --git a/app/src/main/java/com/plcoding/weatherapp/domain/usecase/GetForecastUseCase.kt b/app/src/main/java/com/plcoding/weatherapp/domain/usecase/GetForecastUseCase.kt new file mode 100644 index 0000000..28b8b90 --- /dev/null +++ b/app/src/main/java/com/plcoding/weatherapp/domain/usecase/GetForecastUseCase.kt @@ -0,0 +1,25 @@ +package com.plcoding.weatherapp.domain.usecase + +import com.plcoding.weatherapp.data.dto.ForecastDataDto +import com.plcoding.weatherapp.data.dto.ForecastDto +import com.plcoding.weatherapp.domain.repository.WeatherRepository +import com.plcoding.weatherapp.domain.util.DefaultRetryPolicy +import com.plcoding.weatherapp.domain.util.Resource +import com.plcoding.weatherapp.domain.util.checkError +import com.plcoding.weatherapp.domain.util.retryWithPolicy +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.onStart + +class GetForecastUseCase constructor( + private val weatherRepository: WeatherRepository +) { + + suspend operator fun invoke(lat : Double, long : Double): Flow> = flow { + emit(weatherRepository.getForecast(lat, long)) + }.retryWithPolicy(DefaultRetryPolicy()) + .catch { emit(checkError(it)) } + .onStart { emit(Resource.Loading()) } + +} \ No newline at end of file diff --git a/app/src/main/java/com/plcoding/weatherapp/domain/usecase/GetWeatherUseCase.kt b/app/src/main/java/com/plcoding/weatherapp/domain/usecase/GetWeatherUseCase.kt new file mode 100644 index 0000000..f9ad95b --- /dev/null +++ b/app/src/main/java/com/plcoding/weatherapp/domain/usecase/GetWeatherUseCase.kt @@ -0,0 +1,24 @@ +package com.plcoding.weatherapp.domain.usecase + +import com.plcoding.weatherapp.domain.repository.WeatherRepository +import com.plcoding.weatherapp.domain.util.DefaultRetryPolicy +import com.plcoding.weatherapp.domain.util.Resource +import com.plcoding.weatherapp.domain.util.checkError +import com.plcoding.weatherapp.domain.util.retryWithPolicy +import com.plcoding.weatherapp.domain.weather.WeatherInfo +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.onStart + +class GetWeatherUseCase constructor( + private val weatherRepository: WeatherRepository +) { + + suspend operator fun invoke(lat : Double, long : Double): Flow> = flow { + emit(weatherRepository.getWeatherData(lat, long)) + }.retryWithPolicy(DefaultRetryPolicy()) + .catch { emit(checkError(it)) } + .onStart { emit(Resource.Loading()) } + +} \ No newline at end of file diff --git a/app/src/main/java/com/plcoding/weatherapp/domain/util/Error.kt b/app/src/main/java/com/plcoding/weatherapp/domain/util/Error.kt new file mode 100644 index 0000000..d0c7b6c --- /dev/null +++ b/app/src/main/java/com/plcoding/weatherapp/domain/util/Error.kt @@ -0,0 +1,26 @@ +package com.plcoding.weatherapp.domain.util + +import java.net.ProtocolException +import java.net.SocketTimeoutException +import java.net.UnknownHostException +import javax.net.ssl.SSLHandshakeException + +fun checkError(throwable: Throwable): Resource.Error { + return when (throwable) { + is UnknownHostException -> { + Resource.Error("No internet Connection") + } + is SSLHandshakeException -> { + Resource.Error("SSL handshake error") + } + is SocketTimeoutException -> { + Resource.Error(throwable.localizedMessage ?: "Socket Timeout") + } + is ProtocolException -> { + Resource.Error(throwable.localizedMessage ?: "Protocol Exception") + } + else -> { + Resource.Error(throwable.localizedMessage ?: "Error") + } + } +} diff --git a/app/src/main/java/com/plcoding/weatherapp/domain/util/Resource.kt b/app/src/main/java/com/plcoding/weatherapp/domain/util/Resource.kt index 21dae92..ccee1a2 100644 --- a/app/src/main/java/com/plcoding/weatherapp/domain/util/Resource.kt +++ b/app/src/main/java/com/plcoding/weatherapp/domain/util/Resource.kt @@ -1,6 +1,50 @@ package com.plcoding.weatherapp.domain.util +import android.util.Log +import com.plcoding.weatherapp.data.dto.ForecastDataDto +import com.plcoding.weatherapp.data.dto.ForecastDto +import com.plcoding.weatherapp.data.mappers.toWeatherInfo +import com.plcoding.weatherapp.data.dto.WeatherDto +import com.plcoding.weatherapp.data.mappers.toForecastInfo +import com.plcoding.weatherapp.domain.weather.WeatherInfo +import retrofit2.Response + sealed class Resource(val data: T? = null, val message: String? = null) { - class Success(data: T?): Resource(data) - class Error(message: String, data: T? = null): Resource(data, message) + class Success(data: T?) : Resource(data) + class Error(message: String, data: T? = null) : Resource(data, message) + class Loading(data: T? = null) : Resource(data) +} + +fun checkResponse(response: Response): Resource { + return if (response.isSuccessful) { + response.body()?.let { + Resource.Success(it.toWeatherInfo()) + } ?: run { + Resource.Error("Empty body") + } + } else { + if (response.code().toString().startsWith("5")) { + Resource.Error("Server error") + } else { + Resource.Error(response.errorBody()?.string() ?: "Default Error") + } + } } + +fun checkResponse2(response: Response): Resource { + Log.i("TAG", "checkResponse2: ") + return if (response.isSuccessful) { + response.body()?.let { + Resource.Success(it) + } ?: run { + Resource.Error("Empty body") + } + } else { + if (response.code().toString().startsWith("5")) { + Resource.Error("Server error") + } else { + Resource.Error(response.errorBody()?.string() ?: "Default Error") + } + } +} + diff --git a/app/src/main/java/com/plcoding/weatherapp/domain/util/RetryPolicy.kt b/app/src/main/java/com/plcoding/weatherapp/domain/util/RetryPolicy.kt new file mode 100644 index 0000000..bc90d1d --- /dev/null +++ b/app/src/main/java/com/plcoding/weatherapp/domain/util/RetryPolicy.kt @@ -0,0 +1,35 @@ +package com.plcoding.weatherapp.domain.util + +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.retryWhen +import java.io.IOException + + +interface RetryPolicy { + val numRetries: Long + val delayMillis: Long + val delayFactor: Long +} + +data class DefaultRetryPolicy( + override val numRetries: Long = 3, + override val delayMillis: Long = 400, + override val delayFactor: Long = 1 +) : RetryPolicy + +fun Flow.retryWithPolicy( + retryPolicy: RetryPolicy +): Flow { + var currentDelay = retryPolicy.delayMillis + val delayFactor = retryPolicy.delayFactor + return retryWhen { cause, attempt -> + if (cause is IOException && attempt < retryPolicy.numRetries) { + delay(currentDelay) + currentDelay *= delayFactor + return@retryWhen true + } else { + return@retryWhen false + } + } +} diff --git a/app/src/main/java/com/plcoding/weatherapp/presentation/MainActivity.kt b/app/src/main/java/com/plcoding/weatherapp/presentation/MainActivity.kt index 3445490..8595e4f 100644 --- a/app/src/main/java/com/plcoding/weatherapp/presentation/MainActivity.kt +++ b/app/src/main/java/com/plcoding/weatherapp/presentation/MainActivity.kt @@ -33,6 +33,7 @@ class MainActivity : ComponentActivity() { ActivityResultContracts.RequestMultiplePermissions() ) { viewModel.loadWeatherInfo() + viewModel.loadForecast() } permissionLauncher.launch(arrayOf( Manifest.permission.ACCESS_FINE_LOCATION, diff --git a/app/src/main/java/com/plcoding/weatherapp/presentation/WeatherForecast.kt b/app/src/main/java/com/plcoding/weatherapp/presentation/WeatherForecast.kt index a75d391..2793cd7 100644 --- a/app/src/main/java/com/plcoding/weatherapp/presentation/WeatherForecast.kt +++ b/app/src/main/java/com/plcoding/weatherapp/presentation/WeatherForecast.kt @@ -1,6 +1,10 @@ package com.plcoding.weatherapp.presentation -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.material.Text @@ -9,6 +13,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import java.text.SimpleDateFormat @Composable fun WeatherForecast( @@ -29,12 +34,17 @@ fun WeatherForecast( Spacer(modifier = Modifier.height(16.dp)) LazyRow(content = { items(data) { weatherData -> - HourlyWeatherDisplay( - weatherData = weatherData, - modifier = Modifier - .height(100.dp) - .padding(horizontal = 16.dp) - ) + val date = SimpleDateFormat("yyyy-MM-dd'T'HH:mm").parse(weatherData.time.toString()) + date?.let { + if (it.time > System.currentTimeMillis()) { + HourlyWeatherDisplay( + weatherData = weatherData, + modifier = Modifier + .height(100.dp) + .padding(horizontal = 16.dp) + ) + } + } } }) } diff --git a/app/src/main/java/com/plcoding/weatherapp/presentation/WeatherViewModel.kt b/app/src/main/java/com/plcoding/weatherapp/presentation/WeatherViewModel.kt index e35faee..5a3c124 100644 --- a/app/src/main/java/com/plcoding/weatherapp/presentation/WeatherViewModel.kt +++ b/app/src/main/java/com/plcoding/weatherapp/presentation/WeatherViewModel.kt @@ -1,26 +1,54 @@ package com.plcoding.weatherapp.presentation +import android.util.Log import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.plcoding.weatherapp.domain.location.LocationTracker -import com.plcoding.weatherapp.domain.repository.WeatherRepository +import com.plcoding.weatherapp.domain.usecase.GetForecastUseCase +import com.plcoding.weatherapp.domain.usecase.GetWeatherUseCase import com.plcoding.weatherapp.domain.util.Resource import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class WeatherViewModel @Inject constructor( - private val repository: WeatherRepository, + private val getWeatherUseCase: GetWeatherUseCase, + private val getForecastUseCase: GetForecastUseCase, private val locationTracker: LocationTracker -): ViewModel() { +) : ViewModel() { + private val TAG = "WeatherViewModel" var state by mutableStateOf(WeatherState()) private set + fun loadForecast() { + viewModelScope.launch { + locationTracker.getCurrentLocation()?.let { location -> + getForecastUseCase.invoke(location.latitude,location.longitude).catch { + Log.e(TAG, "loadForecast: ", it) + }.collect { + when(it) { + is Resource.Success -> { + Log.i(TAG, "loadForecast: successful") + } + is Resource.Loading -> { + Log.i(TAG, "loadForecast: loading") + } + is Resource.Error -> { + Log.i(TAG, "loadForecast: error") + } + } + } + } + } + } + fun loadWeatherInfo() { viewModelScope.launch { state = state.copy( @@ -28,20 +56,38 @@ class WeatherViewModel @Inject constructor( error = null ) locationTracker.getCurrentLocation()?.let { location -> - when(val result = repository.getWeatherData(location.latitude, location.longitude)) { - is Resource.Success -> { - state = state.copy( - weatherInfo = result.data, - isLoading = false, - error = null - ) - } - is Resource.Error -> { - state = state.copy( - weatherInfo = null, - isLoading = false, - error = result.message - ) + getWeatherUseCase.invoke(location.latitude, location.longitude).catch { + Log.e("WeatherViewModel", "loadWeatherInfo: ", it) + state = state.copy( + weatherInfo = null, + isLoading = false, + error = it.localizedMessage + ) + }.collect { + when (it) { + is Resource.Success -> { + state = state.copy( + weatherInfo = it.data, + isLoading = false, + error = null + ) + } + + is Resource.Error -> { + state = state.copy( + weatherInfo = null, + isLoading = false, + error = it.message + ) + } + + is Resource.Loading -> { + state = state.copy( + weatherInfo = null, + isLoading = true, + error = null + ) + } } } } ?: kotlin.run {