mirror of
https://github.com/hmalik144/Weather-apps.git
synced 2025-12-10 02:05:20 +00:00
- Espresso mock network interceptor added (#9)
- Ability to stub end points in UI tests
This commit is contained in:
@@ -17,7 +17,7 @@ android {
|
||||
targetSdkVersion 30
|
||||
versionCode 5
|
||||
versionName "3.0"
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
testInstrumentationRunner "com.appttude.h_mal.atlas_weather.application.TestRunner"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
|
||||
buildConfigField "String", "ParamOne", "${paramOneEndPoint}"
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.appttude.h_mal.atlas_weather.application
|
||||
|
||||
import androidx.test.espresso.IdlingRegistry
|
||||
import androidx.test.espresso.idling.CountingIdlingResource
|
||||
import com.appttude.h_mal.atlas_weather.R
|
||||
import com.appttude.h_mal.atlas_weather.data.location.MockLocationProvider
|
||||
import com.appttude.h_mal.atlas_weather.data.network.Api
|
||||
import com.appttude.h_mal.atlas_weather.data.network.NetworkModule
|
||||
import com.appttude.h_mal.atlas_weather.data.network.WeatherApi
|
||||
import com.appttude.h_mal.atlas_weather.data.network.interceptors.MockingNetworkInterceptor
|
||||
import com.appttude.h_mal.atlas_weather.data.network.interceptors.NetworkConnectionInterceptor
|
||||
import com.appttude.h_mal.atlas_weather.data.network.interceptors.QueryParamsInterceptor
|
||||
import com.appttude.h_mal.atlas_weather.data.network.networkUtils.loggingInterceptor
|
||||
import java.io.BufferedReader
|
||||
|
||||
class TestAppClass : BaseAppClass() {
|
||||
private val idlingResources = CountingIdlingResource("Data_loader")
|
||||
private val mockingNetworkInterceptor = MockingNetworkInterceptor(idlingResources)
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
IdlingRegistry.getInstance().register(idlingResources)
|
||||
}
|
||||
|
||||
override fun createNetworkModule(): Api {
|
||||
return NetworkModule().invoke<WeatherApi>(
|
||||
NetworkConnectionInterceptor(this),
|
||||
QueryParamsInterceptor(),
|
||||
loggingInterceptor,
|
||||
mockingNetworkInterceptor
|
||||
)
|
||||
}
|
||||
|
||||
override fun createLocationModule() = MockLocationProvider()
|
||||
|
||||
fun stubUrl(url: String, rawPath: String) {
|
||||
val id = resources.getIdentifier(rawPath, "raw", packageName)
|
||||
val iStream = resources.openRawResource(id)
|
||||
val data = iStream.bufferedReader().use(BufferedReader::readText)
|
||||
mockingNetworkInterceptor.addUrlStub(url = url, data = data)
|
||||
}
|
||||
|
||||
fun removeUrlStub(url: String){
|
||||
mockingNetworkInterceptor.removeUrlStub(url = url)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.appttude.h_mal.atlas_weather.application
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import androidx.test.runner.AndroidJUnitRunner
|
||||
|
||||
class TestRunner : AndroidJUnitRunner() {
|
||||
@Throws(
|
||||
InstantiationException::class,
|
||||
IllegalAccessException::class,
|
||||
ClassNotFoundException::class
|
||||
)
|
||||
override fun newApplication(
|
||||
cl: ClassLoader?,
|
||||
className: String?,
|
||||
context: Context?
|
||||
): Application {
|
||||
return super.newApplication(cl, TestAppClass::class.java.name, context)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.location
|
||||
|
||||
import com.appttude.h_mal.atlas_weather.model.types.LocationType
|
||||
|
||||
class MockLocationProvider : LocationProvider {
|
||||
private val latLong = Pair(0.00, 0.00)
|
||||
|
||||
override suspend fun getCurrentLatLong() = latLong
|
||||
override fun getLatLongFromLocationName(location: String) = latLong
|
||||
|
||||
override suspend fun getLocationNameFromLatLong(lat: Double, long: Double, type: LocationType): String {
|
||||
return "Mock Location"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.network.interceptors
|
||||
|
||||
import androidx.test.espresso.idling.CountingIdlingResource
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.Protocol
|
||||
import okhttp3.Response
|
||||
import okhttp3.ResponseBody.Companion.toResponseBody
|
||||
|
||||
class MockingNetworkInterceptor(
|
||||
private val idlingResource: CountingIdlingResource
|
||||
) : Interceptor {
|
||||
|
||||
private var feedMap: MutableMap<String, String> = mutableMapOf()
|
||||
private var urlCallTracker: MutableMap<String, Int> = mutableMapOf()
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
idlingResource.increment()
|
||||
val original = chain.request()
|
||||
val originalHttpUrl = original.url.toString().split("?")[0]
|
||||
|
||||
urlCallTracker.computeIfPresent(originalHttpUrl) { _, j ->
|
||||
j + 1
|
||||
}
|
||||
|
||||
feedMap[originalHttpUrl]?.let { jsonPath ->
|
||||
val body = jsonPath.toResponseBody("application/json".toMediaType())
|
||||
|
||||
val chainResponseBuilder = Response.Builder()
|
||||
.code(200)
|
||||
.protocol(Protocol.HTTP_1_1)
|
||||
.request(original)
|
||||
.message("OK")
|
||||
.body(body)
|
||||
idlingResource.decrement()
|
||||
return chainResponseBuilder.build()
|
||||
}
|
||||
|
||||
idlingResource.decrement()
|
||||
return chain.proceed(original)
|
||||
}
|
||||
|
||||
fun addUrlStub(url: String, data: String) = feedMap.put(url, data)
|
||||
fun removeUrlStub(url: String) = feedMap.remove(url)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.appttude.h_mal.atlas_weather.monoWeather.robot
|
||||
|
||||
import com.appttude.h_mal.atlas_weather.R
|
||||
import com.appttude.h_mal.atlas_weather.utils.BaseTestRobot
|
||||
|
||||
fun homeScreen(func: HomeScreenRobot.() -> Unit) = HomeScreenRobot().apply { func() }
|
||||
class HomeScreenRobot : BaseTestRobot() {
|
||||
fun verifyCurrentTemperature(temperature: Int) = matchText(R.id.temp_main_4, temperature.toString())
|
||||
fun verifyCurrentLocation(location: String) = matchText(R.id.location_main_4, location)
|
||||
fun refresh() = pullToRefresh(R.id.swipe_refresh)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.appttude.h_mal.atlas_weather.monoWeather.testsuite
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.rule.ActivityTestRule
|
||||
import com.appttude.h_mal.atlas_weather.application.TestAppClass
|
||||
import com.appttude.h_mal.atlas_weather.monoWeather.ui.MainActivity
|
||||
import com.appttude.h_mal.atlas_weather.utils.Stubs
|
||||
import org.junit.After
|
||||
import org.junit.Rule
|
||||
|
||||
open class BaseTest() {
|
||||
|
||||
lateinit var testApp: TestAppClass
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
var mActivityTestRule: ActivityTestRule<MainActivity> = object : ActivityTestRule<MainActivity>(MainActivity::class.java) {
|
||||
override fun beforeActivityLaunched() {
|
||||
super.beforeActivityLaunched()
|
||||
|
||||
testApp = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as TestAppClass
|
||||
setupFeed()
|
||||
}
|
||||
}
|
||||
|
||||
fun stubEndpoint(url: String, stub: Stubs) {
|
||||
testApp.stubUrl(url, stub.id)
|
||||
}
|
||||
|
||||
fun unstubEndpoint(url: String) {
|
||||
testApp.removeUrlStub(url)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
}
|
||||
|
||||
open fun setupFeed() {}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.appttude.h_mal.atlas_weather.monoWeather.testsuite
|
||||
|
||||
|
||||
import androidx.test.rule.GrantPermissionRule
|
||||
import com.appttude.h_mal.atlas_weather.monoWeather.robot.homeScreen
|
||||
import com.appttude.h_mal.atlas_weather.utils.Stubs
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class HomePageUITest : BaseTest() {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
var mGrantPermissionRule: GrantPermissionRule =
|
||||
GrantPermissionRule.grant(
|
||||
"android.permission.ACCESS_COARSE_LOCATION")
|
||||
|
||||
override fun setupFeed() {
|
||||
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Valid)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun loadApp_validWeatherResponse_returnsValidPage() {
|
||||
homeScreen {
|
||||
verifyCurrentTemperature(2)
|
||||
verifyCurrentLocation("Mock Location")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.appttude.h_mal.atlas_weather.utils
|
||||
|
||||
import android.view.View
|
||||
import androidx.test.espresso.Espresso.onData
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.UiController
|
||||
import androidx.test.espresso.ViewAction
|
||||
import androidx.test.espresso.ViewInteraction
|
||||
import androidx.test.espresso.action.ViewActions
|
||||
import androidx.test.espresso.action.ViewActions.swipeDown
|
||||
import androidx.test.espresso.assertion.ViewAssertions
|
||||
import androidx.test.espresso.matcher.ViewMatchers
|
||||
import androidx.test.espresso.matcher.ViewMatchers.*
|
||||
import org.hamcrest.CoreMatchers.allOf
|
||||
import org.hamcrest.CoreMatchers.anything
|
||||
import org.hamcrest.Matcher
|
||||
|
||||
open class BaseTestRobot {
|
||||
|
||||
fun fillEditText(resId: Int, text: String): ViewInteraction =
|
||||
onView(withId(resId)).perform(ViewActions.replaceText(text), ViewActions.closeSoftKeyboard())
|
||||
|
||||
fun clickButton(resId: Int): ViewInteraction = onView((withId(resId))).perform(ViewActions.click())
|
||||
|
||||
fun textView(resId: Int): ViewInteraction = onView(withId(resId))
|
||||
|
||||
fun matchText(viewInteraction: ViewInteraction, text: String): ViewInteraction = viewInteraction
|
||||
.check(ViewAssertions.matches(ViewMatchers.withText(text)))
|
||||
|
||||
fun matchText(resId: Int, text: String): ViewInteraction = matchText(textView(resId), text)
|
||||
|
||||
fun clickListItem(listRes: Int, position: Int) {
|
||||
onData(anything())
|
||||
.inAdapterView(allOf(withId(listRes)))
|
||||
.atPosition(position).perform(ViewActions.click())
|
||||
}
|
||||
|
||||
fun pullToRefresh(resId: Int){
|
||||
onView(allOf(withId(resId), isDisplayed())).perform(swipeDown())
|
||||
}
|
||||
|
||||
fun waitFor(delay: Long): ViewAction? {
|
||||
return object : ViewAction {
|
||||
override fun getConstraints(): Matcher<View> = isRoot()
|
||||
override fun getDescription(): String = "wait for $delay milliseconds"
|
||||
override fun perform(uiController: UiController, v: View?) {
|
||||
uiController.loopMainThreadForAtLeast(delay)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.appttude.h_mal.atlas_weather.utils
|
||||
|
||||
val FALLBACK_TIME: Long = 0L
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.appttude.h_mal.atlas_weather.utils
|
||||
|
||||
enum class Stubs(
|
||||
val id: String
|
||||
) {
|
||||
Valid("valid_response"),
|
||||
Invalid("invalid_response")
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
package com.appttude.h_mal.atlas_weather.application
|
||||
|
||||
import android.app.Application
|
||||
import androidx.test.espresso.idling.CountingIdlingResource
|
||||
import com.appttude.h_mal.atlas_weather.data.location.LocationProvider
|
||||
import com.appttude.h_mal.atlas_weather.data.location.LocationProviderImpl
|
||||
import com.appttude.h_mal.atlas_weather.data.network.Api
|
||||
import com.appttude.h_mal.atlas_weather.data.network.NetworkModule
|
||||
import com.appttude.h_mal.atlas_weather.data.network.WeatherApi
|
||||
import com.appttude.h_mal.atlas_weather.data.network.interceptors.NetworkConnectionInterceptor
|
||||
import com.appttude.h_mal.atlas_weather.data.network.interceptors.QueryParamsInterceptor
|
||||
@@ -15,7 +16,6 @@ import com.appttude.h_mal.atlas_weather.helper.ServicesHelper
|
||||
import com.appttude.h_mal.atlas_weather.viewmodel.ApplicationViewModelFactory
|
||||
import com.google.gson.Gson
|
||||
import org.kodein.di.Kodein
|
||||
import org.kodein.di.KodeinAware
|
||||
import org.kodein.di.android.x.androidXModule
|
||||
import org.kodein.di.generic.bind
|
||||
import org.kodein.di.generic.instance
|
||||
@@ -24,30 +24,16 @@ import org.kodein.di.generic.singleton
|
||||
|
||||
const val LOCATION_PERMISSION_REQUEST = 505
|
||||
|
||||
class AppClass : Application(), KodeinAware {
|
||||
class AppClass : BaseAppClass() {
|
||||
|
||||
companion object {
|
||||
// idling resource to be used for espresso testing
|
||||
// when we need to wait for async operations to complete
|
||||
val idlingResources = CountingIdlingResource("Data_loader")
|
||||
override fun createNetworkModule(): Api {
|
||||
return NetworkModule().invoke<WeatherApi>(
|
||||
NetworkConnectionInterceptor(this),
|
||||
QueryParamsInterceptor(),
|
||||
loggingInterceptor
|
||||
)
|
||||
}
|
||||
|
||||
// Kodein creation of modules to be retrieve within the app
|
||||
override val kodein = Kodein.lazy {
|
||||
import(androidXModule(this@AppClass))
|
||||
|
||||
bind() from singleton { Gson() }
|
||||
bind() from singleton { NetworkConnectionInterceptor(instance()) }
|
||||
bind() from singleton { QueryParamsInterceptor() }
|
||||
bind() from singleton { loggingInterceptor }
|
||||
bind() from singleton { WeatherApi("https://api.openweathermap.org/data/2.5/", instance(), instance(), instance()) }
|
||||
bind() from singleton { AppDatabase(instance()) }
|
||||
bind() from singleton { PreferenceProvider(instance()) }
|
||||
bind() from singleton { RepositoryImpl(instance(), instance(), instance()) }
|
||||
bind() from singleton { SettingsRepositoryImpl(instance()) }
|
||||
bind() from singleton { LocationProviderImpl(instance()) }
|
||||
bind() from singleton { ServicesHelper(instance(), instance(), instance()) }
|
||||
bind() from provider { ApplicationViewModelFactory(instance(), instance()) }
|
||||
}
|
||||
override fun createLocationModule() = LocationProviderImpl(this)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.appttude.h_mal.atlas_weather.application
|
||||
|
||||
import android.app.Application
|
||||
import androidx.test.espresso.idling.CountingIdlingResource
|
||||
import com.appttude.h_mal.atlas_weather.data.location.LocationProvider
|
||||
import com.appttude.h_mal.atlas_weather.data.location.LocationProviderImpl
|
||||
import com.appttude.h_mal.atlas_weather.data.network.Api
|
||||
import com.appttude.h_mal.atlas_weather.data.network.NetworkModule
|
||||
import com.appttude.h_mal.atlas_weather.data.network.WeatherApi
|
||||
import com.appttude.h_mal.atlas_weather.data.network.interceptors.NetworkConnectionInterceptor
|
||||
import com.appttude.h_mal.atlas_weather.data.network.interceptors.QueryParamsInterceptor
|
||||
import com.appttude.h_mal.atlas_weather.data.network.networkUtils.loggingInterceptor
|
||||
import com.appttude.h_mal.atlas_weather.data.prefs.PreferenceProvider
|
||||
import com.appttude.h_mal.atlas_weather.data.repository.RepositoryImpl
|
||||
import com.appttude.h_mal.atlas_weather.data.repository.SettingsRepositoryImpl
|
||||
import com.appttude.h_mal.atlas_weather.data.room.AppDatabase
|
||||
import com.appttude.h_mal.atlas_weather.helper.ServicesHelper
|
||||
import com.appttude.h_mal.atlas_weather.viewmodel.ApplicationViewModelFactory
|
||||
import com.google.gson.Gson
|
||||
import org.kodein.di.Kodein
|
||||
import org.kodein.di.KodeinAware
|
||||
import org.kodein.di.android.x.androidXModule
|
||||
import org.kodein.di.generic.bind
|
||||
import org.kodein.di.generic.instance
|
||||
import org.kodein.di.generic.provider
|
||||
import org.kodein.di.generic.singleton
|
||||
|
||||
abstract class BaseAppClass : Application(), KodeinAware {
|
||||
|
||||
// Kodein creation of modules to be retrieve within the app
|
||||
override val kodein = Kodein.lazy {
|
||||
import(androidXModule(this@BaseAppClass))
|
||||
|
||||
bind() from singleton { createNetworkModule() as WeatherApi}
|
||||
bind() from singleton { createLocationModule() }
|
||||
|
||||
bind() from singleton { Gson() }
|
||||
bind() from singleton { AppDatabase(instance()) }
|
||||
bind() from singleton { PreferenceProvider(instance()) }
|
||||
bind() from singleton { RepositoryImpl(instance(), instance(), instance()) }
|
||||
bind() from singleton { SettingsRepositoryImpl(instance()) }
|
||||
bind() from singleton { ServicesHelper(instance(), instance(), instance()) }
|
||||
bind() from provider { ApplicationViewModelFactory(instance(), instance()) }
|
||||
}
|
||||
|
||||
abstract fun createNetworkModule() : Api
|
||||
abstract fun createLocationModule() : LocationProvider
|
||||
|
||||
}
|
||||
@@ -80,8 +80,6 @@ class LocationProviderImpl(
|
||||
handlerThread.start()
|
||||
// Now get the Looper from the HandlerThread
|
||||
// NOTE: This call will block until the HandlerThread gets control and initializes its Looper
|
||||
// Now get the Looper from the HandlerThread
|
||||
// NOTE: This call will block until the HandlerThread gets control and initializes its Looper
|
||||
val looper = handlerThread.looper
|
||||
|
||||
return suspendCoroutine { cont ->
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.network
|
||||
|
||||
interface Api
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.network
|
||||
|
||||
import com.appttude.h_mal.atlas_weather.data.network.networkUtils.buildOkHttpClient
|
||||
import com.appttude.h_mal.atlas_weather.data.network.networkUtils.createRetrofit
|
||||
import okhttp3.Interceptor
|
||||
|
||||
open class BaseNetworkModule {
|
||||
// Declare the method we want/can change (no annotations)
|
||||
open fun baseUrl() = "/"
|
||||
|
||||
inline fun <reified T: Api> invoke(
|
||||
vararg interceptors: Interceptor
|
||||
): Api {
|
||||
|
||||
val okHttpClient = buildOkHttpClient(*interceptors)
|
||||
|
||||
return createRetrofit(
|
||||
baseUrl(),
|
||||
okHttpClient,
|
||||
T::class.java
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.network
|
||||
|
||||
class NetworkModule : BaseNetworkModule() {
|
||||
override fun baseUrl(): String = "https://api.openweathermap.org/data/2.5/"
|
||||
}
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.network
|
||||
|
||||
import com.appttude.h_mal.atlas_weather.data.network.interceptors.NetworkConnectionInterceptor
|
||||
import com.appttude.h_mal.atlas_weather.data.network.interceptors.QueryParamsInterceptor
|
||||
import com.appttude.h_mal.atlas_weather.data.network.networkUtils.buildOkHttpClient
|
||||
import com.appttude.h_mal.atlas_weather.data.network.networkUtils.createRetrofit
|
||||
import com.appttude.h_mal.atlas_weather.data.network.response.forecast.WeatherResponse
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Response
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Query
|
||||
|
||||
|
||||
interface WeatherApi {
|
||||
interface WeatherApi: Api {
|
||||
|
||||
@GET("onecall?")
|
||||
suspend fun getFromApi(
|
||||
@@ -21,27 +16,5 @@ interface WeatherApi {
|
||||
@Query("units") units: String = "metric"
|
||||
): Response<WeatherResponse>
|
||||
|
||||
// invoke method creating an invocation of the api call
|
||||
companion object{
|
||||
operator fun invoke(
|
||||
baseUrl: String,
|
||||
networkConnectionInterceptor: NetworkConnectionInterceptor,
|
||||
queryParamsInterceptor: QueryParamsInterceptor,
|
||||
loggingInterceptor: HttpLoggingInterceptor
|
||||
) : WeatherApi {
|
||||
|
||||
val okHttpClient = buildOkHttpClient(
|
||||
networkConnectionInterceptor,
|
||||
queryParamsInterceptor,
|
||||
loggingInterceptor
|
||||
)
|
||||
|
||||
return createRetrofit(
|
||||
baseUrl,
|
||||
okHttpClient,
|
||||
WeatherApi::class.java
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import java.io.IOException
|
||||
|
||||
class NetworkConnectionInterceptor(
|
||||
context: Context
|
||||
) : Interceptor {
|
||||
) : NetworkInterceptor {
|
||||
|
||||
private val applicationContext = context.applicationContext
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.network.interceptors
|
||||
|
||||
import okhttp3.Interceptor
|
||||
|
||||
interface NetworkInterceptor : Interceptor
|
||||
@@ -14,14 +14,13 @@ class QueryParamsInterceptor : Interceptor{
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val original = chain.request()
|
||||
val originalHttpUrl = original.url
|
||||
|
||||
val url = originalHttpUrl.newBuilder()
|
||||
val url = original.url.newBuilder()
|
||||
.addQueryParameter("appid", id)
|
||||
.build()
|
||||
|
||||
// Request customization: add request headers
|
||||
val requestBuilder= original.newBuilder().url(url)
|
||||
val requestBuilder = original.newBuilder().url(url)
|
||||
|
||||
val request: Request = requestBuilder.build()
|
||||
return chain.proceed(request)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.network.networkUtils
|
||||
|
||||
import com.appttude.h_mal.atlas_weather.data.network.interceptors.NetworkConnectionInterceptor
|
||||
import com.appttude.h_mal.atlas_weather.data.network.interceptors.NetworkInterceptor
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
@@ -13,7 +14,6 @@ val loggingInterceptor = HttpLoggingInterceptor().apply {
|
||||
}
|
||||
|
||||
fun buildOkHttpClient(
|
||||
networkConnectionInterceptor: NetworkConnectionInterceptor,
|
||||
vararg interceptor: Interceptor,
|
||||
timeoutSeconds: Long = 30L
|
||||
): OkHttpClient {
|
||||
@@ -21,11 +21,14 @@ fun buildOkHttpClient(
|
||||
val builder = OkHttpClient.Builder()
|
||||
|
||||
interceptor.forEach {
|
||||
builder.addInterceptor(it)
|
||||
if (it is NetworkInterceptor) {
|
||||
builder.addNetworkInterceptor(it)
|
||||
} else {
|
||||
builder.addInterceptor(it)
|
||||
}
|
||||
}
|
||||
|
||||
builder.addNetworkInterceptor(networkConnectionInterceptor)
|
||||
.connectTimeout(timeoutSeconds, TimeUnit.SECONDS)
|
||||
builder.connectTimeout(timeoutSeconds, TimeUnit.SECONDS)
|
||||
.writeTimeout(timeoutSeconds, TimeUnit.SECONDS)
|
||||
.readTimeout(timeoutSeconds, TimeUnit.SECONDS)
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@ import com.appttude.h_mal.atlas_weather.data.prefs.LOCATION_CONST
|
||||
import com.appttude.h_mal.atlas_weather.data.prefs.PreferenceProvider
|
||||
import com.appttude.h_mal.atlas_weather.data.room.AppDatabase
|
||||
import com.appttude.h_mal.atlas_weather.data.room.entity.EntityItem
|
||||
import com.appttude.h_mal.atlas_weather.utils.FALLBACK_TIME
|
||||
|
||||
|
||||
private const val FIVE_MINS = 300000L
|
||||
class RepositoryImpl(
|
||||
private val api: WeatherApi,
|
||||
private val db: AppDatabase,
|
||||
@@ -53,7 +53,7 @@ class RepositoryImpl(
|
||||
?: return true
|
||||
val difference = System.currentTimeMillis() - lastSaved
|
||||
|
||||
return difference > FIVE_MINS
|
||||
return difference > FALLBACK_TIME
|
||||
}
|
||||
|
||||
override fun saveLastSavedAt(locationName: String) {
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.appttude.h_mal.atlas_weather.utils
|
||||
|
||||
val FALLBACK_TIME: Long = 300000L
|
||||
4
app/src/main/res/raw/invalid_response.json
Normal file
4
app/src/main/res/raw/invalid_response.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"cod": 401,
|
||||
"message": "Invalid API key. Please see http://openweathermap.org/faq#error401 for more info."
|
||||
}
|
||||
349
app/src/main/res/raw/valid_response.json
Normal file
349
app/src/main/res/raw/valid_response.json
Normal file
@@ -0,0 +1,349 @@
|
||||
{
|
||||
"lat": 51.51,
|
||||
"lon": -0.13,
|
||||
"timezone": "Europe/London",
|
||||
"timezone_offset": 3600,
|
||||
"current": {
|
||||
"dt": 1648932980,
|
||||
"sunrise": 1648877626,
|
||||
"sunset": 1648924449,
|
||||
"temp": 2.14,
|
||||
"feels_like": 0.13,
|
||||
"pressure": 1025,
|
||||
"humidity": 79,
|
||||
"dew_point": -0.99,
|
||||
"uvi": 0,
|
||||
"clouds": 72,
|
||||
"visibility": 10000,
|
||||
"wind_speed": 1.92,
|
||||
"wind_deg": 69,
|
||||
"wind_gust": 4.81,
|
||||
"weather": [
|
||||
{
|
||||
"id": 803,
|
||||
"main": "Clouds",
|
||||
"description": "broken clouds",
|
||||
"icon": "04n"
|
||||
}
|
||||
]
|
||||
},
|
||||
"daily": [
|
||||
{
|
||||
"dt": 1648900800,
|
||||
"sunrise": 1648877626,
|
||||
"sunset": 1648924449,
|
||||
"moonrise": 1648880220,
|
||||
"moonset": 1648930140,
|
||||
"moon_phase": 0.04,
|
||||
"temp": {
|
||||
"day": 8.5,
|
||||
"min": 1,
|
||||
"max": 8.97,
|
||||
"night": 2.43,
|
||||
"eve": 4.76,
|
||||
"morn": 1
|
||||
},
|
||||
"feels_like": {
|
||||
"day": 6.21,
|
||||
"night": 0.54,
|
||||
"eve": 2.22,
|
||||
"morn": -1.74
|
||||
},
|
||||
"pressure": 1022,
|
||||
"humidity": 35,
|
||||
"dew_point": -6.02,
|
||||
"wind_speed": 4.07,
|
||||
"wind_deg": 41,
|
||||
"wind_gust": 7.58,
|
||||
"weather": [
|
||||
{
|
||||
"id": 804,
|
||||
"main": "Clouds",
|
||||
"description": "overcast clouds",
|
||||
"icon": "04d"
|
||||
}
|
||||
],
|
||||
"clouds": 100,
|
||||
"pop": 0.27,
|
||||
"uvi": 3.03
|
||||
},
|
||||
{
|
||||
"dt": 1648987200,
|
||||
"sunrise": 1648963890,
|
||||
"sunset": 1649010949,
|
||||
"moonrise": 1648967460,
|
||||
"moonset": 1649020980,
|
||||
"moon_phase": 0.07,
|
||||
"temp": {
|
||||
"day": 8.92,
|
||||
"min": 0.99,
|
||||
"max": 9.47,
|
||||
"night": 5.62,
|
||||
"eve": 8.19,
|
||||
"morn": 1.01
|
||||
},
|
||||
"feels_like": {
|
||||
"day": 7.63,
|
||||
"night": 2.63,
|
||||
"eve": 6.61,
|
||||
"morn": -0.71
|
||||
},
|
||||
"pressure": 1026,
|
||||
"humidity": 38,
|
||||
"dew_point": -4.76,
|
||||
"wind_speed": 3.99,
|
||||
"wind_deg": 250,
|
||||
"wind_gust": 9.75,
|
||||
"weather": [
|
||||
{
|
||||
"id": 802,
|
||||
"main": "Clouds",
|
||||
"description": "scattered clouds",
|
||||
"icon": "03d"
|
||||
}
|
||||
],
|
||||
"clouds": 30,
|
||||
"pop": 0,
|
||||
"uvi": 3.04
|
||||
},
|
||||
{
|
||||
"dt": 1649073600,
|
||||
"sunrise": 1649050154,
|
||||
"sunset": 1649097449,
|
||||
"moonrise": 1649054880,
|
||||
"moonset": 1649111880,
|
||||
"moon_phase": 0.1,
|
||||
"temp": {
|
||||
"day": 9.43,
|
||||
"min": 4.54,
|
||||
"max": 12.17,
|
||||
"night": 10.64,
|
||||
"eve": 11.38,
|
||||
"morn": 4.91
|
||||
},
|
||||
"feels_like": {
|
||||
"day": 6.79,
|
||||
"night": 10.01,
|
||||
"eve": 10.87,
|
||||
"morn": 0.46
|
||||
},
|
||||
"pressure": 1011,
|
||||
"humidity": 93,
|
||||
"dew_point": 8.41,
|
||||
"wind_speed": 6.87,
|
||||
"wind_deg": 245,
|
||||
"wind_gust": 15.86,
|
||||
"weather": [
|
||||
{
|
||||
"id": 500,
|
||||
"main": "Rain",
|
||||
"description": "light rain",
|
||||
"icon": "10d"
|
||||
}
|
||||
],
|
||||
"clouds": 100,
|
||||
"pop": 0.92,
|
||||
"rain": 1.95,
|
||||
"uvi": 1.07
|
||||
},
|
||||
{
|
||||
"dt": 1649160000,
|
||||
"sunrise": 1649136419,
|
||||
"sunset": 1649183949,
|
||||
"moonrise": 1649142480,
|
||||
"moonset": 0,
|
||||
"moon_phase": 0.13,
|
||||
"temp": {
|
||||
"day": 13.7,
|
||||
"min": 9.71,
|
||||
"max": 14.06,
|
||||
"night": 9.71,
|
||||
"eve": 11.64,
|
||||
"morn": 10.05
|
||||
},
|
||||
"feels_like": {
|
||||
"day": 12.85,
|
||||
"night": 6.63,
|
||||
"eve": 10.66,
|
||||
"morn": 9.25
|
||||
},
|
||||
"pressure": 1009,
|
||||
"humidity": 66,
|
||||
"dew_point": 7.47,
|
||||
"wind_speed": 6.8,
|
||||
"wind_deg": 258,
|
||||
"wind_gust": 13,
|
||||
"weather": [
|
||||
{
|
||||
"id": 500,
|
||||
"main": "Rain",
|
||||
"description": "light rain",
|
||||
"icon": "10d"
|
||||
}
|
||||
],
|
||||
"clouds": 99,
|
||||
"pop": 0.36,
|
||||
"rain": 0.13,
|
||||
"uvi": 2.59
|
||||
},
|
||||
{
|
||||
"dt": 1649246400,
|
||||
"sunrise": 1649222684,
|
||||
"sunset": 1649270449,
|
||||
"moonrise": 1649230500,
|
||||
"moonset": 1649202540,
|
||||
"moon_phase": 0.17,
|
||||
"temp": {
|
||||
"day": 12.97,
|
||||
"min": 8.53,
|
||||
"max": 13.85,
|
||||
"night": 8.53,
|
||||
"eve": 9.68,
|
||||
"morn": 9.51
|
||||
},
|
||||
"feels_like": {
|
||||
"day": 11.94,
|
||||
"night": 4.36,
|
||||
"eve": 6.3,
|
||||
"morn": 6.57
|
||||
},
|
||||
"pressure": 996,
|
||||
"humidity": 62,
|
||||
"dew_point": 5.84,
|
||||
"wind_speed": 9.57,
|
||||
"wind_deg": 260,
|
||||
"wind_gust": 18.43,
|
||||
"weather": [
|
||||
{
|
||||
"id": 500,
|
||||
"main": "Rain",
|
||||
"description": "light rain",
|
||||
"icon": "10d"
|
||||
}
|
||||
],
|
||||
"clouds": 100,
|
||||
"pop": 0.86,
|
||||
"rain": 1.95,
|
||||
"uvi": 3.29
|
||||
},
|
||||
{
|
||||
"dt": 1649332800,
|
||||
"sunrise": 1649308949,
|
||||
"sunset": 1649356949,
|
||||
"moonrise": 1649319000,
|
||||
"moonset": 1649292960,
|
||||
"moon_phase": 0.2,
|
||||
"temp": {
|
||||
"day": 9.25,
|
||||
"min": 4.63,
|
||||
"max": 10.29,
|
||||
"night": 7.17,
|
||||
"eve": 8.81,
|
||||
"morn": 4.63
|
||||
},
|
||||
"feels_like": {
|
||||
"day": 6.57,
|
||||
"night": 5.9,
|
||||
"eve": 7.06,
|
||||
"morn": -0.26
|
||||
},
|
||||
"pressure": 1000,
|
||||
"humidity": 33,
|
||||
"dew_point": -6.33,
|
||||
"wind_speed": 9.1,
|
||||
"wind_deg": 258,
|
||||
"wind_gust": 21.32,
|
||||
"weather": [
|
||||
{
|
||||
"id": 803,
|
||||
"main": "Clouds",
|
||||
"description": "broken clouds",
|
||||
"icon": "04d"
|
||||
}
|
||||
],
|
||||
"clouds": 66,
|
||||
"pop": 0,
|
||||
"uvi": 3.59
|
||||
},
|
||||
{
|
||||
"dt": 1649419200,
|
||||
"sunrise": 1649395215,
|
||||
"sunset": 1649443449,
|
||||
"moonrise": 1649408100,
|
||||
"moonset": 1649382900,
|
||||
"moon_phase": 0.23,
|
||||
"temp": {
|
||||
"day": 8,
|
||||
"min": 3.94,
|
||||
"max": 8.91,
|
||||
"night": 4.79,
|
||||
"eve": 7.09,
|
||||
"morn": 3.94
|
||||
},
|
||||
"feels_like": {
|
||||
"day": 6.02,
|
||||
"night": 1.81,
|
||||
"eve": 4.59,
|
||||
"morn": 2.23
|
||||
},
|
||||
"pressure": 1000,
|
||||
"humidity": 39,
|
||||
"dew_point": -5.12,
|
||||
"wind_speed": 4.52,
|
||||
"wind_deg": 354,
|
||||
"wind_gust": 9.29,
|
||||
"weather": [
|
||||
{
|
||||
"id": 500,
|
||||
"main": "Rain",
|
||||
"description": "light rain",
|
||||
"icon": "10d"
|
||||
}
|
||||
],
|
||||
"clouds": 100,
|
||||
"pop": 0.8,
|
||||
"rain": 1.51,
|
||||
"uvi": 4
|
||||
},
|
||||
{
|
||||
"dt": 1649505600,
|
||||
"sunrise": 1649481482,
|
||||
"sunset": 1649529949,
|
||||
"moonrise": 1649497920,
|
||||
"moonset": 1649472180,
|
||||
"moon_phase": 0.25,
|
||||
"temp": {
|
||||
"day": 8.7,
|
||||
"min": 1.91,
|
||||
"max": 9.35,
|
||||
"night": 5.29,
|
||||
"eve": 7.66,
|
||||
"morn": 1.91
|
||||
},
|
||||
"feels_like": {
|
||||
"day": 5.76,
|
||||
"night": 2.55,
|
||||
"eve": 4.78,
|
||||
"morn": -1.89
|
||||
},
|
||||
"pressure": 1016,
|
||||
"humidity": 36,
|
||||
"dew_point": -5.69,
|
||||
"wind_speed": 5.65,
|
||||
"wind_deg": 314,
|
||||
"wind_gust": 11.58,
|
||||
"weather": [
|
||||
{
|
||||
"id": 500,
|
||||
"main": "Rain",
|
||||
"description": "light rain",
|
||||
"icon": "10d"
|
||||
}
|
||||
],
|
||||
"clouds": 55,
|
||||
"pop": 0.3,
|
||||
"rain": 0.22,
|
||||
"uvi": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user