Skip to content

Commit

Permalink
Add memoized selector
Browse files Browse the repository at this point in the history
  • Loading branch information
matt-ramotar committed Apr 26, 2024
1 parent afa249c commit 4d5e36c
Show file tree
Hide file tree
Showing 12 changed files with 131 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,8 @@ interface Market<S: Market.State> {
interface Dispatcher<A : Action> {
fun dispatch(action: A)
}

fun interface Selector<S: State, R: Any> {
fun select(state: S): R
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.mobilenativefoundation.market


class MemoizedSelector<S : Market.State, P : Any, R : Any>(
private val selector: (S) -> R,
private val getParams: (S) -> P,
private val areParamsEqual: (P, P) -> Boolean = { p1, p2 -> p1 == p2 },
) : Market.Selector<S, R> {

private var lastParams: P? = null
private var lastResult: R? = null

override fun select(state: S): R {
val params = getParams(state)

if (lastParams == null || params == Unit || !areParamsEqual(params, lastParams!!)) {
lastParams = params
lastResult = selector(state)
}

return lastResult!!
}
}

fun <S : Market.State, P : Any, R : Any> memoize(
selectState: (S) -> R,
getParams: (S) -> P,
areParamsEqual: (P, P) -> Boolean = { p1, p2 -> p1 == p2 }
): Market.Selector<S, R> {
return MemoizedSelector(selectState, getParams, areParamsEqual)
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,8 @@ class RealScheduledMarketSupplier<K : Any, O : Any, A : Market.Action, D : Marke

private suspend fun supplyMarket(key: K) {
try {
println("SUPPLYING MARKET for key $key")
val storeOutput = store.fresh(key)
val marketAction = marketActionFactory.create(storeOutput)
println("UPDATING MARKET WITH $storeOutput")
marketDispatcher.dispatch(marketAction)
} catch (e: Exception) {
// TODO
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ interface Warehouse<S : Warehouse.State, A : Warehouse.Action> {
class RealWarehouse<S : Warehouse.State, A : Warehouse.Action, MS : Market.State, M : Market<MS>>(
coroutineDispatcher: CoroutineDispatcher,
private val market: M,
private val extractor: (MS) -> S,
private val selector: Market.Selector<MS, S>,
private val actionHandler: (A, MS) -> Unit
) : Warehouse<S, A> {

private val coroutineScope = CoroutineScope(coroutineDispatcher)

override val state: StateFlow<S> = market.state.map {
extractor(it)
}.stateIn(coroutineScope, SharingStarted.Eagerly, extractor(market.state.value))
selector.select(it)
}.stateIn(coroutineScope, SharingStarted.Eagerly, selector.select(market.state.value))


override fun <D : Any> subscribe(selector: Warehouse.Selector<S, D>): Flow<D> =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,6 @@ abstract class CoreComponent : NetworkingComponent {
): FeedSupplier {

val marketActionFactory = MarketActionFactory<Feed, OctonautMarketAction> { storeOutput ->
println("STORE OUTPUT = $storeOutput")
OctonautMarketAction.UpdateFeed(storeOutput)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.mobilenativefoundation.sample.octonaut.xplat.common.market

import org.mobilenativefoundation.market.memoize

fun <P : Any, R : Any> memoize(
selectState: (OctonautMarketState) -> R,
getParams: (OctonautMarketState) -> P,
areParamsEqual: (P, P) -> Boolean = { p1, p2 -> p1 == p2 }
) = memoize(
selectState = selectState,
getParams = getParams,
areParamsEqual = areParamsEqual
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,22 @@ package org.mobilenativefoundation.sample.octonaut.xplat.common.market

import kotlinx.coroutines.CoroutineDispatcher
import me.tatarka.inject.annotations.Inject
import org.mobilenativefoundation.market.Market
import org.mobilenativefoundation.market.warehouse.RealWarehouse
import org.mobilenativefoundation.market.warehouse.Warehouse


interface WarehouseBuilder<S : Warehouse.State, A : Warehouse.Action> {
fun <P : Any> memoizedSelector(
getParams: (OctonautMarketState) -> P,
areParamsEqual: (P, P) -> Boolean = { p1, p2 -> p1 == p2 },
selectState: (OctonautMarketState) -> S,
): WarehouseBuilder<S, A>

fun selector(
selector: Market.Selector<OctonautMarketState, S>
): WarehouseBuilder<S, A>

fun extractor(extractor: (OctonautMarketState) -> S): WarehouseBuilder<S, A>
fun actionHandler(actionHandler: (A, OctonautMarketState) -> Unit): WarehouseBuilder<S, A>
fun build(): Warehouse<S, A>
}
Expand All @@ -26,22 +35,36 @@ class RealWarehouseBuilderFactory(
override fun <S : Warehouse.State, A : Warehouse.Action> create(): WarehouseBuilder<S, A> {
return object : WarehouseBuilder<S, A> {

private lateinit var extractor: (OctonautMarketState) -> S
private lateinit var actionHandler: (A, OctonautMarketState) -> Unit
private lateinit var selector: Market.Selector<OctonautMarketState, S>

override fun extractor(extractor: (OctonautMarketState) -> S) = apply {
this.extractor = extractor
}

override fun actionHandler(actionHandler: (A, OctonautMarketState) -> Unit) = apply {
this.actionHandler = actionHandler
}


override fun selector(selector: Market.Selector<OctonautMarketState, S>): WarehouseBuilder<S, A> = apply {
this.selector = selector
}

override fun <P : Any> memoizedSelector(
getParams: (OctonautMarketState) -> P,
areParamsEqual: (P, P) -> Boolean,
selectState: (OctonautMarketState) -> S
): WarehouseBuilder<S, A> = apply {
this.selector = memoize(
selectState = selectState,
getParams = getParams,
areParamsEqual = areParamsEqual
)
}

override fun build(): Warehouse<S, A> {
return RealWarehouse(
coroutineDispatcher,
market,
extractor,
selector,
actionHandler
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ class UserStoreFactory(
fun create(): UserStore {
val storeBuilder = StoreBuilder.from(
fetcher = Fetcher.of { query: GetUserQuery ->
println("QUERY = $query")
networkingClient.getUser(query)?.user ?: throw IllegalStateException("No user")
},
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,24 @@ import org.mobilenativefoundation.sample.octonaut.xplat.common.market.WarehouseB

@Inject
class ExploreTabWarehouseFactory(
private val warehouseBuilderFactory: WarehouseBuilderFactory
warehouseBuilderFactory: WarehouseBuilderFactory
) {
fun create(): ExploreTabWarehouse {
val warehouseBuilder = warehouseBuilderFactory.create<
ExploreTabWarehouseState, ExploreTabWarehouseAction>()

return warehouseBuilder.extractor { marketState ->
ExploreTabWarehouseState(
user = marketState.currentUser?.user,
searchResults = emptyList() // TODO
)
}.actionHandler { action, marketState ->
when (action) {
is ExploreTabWarehouseAction.Search -> {
// TODO
}
private val warehouseBuilder = warehouseBuilderFactory.create<ExploreTabWarehouseState, ExploreTabWarehouseAction>()

fun create(): ExploreTabWarehouse =
warehouseBuilder
.memoizedSelector(getParams = { _ -> }) { state ->
ExploreTabWarehouseState(
state.currentUser.user,
emptyList()
)
}
}.build()
}
.actionHandler { action, _ ->
when (action) {
is ExploreTabWarehouseAction.Search -> {
// TODO
}
}
}.build()
}

Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,23 @@ import org.mobilenativefoundation.sample.octonaut.xplat.foundation.networking.ap
@Inject
class HomeTabWarehouseFactory(
private val userSupplier: UserSupplier,
private val warehouseBuilderFactory: WarehouseBuilderFactory
warehouseBuilderFactory: WarehouseBuilderFactory
) {
fun create(): HomeTabWarehouse {
val warehouseBuilder = warehouseBuilderFactory
.create<HomeTabWarehouseState, HomeTabWarehouseAction>()
private val warehouseBuilder = warehouseBuilderFactory.create<HomeTabWarehouseState, HomeTabWarehouseAction>()

return warehouseBuilder
.extractor { marketState ->
HomeTabWarehouseState(marketState.currentUser?.user, marketState.feed)
fun create(): HomeTabWarehouse =
warehouseBuilder
.memoizedSelector(getParams = { _ -> }) { state ->
HomeTabWarehouseState(
state.currentUser.user,
state.feed
)
}
.actionHandler { action, marketState ->
when (action) {
HomeTabWarehouseAction.Refresh -> {
// Refresh user
val currentUser = marketState.currentUser!!.user!!
val currentUser = marketState.currentUser.user!!
userSupplier.supply(GetUserQuery(currentUser.login))

// Refresh repositories
Expand All @@ -32,5 +34,4 @@ class HomeTabWarehouseFactory(
}
}
.build()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,22 @@ import org.mobilenativefoundation.sample.octonaut.xplat.domain.notifications.api
@Inject
class NotificationsTabWarehouseFactory(
private val notificationsSupplier: NotificationsSupplier,
private val warehouseBuilderFactory: WarehouseBuilderFactory
warehouseBuilderFactory: WarehouseBuilderFactory
) {
fun create(): NotificationsTabWarehouse {
val warehouseBuilder =
warehouseBuilderFactory.create<NotificationsTabWarehouseState, NotificationsTabWarehouseAction>()
private val warehouseBuilder =
warehouseBuilderFactory.create<NotificationsTabWarehouseState, NotificationsTabWarehouseAction>()

return warehouseBuilder.extractor { marketState ->
NotificationsTabWarehouseState(marketState.notifications.allIds.mapNotNull { marketState.notifications.byId[it] })
}
.actionHandler { action, marketState ->
fun create(): NotificationsTabWarehouse =
warehouseBuilder
.memoizedSelector(getParams = { _ -> }) { state ->
NotificationsTabWarehouseState(state.notifications.allIds.mapNotNull { state.notifications.byId[it] })
}
.actionHandler { action, _ ->
when (action) {
is NotificationsTabWarehouseAction.MarkDone -> {
// TODO
}
}
}
.build()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,32 @@ import me.tatarka.inject.annotations.Assisted
import me.tatarka.inject.annotations.Inject
import org.mobilenativefoundation.sample.octonaut.xplat.common.market.WarehouseBuilderFactory
import org.mobilenativefoundation.sample.octonaut.xplat.domain.user.api.UserSupplier
import org.mobilenativefoundation.sample.octonaut.xplat.feat.userProfile.api.UserProfileScreen
import org.mobilenativefoundation.sample.octonaut.xplat.foundation.networking.api.GetUserQuery

@Inject
class UserProfileScreenWarehouseFactory(
@Assisted private val login: String,
private val warehouseBuilderFactory: WarehouseBuilderFactory,
warehouseBuilderFactory: WarehouseBuilderFactory,
private val userSupplier: UserSupplier,
) {
fun create(): UserProfileScreenWarehouse {
val warehouseBuilder =
warehouseBuilderFactory.create<UserProfileScreenWarehouseState, UserProfileScreenWarehouseAction>()
private val warehouseBuilder =
warehouseBuilderFactory.create<UserProfileScreenWarehouseState, UserProfileScreenWarehouseAction>()

return warehouseBuilder.extractor { marketState ->
val userId = marketState.users.loginToId[login]
val user = marketState.users.byId[userId]
UserProfileScreenWarehouseState(user)
}
.actionHandler { action, marketState ->
fun create(): UserProfileScreenWarehouse =
warehouseBuilder
.memoizedSelector(getParams = { state ->
state.users.loginToId[login]?.let {
state.users.byId[it]
} ?: Unit
}) { state ->
val userId = state.users.loginToId[login]
val user = state.users.byId[userId]
UserProfileScreenWarehouseState(user)
}
.actionHandler { action, _ ->
when (action) {
is UserProfileScreenWarehouseAction.LoadUser -> userSupplier.supply(GetUserQuery(action.login))
}
}
.build()
}
}

0 comments on commit 4d5e36c

Please sign in to comment.