Skip to content

refactor : update project to match clean architecture best practices #8

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .idea/inspectionProfiles/Project_Default.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -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<String>,
@field:Json(name = "temperature_2m_max")
val maxTemperatures: List<Double>,
@field:Json(name = "temperature_2m_min")
val minTemperatures : List<Double>,
@field:Json(name = "weathercode")
val weatherCodes: List<Int>,
@field:Json(name = "sunrise")
val sunrise: List<String>,
@field:Json(name = "sunset")
val sunset: List<String>,
@field:Json(name = "rain_sum")
val rainSum: List<Double>,
@field:Json(name = "showers_sum")
val showersSum: List<Double>,
@field:Json(name = "snowfall_sum")
val snowfallSum: List<Double>

)
Original file line number Diff line number Diff line change
@@ -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

)
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.plcoding.weatherapp.data.remote
package com.plcoding.weatherapp.data.dto

import com.squareup.moshi.Json

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.plcoding.weatherapp.data.remote
package com.plcoding.weatherapp.data.dto

import com.squareup.moshi.Json

Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -49,4 +51,8 @@ fun WeatherDto.toWeatherInfo(): WeatherInfo {
weatherDataPerDay = weatherDataMap,
currentWeatherData = currentWeatherData
)
}

fun ForecastDto.toForecastInfo() : ForecastDataDto {
return this.forecastDataDto
}
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -9,5 +12,12 @@ interface WeatherApi {
suspend fun getWeatherData(
@Query("latitude") lat: Double,
@Query("longitude") long: Double
): WeatherDto
): Response<WeatherDto>

@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<ForecastDto>

}
Original file line number Diff line number Diff line change
@@ -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<WeatherInfo> {
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<WeatherInfo> =
checkResponse(api.getWeatherData(lat, long))

override suspend fun getForecast(lat: Double, long: Double): Resource<ForecastDto> =
checkResponse2(api.getDailyForecast(lat,long))
}
38 changes: 37 additions & 1 deletion app/src/main/java/com/plcoding/weatherapp/di/AppModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)

}
Original file line number Diff line number Diff line change
@@ -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<WeatherInfo>
suspend fun getForecast(lat: Double, long: Double): Resource<ForecastDto>
}
Original file line number Diff line number Diff line change
@@ -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<Resource<ForecastDto>> = flow {
emit(weatherRepository.getForecast(lat, long))
}.retryWithPolicy(DefaultRetryPolicy())
.catch { emit(checkError(it)) }
.onStart { emit(Resource.Loading()) }

}
Original file line number Diff line number Diff line change
@@ -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<Resource<WeatherInfo>> = flow {
emit(weatherRepository.getWeatherData(lat, long))
}.retryWithPolicy(DefaultRetryPolicy())
.catch { emit(checkError(it)) }
.onStart { emit(Resource.Loading()) }

}
26 changes: 26 additions & 0 deletions app/src/main/java/com/plcoding/weatherapp/domain/util/Error.kt
Original file line number Diff line number Diff line change
@@ -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 <T> checkError(throwable: Throwable): Resource.Error<T> {
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")
}
}
}
48 changes: 46 additions & 2 deletions app/src/main/java/com/plcoding/weatherapp/domain/util/Resource.kt
Original file line number Diff line number Diff line change
@@ -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<T>(val data: T? = null, val message: String? = null) {
class Success<T>(data: T?): Resource<T>(data)
class Error<T>(message: String, data: T? = null): Resource<T>(data, message)
class Success<T>(data: T?) : Resource<T>(data)
class Error<T>(message: String, data: T? = null) : Resource<T>(data, message)
class Loading<T>(data: T? = null) : Resource<T>(data)
}

fun checkResponse(response: Response<WeatherDto>): Resource<WeatherInfo> {
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<ForecastDto>): Resource<ForecastDto> {
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")
}
}
}

Loading