Skip to content

Commit

Permalink
Add subscription extension methods and sample code (#8)
Browse files Browse the repository at this point in the history
* ✨ Add SubscriptionExtensions.kt

* πŸ‘¨β€πŸ­ Add user subscription to demonstrate composite subscription
  • Loading branch information
semanticer authored Sep 29, 2021
1 parent c24dece commit fe352f0
Show file tree
Hide file tree
Showing 9 changed files with 117 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.toggl.komposable.extensions

import com.toggl.komposable.architecture.Subscription
import com.toggl.komposable.internal.CompositeSubscription

/**
* @param subscriptions List of subscriptions which should be merged
* @return A [CompositeSubscription] of a given subscriptions
* @see CompositeSubscription
*/
fun <State, Action : Any> mergeSubscriptions(vararg subscriptions: Subscription<State, Action>): Subscription<State, Action> =
CompositeSubscription(subscriptions.toList())

/**
* @receiver First subscription
* @param subscription Second subscription that will be merged with the receiver
* @return A [CompositeSubscription] of a given subscriptions
* @see CompositeSubscription
*/
infix fun <State, Action : Any> Subscription<State, Action>.mergeWith(subscription: Subscription<State, Action>): Subscription<State, Action> =
mergeSubscriptions(this, subscription)
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package com.toggl.komposable.sample.todo

import com.toggl.komposable.sample.todo.data.Identity
import com.toggl.komposable.sample.todo.edit.EditAction
import com.toggl.komposable.sample.todo.list.ListAction

sealed class AppAction {
class List(override val action: ListAction) : AppAction(), ActionWrapper<ListAction>
class Edit(override val action: EditAction) : AppAction(), ActionWrapper<EditAction>
data class IdentityUpdated(val newIdentity: Identity) : AppAction()
object BackPressed : AppAction()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import com.toggl.komposable.architecture.Effect
import com.toggl.komposable.architecture.Mutable
import com.toggl.komposable.extensions.mutateWithoutEffects
import com.toggl.komposable.sample.todo.data.EditableTodoItem
import com.toggl.komposable.sample.todo.data.Identity
import com.toggl.komposable.sample.todo.data.TodoItem

data class AppState(
val todoList: List<TodoItem> = emptyList(),
val editableTodo: EditableTodoItem = EditableTodoItem(title = "", description = ""),
val identity: Identity = Identity.Unknown,
override val backStack: BackStack = listOf(AppDestination.List)
) : BackStackAwareState<AppState> {
override fun changeBackStack(backStack: BackStack): AppState =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.toggl.komposable.sample.todo

import com.toggl.komposable.architecture.Effect
import com.toggl.komposable.architecture.Mutable
import com.toggl.komposable.architecture.Reducer
import com.toggl.komposable.extensions.mutateWithoutEffects
import com.toggl.komposable.extensions.noEffect
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class AuthReducer @Inject constructor() : Reducer<AppState, AppAction> {
override fun reduce(state: Mutable<AppState>, action: AppAction): List<Effect<AppAction>> =
when (action) {
is AppAction.IdentityUpdated -> state.mutateWithoutEffects { copy(identity = action.newIdentity) }
else -> noEffect()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ import com.toggl.komposable.architecture.Reducer
import com.toggl.komposable.architecture.Store
import com.toggl.komposable.extensions.combine
import com.toggl.komposable.extensions.createStore
import com.toggl.komposable.extensions.mergeWith
import com.toggl.komposable.extensions.pullback
import com.toggl.komposable.sample.todo.data.AppDatabase
import com.toggl.komposable.sample.todo.data.TodoDao
import com.toggl.komposable.sample.todo.data.UserSubscription
import com.toggl.komposable.sample.todo.edit.EditAction
import com.toggl.komposable.sample.todo.edit.EditReducer
import com.toggl.komposable.sample.todo.edit.EditState
Expand All @@ -35,11 +37,13 @@ class TodoModule {
@Provides
fun appReducer(
navigationReducer: NavigationReducer,
authReducer: AuthReducer,
listReducer: ListReducer,
editReducer: EditReducer
): Reducer<AppState, AppAction> =
combine(
navigationReducer,
authReducer,
listReducer.pullback(
mapToLocalState = { appState -> ListState(appState.todoList, appState.backStack) },
mapToLocalAction = AppAction::unwrap,
Expand All @@ -59,13 +63,14 @@ class TodoModule {
fun appStore(
reducer: Reducer<AppState, AppAction>,
listSubscription: ListSubscription,
userSubscription: UserSubscription,
dispatcherProvider: DispatcherProvider,
application: Application
): Store<AppState, AppAction> =
createStore(
initialState = AppState(),
reducer = reducer,
subscription = listSubscription,
subscription = listSubscription mergeWith userSubscription,
dispatcherProvider = dispatcherProvider,
storeScopeProvider = application as StoreScopeProvider
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package com.toggl.komposable.sample.todo

import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.BottomAppBar
import androidx.compose.material.FabPosition
Expand All @@ -13,10 +15,13 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Check
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import com.toggl.komposable.sample.todo.data.Identity
import com.toggl.komposable.sample.todo.edit.EditAction
import com.toggl.komposable.sample.todo.list.AddTodoFab
import kotlinx.coroutines.ExperimentalCoroutinesApi
Expand All @@ -43,18 +48,7 @@ fun TodoScaffold() {
floatingActionButton = { if (currentDestination == AppDestination.List) AddTodoFab() },
floatingActionButtonPosition = FabPosition.Center,
isFloatingActionButtonDocked = true,
bottomBar = {
BottomAppBar(cutoutShape = RoundedCornerShape(100)) {
if (currentDestination == AppDestination.Add) {
OutlinedButton(
onClick = { appStore.dispatch(AppAction.Edit(EditAction.SaveTapped)) }
) {
Icon(Icons.Rounded.Check, contentDescription = null)
Text(text = "Save")
}
}
}
}
bottomBar = { TodoBottomAppBar(appStore, currentDestination) }
) {
AppNavigationHost(
backStack = backStack
Expand All @@ -81,3 +75,28 @@ fun AppCompatActivity.handleBackPressesEmitting(callback: () -> Unit) {
}
)
}

@Composable
private fun TodoBottomAppBar(appStore: AppStoreViewModel, currentDestination: AppDestination) {

val identity by appStore.state
.map { it.identity }
.collectAsStateWhenStarted(initial = Identity.Unknown)

BottomAppBar(cutoutShape = RoundedCornerShape(100)) {
if (currentDestination == AppDestination.Add) {
OutlinedButton(
onClick = { appStore.dispatch(AppAction.Edit(EditAction.SaveTapped)) }
) {
Icon(Icons.Rounded.Check, contentDescription = null)
Text(text = "Save")
}
}
Spacer(modifier = Modifier.weight(1f))
val identityText = when (identity) {
Identity.Unknown -> "Loading..."
is Identity.User -> (identity as Identity.User).username
}
Text(text = identityText, modifier = Modifier.padding(end = 12.dp))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.toggl.komposable.sample.todo.data

import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class AccountManager @Inject constructor() {
fun getLoggedInUser(): Flow<Identity> = flow {
emit(Identity.Unknown)
delay(3000)
emit(Identity.User("John Balance", "[email protected]"))
}
}

sealed class Identity {
object Unknown : Identity()
data class User(val username: String, val email: String) : Identity()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.toggl.komposable.sample.todo.data

import com.toggl.komposable.architecture.Subscription
import com.toggl.komposable.sample.todo.AppAction
import com.toggl.komposable.sample.todo.AppState
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class UserSubscription @Inject constructor(private val accountManager: AccountManager) : Subscription<AppState, AppAction> {
override fun subscribe(state: Flow<AppState>): Flow<AppAction> =
accountManager.getLoggedInUser().map { AppAction.IdentityUpdated(it) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class ListSubscription @Inject constructor(val todoDao: TodoDao) : Subscription<AppState, AppAction> {
class ListSubscription @Inject constructor(private val todoDao: TodoDao) : Subscription<AppState, AppAction> {
override fun subscribe(state: Flow<AppState>): Flow<AppAction> =
todoDao.getAll().map { AppAction.List(ListAction.ListUpdated(it)) }
}

0 comments on commit fe352f0

Please sign in to comment.