Supplier Initialisation
Supplier minimum requirement
You are a retailer and you have your own groceries list that need to be synchronized with the Mealz groceries list.
SupplierKey - base64 key to set information
This will be provided by Mealz during implementation. This will include information such as PROD vs UAT & your unique company identifier.
If you just want to play around, you can use the sample Mealz Store:
let supplierKey = "ewoJInN1cHBsaWVyX2lkIjogIjE0IiwKCSJwbGF1c2libGVfZG9tYWluZSI6ICJtaWFtLnRlc3QiLAoJIm1pYW1fb3JpZ2luIjogIm1pYW0iLAoJIm9yaWdpbiI6ICJtaWFtIiwKCSJtaWFtX2Vudmlyb25tZW50IjogIlVBVCIKfQ=="
which you'll see decodes from base 64 to this:
{
"supplier_id": "14",
"plausible_domaine": "miam.test",
"miam_origin": "miam",
"origin": "miam",
"miam_environment": "UAT"
}
Your keys will be provided by our Development team
Basic implementation
There are several options to configure to handle authless users. Here is a basic implemtation where the Mealz initialization process will only start after the user is logged in and has selected a valid store. It will then need to complete the basket synchronization
First to init Mealz we need your application context and also your supplier key
Two key have been send to you: one for developement and one for production.
public class MealzManager: ObservableObject {
public static let sharedInstance = MealzManager()
private init() {
Mealz.shared.Core(init: { coreBuilder in
// set provider key
coreBuilder.sdkRequirement(init: { requirementBuilder in
requirementBuilder.key = self.providerKey
})
})
}
}
User setup
Here is how to pass the user ID to the SDK, directly within the host app:
// From anywhere
import mealzcore
// existingUserId is your user id, type String is expected
Mealz.shared.user.updateUserId(userId: existingUserId, authorization: Authorization.userId)
Here is how to inform the SDK whenever the user login state changes. We recommend using Observables or EventListeners to that end.
// file MealzManager.swift
import mealzcore
public class MealzManager {
// CODE
private init() {
// CODE
OBSERVABLE_ON_USER_OBJECT.sink { _ in
// existingUserId is your user id, type String is expected
Mealz.shared.user.updateUserId(userId: existingUserId, authorization: Authorization.userId)
}
}
// CODE
}
To get full list of user feature check User configurations.
Store setup
For Mealz to work properly, your user must be connected to a specific store so we can accurately provide recipes with available ingredients. To add the store that the user is currently at, you can use this code:
// From anywhere
import mealzcore
// STORE_ID_IN_HOST_APP is your store id, type String is expected
Mealz.shared.user.setStoreId(storeId: STORE_ID_IN_HOST_APP)
Basket synchronization Setup
Most importantly, the Mealz Basket must be kept up to date with your in-app basket. The SDK handles this complex logic by translating between Mealz Products & your real products. This mechanism is mandatory -> otherwise products will not be able to be added nor deleted properly between the two stores.
For example, when a user adds a Mealz Recipe to their cart, they are adding Mealz products to their basket. We then send them to you, the retailer, to be translated into your products & then added to your basket.
Likewise, when a product is deleted from your basket, the retailer, we listen for updates. When we notice a product used in a Mealz Recipe was deleted, we update our basket to reflect that change as well.
If at some point, you feel like products magically disappear from Mealz recipes, or are not removed from the app basket while they should be, this is probably related to this part.
Helper Function to map between products
More than likely, your Product object will be quite different than ours so we have created a bridge model called SupplierProduct.
Because of this, we recommend creating a helper function or two to translate between your object & the bridge.
This will make your code easier to understand.
private func pretendProductsToRetailerProducts(
products: [PretendProduct]
) -> [SupplierProduct] {
return products.map {
return SupplierProduct(
id: $0.id,
quantity: Int32($0.quantity),
name: $0.name,
productIdentifier: nil,
imageURL: $0.imageUrl
)
}
}
BasketSubscriber & BasketListener
We have two protocols BasketSubscriber & BasketListener to coordinate the two baskets together.
As the name suggestions, the listeners listens for updates from your basket.
The subscriber allows you to listen.
We think it's best to start to create a new class that implements our new protocols. You can copy this & change the name:
class YourNewBasketService: BasketSubscriber, BasketPublisher {
var initialValue: [SupplierProduct]
func receive(event: [SupplierProduct]) {
<#code#>
}
func onBasketUpdate(sendUpdateToSDK: @escaping ([SupplierProduct]) -> Void) {
<#code#>
}
}
IMPORTANT: For all upcoming code examples, we will use PretendProduct to replicate YOUR product.
In your implementation, use your actual Product class.
onBasketUpdate
Now, we need a way to stream the updates from the Mealz Basket to your own.
We recommend using an observable.
From there, we can sink your basket (in this case, the ExampleState) with the updates from Mealz.
Here's our example:
import Combine
private var cancellable: AnyCancellable? // used to create stream between mealz basket & our own
func onBasketUpdate(sendUpdateToSDK: @escaping ([SupplierProduct]) -> Void) {
cancellable = PretendBasket.shared.$items.sink { receiveValue in
`sendUpdateToSDK`(
self.pretendProductsToRetailerProducts(products: receiveValue)
)
}
}
initialValue
When Mealz is launching, we need to know the state of the current customer basket. Whenever this basket changes, we will refresh the Mealz SDK.
In our implementation, we create an initializer to pass our DemoProducts into before translating them. Here is our code:
init(initialBasketList: [PretendProduct]) {
self.initialValue = [] // First, initialize all properties as [] so we can use pretendProductsToRetailerProducts
if initialBasketList.count > 0 {
// Now convert (safely) if we have products
self.initialValue = pretendProductsToRetailerProducts(products: initialBasketList)
}
}
So now in our MealzManager, we can call:
let demoBasketService = DemoBasketService(initialBasketList: PretendBasket.shared.items)
IMPORTANT: passing an empty list to Mealz SDK on initialization when there are actual products in the basket will lead to unexpected side effects. If this is done, products in the user's basket will appear as removed by the user in the Mealz basket.
receive
The last step of the basketService is to implement receive.
This will make sure both baskets are connected & up to date.
You'll need to create a new function, ours is named updateBasketFromExternalSource that accepts a list of SupplierProducts & updates your basket.
The function will iterate through the new products, checking if they are in your basket.
If yes, they will either delete them (if the quantity is 0), or update their quantity & info.
If no, they will be added to your basket.
IMPORTANT: you'll want to create a local temporary list inside this function so that you can edit your basket all at once. If you update your basket with each product iteration, you will have unexpected behavior (& increase time complexity as you'll be iterating through a growing list).
Here is our example:
private func updateBasketFromExternalSource(products: [SupplierProduct]) {
// we need to update the basket all at once, otherwise we will have issues with Mealz updating too frequently
var basketCopy = PretendBasket.shared.items
for product in products {
// check if we already have the product to remove or update info
if let productToUpdateIndex = PretendBasket.shared.items.firstIndex(where: { $0.id == product.id }) {
if product.quantity == 0 { // we know an item is deleted if the qty is 0
if basketCopy.indices.contains(productToUpdateIndex) {
basketCopy.remove(at: productToUpdateIndex)
}
} else {
let item = PretendBasket.shared.items[productToUpdateIndex]
basketCopy[productToUpdateIndex] = PretendProduct( // your product
id: product.id,
name: product.name ?? item.name,
quantity: product.quantity,
imageUrl: product.imageUrl ?? item.imageUrl)
}
} else { // otherwise add it to the client basket
let newProduct = PretendProduct( // your product
id: product.id,
name: product.name ?? "product",
quantity: Int(product.quantity),
imageUrl: product.imageURL
)
basketCopy.append(newProduct)
}
}
// update your basket after all operations
PretendBasket.shared.items = basketCopy
}
and then simply:
func receive(event: [SupplierProduct]) {
updateBasketFromExternalSource(products: event)
}
Putting it all together
let demoBasketService = DemoBasketService(initialBasketList: PretendBasket.shared.items)
Mealz.shared.Core(init: { coreBuilder in
// set supplier key
coreBuilder.sdkRequirement(init: { requirementBuilder in
requirementBuilder.key = supplierKey
})
// set listeners & notifiers
coreBuilder.subscriptions(init: { subscriptionBuilder in
subscriptionBuilder.basket(init: { basketSubscriptionBuilder in
// subscribe
basketSubscriptionBuilder.subscribe(subscriber: demoBasketService)
// push updates
basketSubscriptionBuilder.register(publisher: demoBasketService)
})
})
})
Checkout
Now, to properly finish the Mealz session, you'll need to call the below function:
Mealz.shared.basket.handlePayment(totalAmount: <#T##Double#>)
This will clear the basket & lodge the products as purchased, especially with regard to our AI model. The more a recipe is purchased, the more confident we are in the model & products associated. Further, we also can use this data for analytics purposes to see which recipes are most frequently purchased.
This should be called directly before the user pays & is mandatory.
Congratulations, Mealz is good to go 🥳