-
Notifications
You must be signed in to change notification settings - Fork 132
Shipping methods networking #11478
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
Shipping methods networking #11478
Changes from 8 commits
37ae301
8b5c7ba
17485d4
a6eb419
44970bc
577c929
c83d80e
6374ec4
c222a0e
354d9a5
f146ba4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,16 @@ | ||
package com.woocommerce.android.model | ||
|
||
import android.os.Parcelable | ||
import com.woocommerce.android.network.shippingmethods.ShippingMethodsRestClient | ||
import com.woocommerce.android.ui.orders.creation.shipping.ShippingMethodsRepository | ||
import kotlinx.parcelize.Parcelize | ||
|
||
@Parcelize | ||
data class ShippingMethod( | ||
val id: String, | ||
val title: String | ||
) : Parcelable | ||
|
||
fun ShippingMethodsRestClient.ShippingMethodDto.toAppModel(): ShippingMethod { | ||
return ShippingMethod(id = this.id ?: ShippingMethodsRepository.OTHER_ID, title = this.title.orEmpty()) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package com.woocommerce.android.network.shippingmethods | ||
|
||
import org.wordpress.android.fluxc.generated.endpoint.WOOCOMMERCE | ||
import org.wordpress.android.fluxc.model.SiteModel | ||
import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooNetwork | ||
import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooPayload | ||
import org.wordpress.android.fluxc.utils.toWooPayload | ||
import javax.inject.Inject | ||
|
||
class ShippingMethodsRestClient @Inject constructor(private val wooNetwork: WooNetwork) { | ||
suspend fun fetchShippingMethods(site: SiteModel): WooPayload<List<ShippingMethodDto>> { | ||
val url = WOOCOMMERCE.shipping_methods.pathV3 | ||
|
||
return wooNetwork.executeGetGsonRequest( | ||
site = site, | ||
path = url, | ||
clazz = Array<ShippingMethodDto>::class.java, | ||
).toWooPayload { methods -> methods.toList() } | ||
} | ||
|
||
data class ShippingMethodDto( | ||
val id: String? = null, | ||
val title: String? = null, | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package com.woocommerce.android.ui.orders.creation.shipping | ||
|
||
import com.woocommerce.android.model.ShippingMethod | ||
import javax.inject.Inject | ||
|
||
class GetShippingMethodById @Inject constructor( | ||
private val shippingMethodsRepository: ShippingMethodsRepository | ||
) { | ||
suspend operator fun invoke(shippingMethodId: String?): ShippingMethod { | ||
val other = shippingMethodsRepository.getOtherShippingMethod() | ||
if (shippingMethodId == ShippingMethodsRepository.OTHER_ID || shippingMethodId == null) { | ||
return other | ||
} | ||
val result = shippingMethodsRepository.fetchShippingMethods() | ||
return result.model?.firstOrNull { shippingMethod -> shippingMethod.id == shippingMethodId } ?: other | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package com.woocommerce.android.ui.orders.creation.shipping | ||
|
||
import com.woocommerce.android.model.ShippingMethod | ||
import javax.inject.Inject | ||
|
||
class GetShippingMethodsWithOtherValue @Inject constructor( | ||
private val shippingMethodsRepository: ShippingMethodsRepository | ||
) { | ||
suspend operator fun invoke(): Result<List<ShippingMethod>> { | ||
val result = shippingMethodsRepository.fetchShippingMethods() | ||
return when { | ||
result.model != null -> { | ||
val shippingMethodsWithOtherValue = result.model!!.toMutableList().also { | ||
it.add(shippingMethodsRepository.getOtherShippingMethod()) | ||
} | ||
|
||
Result.success(shippingMethodsWithOtherValue) | ||
} | ||
|
||
else -> { | ||
val message = result.error.message ?: "error fetching shipping methods" | ||
Result.failure(Exception(message)) | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package com.woocommerce.android.ui.orders.creation.shipping | ||
|
||
import com.woocommerce.android.R | ||
import com.woocommerce.android.model.ShippingMethod | ||
import com.woocommerce.android.model.toAppModel | ||
import com.woocommerce.android.network.shippingmethods.ShippingMethodsRestClient | ||
import com.woocommerce.android.tools.SelectedSite | ||
import com.woocommerce.android.util.CoroutineDispatchers | ||
import com.woocommerce.android.viewmodel.ResourceProvider | ||
import kotlinx.coroutines.withContext | ||
import org.wordpress.android.fluxc.model.SiteModel | ||
import org.wordpress.android.fluxc.network.BaseRequest | ||
import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooError | ||
import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooErrorType | ||
import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooResult | ||
import javax.inject.Inject | ||
|
||
class ShippingMethodsRepository @Inject constructor( | ||
private val selectedSite: SelectedSite, | ||
private val shippingMethodsRestClient: ShippingMethodsRestClient, | ||
private val resourceProvider: ResourceProvider, | ||
private val dispatchers: CoroutineDispatchers | ||
) { | ||
companion object { | ||
const val OTHER_ID = "other" | ||
} | ||
|
||
suspend fun fetchShippingMethods(site: SiteModel = selectedSite.get()): WooResult<List<ShippingMethod>> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this is a networking PR, I imagine this will be tackled later. But just to make sure: do you plan to add database support for the Methods after fetching? It could be beneficial to avoid requesting all the data every time one of the Use Cases is triggered, especially the ID one. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Good point! I will add a task to improve this and rely more on local data in an upcoming PR |
||
return withContext(dispatchers.io) { | ||
val response = shippingMethodsRestClient.fetchShippingMethods(site) | ||
when { | ||
response.isError -> { | ||
WooResult(response.error) | ||
} | ||
|
||
response.result != null -> { | ||
val shippingMethods = response.result!!.map { dto -> dto.toAppModel() } | ||
WooResult(shippingMethods) | ||
} | ||
|
||
else -> WooResult(WooError(WooErrorType.GENERIC_ERROR, BaseRequest.GenericErrorType.UNKNOWN)) | ||
} | ||
} | ||
} | ||
|
||
fun getOtherShippingMethod(): ShippingMethod { | ||
return ShippingMethod( | ||
id = OTHER_ID, | ||
title = resourceProvider.getString(R.string.other) | ||
) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package com.woocommerce.android.ui.orders.creation.shipping | ||
|
||
import com.woocommerce.android.network.shippingmethods.ShippingMethodsRestClient | ||
import com.woocommerce.android.tools.SelectedSite | ||
import com.woocommerce.android.viewmodel.BaseUnitTest | ||
import com.woocommerce.android.viewmodel.ResourceProvider | ||
import kotlinx.coroutines.ExperimentalCoroutinesApi | ||
import org.assertj.core.api.Assertions.assertThat | ||
import org.junit.Test | ||
import org.mockito.kotlin.any | ||
import org.mockito.kotlin.doReturn | ||
import org.mockito.kotlin.mock | ||
import org.mockito.kotlin.never | ||
import org.mockito.kotlin.verify | ||
import org.mockito.kotlin.whenever | ||
import org.wordpress.android.fluxc.model.SiteModel | ||
import org.wordpress.android.fluxc.network.BaseRequest | ||
import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooError | ||
import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooErrorType | ||
import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooPayload | ||
|
||
@OptIn(ExperimentalCoroutinesApi::class) | ||
class GetShippingMethodByIdTest : BaseUnitTest() { | ||
private val siteModel = SiteModel() | ||
private val selectedSite: SelectedSite = mock { | ||
on { get() } doReturn siteModel | ||
} | ||
private val resourceProvider: ResourceProvider = mock() | ||
private val shippingMethodsRestClient: ShippingMethodsRestClient = mock() | ||
|
||
private val shippingMethodsRepository = ShippingMethodsRepository( | ||
selectedSite = selectedSite, | ||
dispatchers = coroutinesTestRule.testDispatchers, | ||
resourceProvider = resourceProvider, | ||
shippingMethodsRestClient = shippingMethodsRestClient | ||
) | ||
|
||
val sut = GetShippingMethodById(shippingMethodsRepository) | ||
|
||
@Test | ||
fun `when the method is in the result, then return is the expected`() = testBlocking { | ||
val fetchResult = List(3) { i -> | ||
ShippingMethodsRestClient.ShippingMethodDto( | ||
id = "id$i", | ||
title = "title$i", | ||
) | ||
} | ||
whenever(shippingMethodsRestClient.fetchShippingMethods(siteModel)).doReturn(WooPayload(fetchResult)) | ||
whenever(resourceProvider.getString(any())).doReturn("Other") | ||
|
||
val result = sut.invoke("id1") | ||
assertThat(result).isNotNull | ||
assertThat(result.id).isEqualTo("id1") | ||
} | ||
|
||
@Test | ||
fun `when the method id is other, then return is the expected`() = testBlocking { | ||
whenever(resourceProvider.getString(any())).doReturn("Other") | ||
|
||
val result = sut.invoke(ShippingMethodsRepository.OTHER_ID) | ||
|
||
// If is other doesn't need to fetch the values from the API | ||
verify(shippingMethodsRestClient, never()).fetchShippingMethods(siteModel) | ||
assertThat(result).isNotNull | ||
assertThat(result.id).isEqualTo(ShippingMethodsRepository.OTHER_ID) | ||
} | ||
|
||
@Test | ||
fun `when the method is NOT in the result, then return other`() = testBlocking { | ||
val fetchResult = List(3) { i -> | ||
ShippingMethodsRestClient.ShippingMethodDto( | ||
id = "id$i", | ||
title = "title$i", | ||
) | ||
} | ||
whenever(shippingMethodsRestClient.fetchShippingMethods(siteModel)).doReturn(WooPayload(fetchResult)) | ||
whenever(resourceProvider.getString(any())).doReturn("Other") | ||
|
||
val result = sut.invoke("id8") | ||
assertThat(result).isNotNull | ||
assertThat(result.id).isEqualTo(ShippingMethodsRepository.OTHER_ID) | ||
} | ||
|
||
@Test | ||
fun `when fetching shipping methods fail, then return other`() = testBlocking { | ||
val fetchResult = WooError(WooErrorType.GENERIC_ERROR, BaseRequest.GenericErrorType.UNKNOWN) | ||
whenever(shippingMethodsRestClient.fetchShippingMethods(siteModel)).doReturn(WooPayload(fetchResult)) | ||
whenever(resourceProvider.getString(any())).doReturn("Other") | ||
|
||
val result = sut.invoke("id8") | ||
assertThat(result).isNotNull | ||
assertThat(result.id).isEqualTo(ShippingMethodsRepository.OTHER_ID) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out of curiosity, does the API allow querying for a shipping method with a given ID? I'm wondering if we have a way to avoid fetching everything in a request only to filter it locally.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great point! I totally missed the endpoint at first 🤦 . The fix is here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome! Thanks for revisiting the API and finding such a great solution! 🎉