1 min read
Customise the Product Details screen to show related products based on custom data
Last Updated - Platform 22.0.1 - SDK 17.0.1This guide will walk through how you can customise the ProductViewDataSource
to present a Product Carousel using custom data.
This would require a backend customisation and for the data to come from somewhere.
Mapping the Data
Before we begin we're going to assume that the backend has been customised to send an array of product identifiers. This data will be returned within the custom data payload of the product object.
{ ... "customData": { "relatedProducts": [ { "productId": "awesome-dress", "listingId": "awesome-dress-red" }, { "productId": "another-dress", "listingId": "another-dress-purple" } ] }}
- Create a new file for your custom
ProductData
somewhere likeSources/Product/Models
. - Create a struct
CustomProductData
and implementDecodable
.
struct CustomProductData: Decodable { struct ProductIdentifierData: Decodable { var productId: String? var listingId: String? }
var relatedProducts: [ProductIdentifierData]?}
It's not a bad idea to make everything optional in data models incase the backend has issues.
Right now this data is raw data and harder to work with, so it's a good idea to map this to a more usable format.
- Create a new file for a custom domain model for
Product
in the same folder. - Create another struct
CustomProduct
but implementHashable
instead.
struct CustomProduct: Hashable { var relatedProductIds: [ProductIdentifier]}
- Create a new file for a custom
ProductMapper
somewhere likeSources/Product/Mappers
. - Subclass the
PoqProductDataMapper
and map your custom data using thecustomDataMapper
.
This can be done directly using the initialiser, without a subclass, but it may be better to keep code clearly separate.
import PoqCatalogueClientimport PoqFoundation
class CustomProductMapper: PoqProductMapper { init() { super.init { customData in // Override the custom data mapper. customData?.decode(CustomProductData.self).flatMap { data in CustomProduct( // Compact map the raw product identifiers into domain ProductIdentifiers. relatedProductIds: data.relatedProducts?.compactMap { id in guard let productId = id.productId else { return nil } return ProductIdentifier(productId: productId, listingId: id.listingId) } ?? [] ) } } }}
- Inject your custom mapper in the
AppModule
in thedidAddToPlatform
orsetUpDependencies
function.
Container.shared.mappers.productMapper.replace { CustomProductMapper() }
Great, now we have the data in a usable format within product.customData
.
Now let's use that data.
Adding the Carousel
For this we're going to customise the ProductViewDataSource
.
There are two variations of this so it's important to pick the one best for your app:
- If you have a variant Wishlist or want to present all the inline form pickers then subclass the
PoqInlineProductViewDataSource
. - Otherwise subclass the
PoqProductViewDataSource
.
- Create a new file for the custom
ProductViewDataSource
somewhere likeSources/ProductDetails/Views
. - Subclass your desired data source based on above and override the
makeViews
function.
import PoqCatalogueimport PoqProductDetail
class CustomProductViewDataSource: PoqProductViewDataSource { override func makeViews(for product: ProductViewData) -> [UIView] { var views = super.makeViews(for: product) return views }}
- Implement a function to create and return a cached Product Carousel using your custom data.
var relatedProductsView: UIView?
...
func makeRelatedProductsView(for product: ProductViewData) -> UIView? { guard let customData = product.customData(CustomProduct.self) else { return nil } guard !customData.relatedProductIds.isEmpty else { return nil }
// Return the cached carousel view (because the products do not change). return relatedProductsView ?? { // If not cached yet then build and cache the carousel. let view = ProductCarouselBuilder() .build(source: .products(ids: customData.relatedProductIds), title: "Related Products".localized()) .inSectionView(hasViewAllButton: true) relatedProductsView = view return view }()}
- Insert your carousel within the
makeViews
function where you want it within the array.
override func makeViews(for product: ProductViewData) -> [UIView] { var views = super.makeViews(for: product) views.insert return views}
- Optionally, if you don't like all the carousels you can remove the recently viewed carousel by returning nil from the function.
override func makeRecentlyViewedView(with product: ProductViewData) -> UIView? { nil }
- Inject your custom data source in the
AppModule
in thedidAddToPlatform
orsetUpProductDetails
function
Container.shared.dataSource.productViewDataSource.replace { CustomProductViewDataSource() }
Success! The customisation is complete and you can now see the related products carousel.