Skip to content

Instantly share code, notes, and snippets.

@macieknajbar
Last active September 28, 2022 12:06
Show Gist options
  • Save macieknajbar/93c8e2c7e9ec4324f92d3eab45452427 to your computer and use it in GitHub Desktop.
Save macieknajbar/93c8e2c7e9ec4324f92d3eab45452427 to your computer and use it in GitHub Desktop.
Finite State Machine
class OrderPresenter(
private val view: OrderContract.View,
private val createNewOrder: CreateNewOrder,
private val addItemToOrder: AddItemToOrder,
private val permissionRequester: PermissionRequester,
private val scheduler: Scheduler,
) : OrderContract.Presenter {
private var state = State(
order = null,
)
private var fsm: OrderFSM = OrderFSM.NewOrder(context = this)
fun changeState(newState: OrderFSM) {
fsm = newState
}
private var menuItemClickVisitor: MenuItemClickVisitor =
MenuItemClickVisitor.Unauthenticated(context = this)
fun changeMenuItemClickVisitor(visitor: MenuItemClickVisitor.Authenticated) {
menuItemClickVisitor = visitor
}
override fun onItemClicked(itemId: String) {
fsm.onMenuItemClicked(
visitor = menuItemClickVisitor,
itemId = itemId,
)
}
fun requestPermission(permission: String, callback: () -> Unit) {
try {
permissionRequester.requestPermission(
permission = permission,
callback = { callback() }
)
} catch (e: PermissionRequester.Issue.Unauthorized) {
view.showError()
view.hideLoading()
}
}
fun createNewOrder() {
val newOrder = createNewOrder.exec()
state = state.copy(order = newOrder)
}
fun getOrder(): Order {
return checkNotNull(state.order)
}
fun addItemToOrder(itemId: String, order: Order) {
addItemToOrder.exec(itemId, order.id)
view.render()
view.hideLoading()
changeState(OrderFSM.ExistingOrder(context = this))
}
fun showLoading() {
view.showLoading()
}
fun scheduleAction(after: Long, action: () -> Unit) {
scheduler.schedule(after, action)
}
fun onWaiting() {
fsm.onWaiting()
}
data class State(
val order: Order?,
)
}
abstract class MenuItemClickVisitor(protected val context: OrderPresenter) {
abstract fun visit(state: OrderFSM.ExistingOrder, itemId: String)
abstract fun visit(state: OrderFSM.NewOrder, itemId: String)
class Unauthenticated(context: OrderPresenter) : MenuItemClickVisitor(context) {
override fun visit(state: OrderFSM.ExistingOrder, itemId: String) {
context.requestPermission(
permission = PermissionRequester.ADD_ITEM_TO_ORDER,
callback = {
context.changeMenuItemClickVisitor(
visitor = Authenticated(context = context)
)
state.performMenuItemClicked(itemId)
},
)
}
override fun visit(state: OrderFSM.NewOrder, itemId: String) {
context.requestPermission(
permission = PermissionRequester.ADD_ITEM_TO_ORDER,
callback = {
context.changeMenuItemClickVisitor(
visitor = Authenticated(context = context)
)
state.performMenuItemClicked(itemId)
},
)
}
}
class Authenticated(context: OrderPresenter) : MenuItemClickVisitor(context) {
override fun visit(state: OrderFSM.ExistingOrder, itemId: String) {
state.performMenuItemClicked(itemId)
}
override fun visit(state: OrderFSM.NewOrder, itemId: String) {
state.performMenuItemClicked(itemId)
}
}
}
abstract class OrderFSM(protected val context: OrderPresenter) {
open fun onMenuItemClicked(
visitor: MenuItemClickVisitor,
itemId: String,
) = Unit
open fun onWaiting() = Unit
class ExistingOrder(context: OrderPresenter) : OrderFSM(context) {
override fun onMenuItemClicked(
visitor: MenuItemClickVisitor,
itemId: String,
) {
context.changeState(Locked(context))
visitor.visit(
state = this,
itemId = itemId,
)
}
fun performMenuItemClicked(itemId: String) {
val order = context.getOrder()
context.addItemToOrder(itemId, order)
}
}
class NewOrder(context: OrderPresenter) : OrderFSM(context) {
override fun onMenuItemClicked(
visitor: MenuItemClickVisitor,
itemId: String,
) {
context.changeState(Locked(context))
visitor.visit(
state = this,
itemId = itemId,
)
}
fun performMenuItemClicked(itemId: String) {
context.createNewOrder()
val order = context.getOrder()
context.addItemToOrder(itemId, order)
}
}
class Locked(context: OrderPresenter) : OrderFSM(context) {
init {
context.scheduleAction(
after = 200L,
action = { context.onWaiting() }
)
}
override fun onWaiting() {
context.showLoading()
}
}
}
interface OrderContract {
interface View {
fun render()
fun showLoading()
fun hideLoading()
fun showError()
}
interface Presenter {
fun onItemClicked(itemId: String)
}
}
class Order(
val id: String,
)
interface CreateNewOrder {
fun exec(): Order
}
interface AddItemToOrder {
fun exec(itemId: String, orderId: String)
}
interface PermissionRequester {
fun requestPermission(permission: String, callback: () -> Unit)
sealed class Issue : RuntimeException() {
object Unauthorized : Issue()
}
companion object {
const val ADD_ITEM_TO_ORDER = "AITO"
}
}
interface Scheduler {
fun schedule(after: Long, action: () -> Unit)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment