Skip to main content
Version: 5.6

Updating MiamManager

This section is upgrading miam-sdk from Version 3 to Version 4.

Initialization

First & foremost, you'll need to update how you initialize your miam-sdk inside your MiamManager. We have made it more straightforward & readable to initialize miam-sdk, which comes with changes to existing implementations.

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:

val 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"
}

BasketHandler - Communicating with the Mealz Baskets

Previous, implementations would the BasketHandler to communicate with the two baskets. The BasketHandler has been replaced in version 4 by protocols BasketSubscriber & BasketPublisher.

Your previous code would look something like this inside your MiamManager:

BasketHandlerInstance.instance.pushProductsToMiamBasket(retailerBasketSubject.value.items.map { product ->
myProductTORetailerProduct(
product
)
})
BasketHandlerInstance.instance.setListenToRetailerBasket(func: initBasketListener)
BasketHandlerInstance.instance.setPushProductsToSupplierBasket(func: pushProductToBasket)

We'll go through the differences on these calls one by one.

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(): BasketPublisher, BasketSubscriber, CoroutineScope by CoroutineScope(Dispatchers.Main) {
override var initialValue: List<SupplierProduct>

override fun onBasketUpdate(sendUpdateToSDK: (List<SupplierProduct>) -> Unit) {
TODO("Not yet implemented")
}

override fun receive(event: List<SupplierProduct>) {
TODO("Not yet implemented")
}
}
danger

IMPORTANT: For all upcoming code examples, we will use MyProduct to replicate YOUR product. In your implementation, use your actual Product class.

onBasketUpdate - setListenToRetailerBasket

This code is very similar to before, but instead we put the code inside our onBasketUpdate function. For example, your old code should be streaming your basket to the Mealz Basket, something like this:

private fun initBasketListener() {
launch(coroutineHandler) {
retailerBasketSubject.collect {
basketHandler.pushProductsToMiamBasket(it.items.map { product ->
myProductTORetailerProduct(
product
)
})
}
}
}

Now, you can move the content from initBasketListener directly into the onBasketUpdate.

override fun onBasketUpdate(sendUpdateToSDK: (List<SupplierProduct>) -> Unit) {
launch {
_RetailerBasketSubject.collect { state ->
LogHandler.debug("[New Mealz] push basket update from supplier")
sendUpdateToSDK(state.items.map { SupplierProduct(it.id, it.quantity) })
}
}
}

initialValue - pushProductsToMiamBasket

The pushProductsToMiamBasket creates the initial basket state. Instead, we set this inside our new BasketService.

In our implementation, we create an observable that watches the state of your basket & listens for updates. This observable is inside our BasketService sample implementation. Here is our code:

class MyBasketService(): BasketPublisher, BasketSubscriber, CoroutineScope by CoroutineScope(Dispatchers.Main) {

// we observe all the changes to your basket
private val _RetailerBasketSubject: MutableStateFlow<ExampleState> = MutableStateFlow(pretendExampleState)

// we set the initialValue of the BasketService to the items currently in your basket
override var initialValue: List<SupplierProduct> = _RetailerBasketSubject.value.items.map { SupplierProduct(it.id, it.quantity, it.name, it.image) }

So now in our MealzManager or your local DI, we can call:

private val basketService: MyBasketService = MyBasketService()

receive - setPushProductsToSupplierBasket

The last step of the basketHandler is to implement the receive. This will update 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.

Here is our example:

fun updateBasketFromExternalSource(products: List<SupplierProduct>) {
launch {
products.forEach { sp ->
val productToUpdateIdx =
_RetailerBasketSubject.value.items.indexOfFirst { it.id == sp.id }

// Product to add to your basket
if (productToUpdateIdx == -1) {
val product = MyProduct(
sp.id,
attributes = ProductAttributes(name = sp.name ?: "a name", image = sp.imageURL ?: "", price = 1.0),
quantity = sp.quantity
)
_RetailerBasketSubject.value.add(product)
} else if (sp.quantity == 0) {
_RetailerBasketSubject.value.removeItem(productToUpdateIdx)
} else {
_RetailerBasketSubject.value.replaceItem(
index = productToUpdateIdx,
newProduct = _RetailerBasketSubject.value.items[productToUpdateIdx].copy(quantity = sp.quantity)
)
}
}
_RetailerBasketSubject.emit(
ExampleState(
_RetailerBasketSubject.value.items,
_RetailerBasketSubject.value.recipeCount
)
)
}
}

and then simply:

override fun receive(event: List<SupplierProduct>) {
updateBasketFromExternalSource(event)
}
danger

Make sure to handle our event in a synchronious way otherwise your basket may emit too early and we might consider missing product as deleted.

If for example:

  • we send you a list of two products to add tomato and beans
  • then you add tomato and emit your new basket
  • we will consider that beans have been removed

Good way to proceed can be to either add products in batches

  • we send you a list of two products to add tomato and beans
  • then you add tomato and beans in the same time then emit your new basket

or

  • we send you a list of two products to add tomato and beans
  • you add tomato without emiting your new basket
  • then add beans then emit your new basket

Putting it all together

You can now remove the previous BasketHandler & add this code:

import ai.mealz.core.Mealz

object MealzManager {

private var isInitialized = false
private val basketService: MyBasketService = MyBasketService()

public fun initialize(applicationContext: Context) {
Mealz.Core {
sdkRequirement {
key = supplierKey
context = applicationContext
}
subscriptions {
basket {
// Listen to Miam's basket updates
subscribe(basketService)
// Push client basket notifications
register(basketService)
}
}
}
isInitialized = true
}
}

User

ContextHandlerInstance

If you've implemented the above code, you can delete this line:

ContextHandlerInstance.instance.doInitMiam(base64_key = supplierKey)

PointOfSaleHandler

Previously, to set the store, you'd call the PointOfSaleHandler like this:

PointOfSaleHandler.updateStoreId(storeId = "25910")

Now, you use user inside the Mealz SDK like this:

// set store
Mealz.user.setStoreId(storeId = "25910")

UserHandler

Previously, to set the userId, you'd call the UserHandler like this:

UserHandler.updateUserId(userId = existingUserId)

Now, you use user inside the Mealz SDK like this:

// set userID
Mealz.user.updateUserId(userId = existingUserId, authorization = Authorization.userId)

Additionally

You can also add this code to be explicit about enabled Favoriting Recipes & Personalized Recipes.

// allow profiling -> can we use your personal data to provide custom recipes?
Mealz.user.setProfilingAllowance(allowance = true)
// allow users to heart recipes
Mealz.user.setEnableLike(isEnable = true)

Clear Basket

To clear the basket, you can remove this code:

BasketHandlerInstance.instance.clear()

& replace it with this:

Mealz.basket.clear()

Set Price Book

To set the price book, you can remove this code:

BasketHandlerInstance.shared.instance.setPriceBookKey(priceBookKey = pricebook.rawValue)

& replace it with this:

Mealz.basket.setPriceBook(priceBookId = pricebook.rawValue)

Handle Payment

To inform the SDK that recipes will be purchased, you can remove this code:

BasketHandlerInstance.instance.handlePayment(totalAmount = totalAmount)

& replace it with this:

Mealz.basket.handlePayment(totalAmount = totalAmount)

Analytics & Notifications

Analytics

Previously, you would use the AnalyticsHandler to follow the Mealz updates:

launch(coroutineHandler) { AnalyticsInstance.instance.observeSideEffect().collect { LogHandler.info("Analytics $it") } }

Now, you can call:

// listen to analytics events
Mealz.notifications.analytics.listen {
println("Mealz.Notifications.analytics $it")
}

You can also deactivate analytics on our side and continue to benefit from these notifications by setting this configuration at any time :

  Mealz.environment.setAnalyticsTracking(false)

You will still recieve notification from AnalyticsNotifier but they will not be sent to our analytics solution.

Notifications

Previously, you could use Mealz Toast like this:

ToasterHandler.setOnAddRecipeText("Well done !")
ToasterHandler.setOnLikeRecipeText("Good taste !")

We have added notifications like Toaster, so you can add this to listen:

// show toast to users on certain events
Mealz.Notifications.toaster.listen { notification ->
when (notification) {
is ToasterNotification.RecipeAdded -> {
println("Mealz.Notifications.recipesCount recipe add")
}
is ToasterNotification.RecipeLiked -> {
println("Mealz.Notifications.recipesCount recipe like")
}
}
println("Mealz.Notifications.recipesCount $")
}

Is Ready

Previously, you would use one of these functions to know if Mealz was ready

ContextHandlerInstance.instance.isReady()

or

launch(coroutineHandler) {
ContextHandlerInstance.instance.observeReadyEvent().collect { it ->
LogHandler.info("I know you are readdy !!! $it")
}
}

Now, you can use our AvailabilityStore like this:

Mealz.environment.isAvailable()

Recipe Count

Previously you could monitor the recipe count this way:

GroceriesListHandler.shared.onRecipeCountChanges { count ->
// do stuff
println(count)
}

Now you can access it with this:

Mealz.basket.getRecipeCountInBasket()

or listen to it with a subscriber like this:

Mealz.notifications.recipeCount()