Skip to content

Commit

Permalink
Add Snackbar handling
Browse files Browse the repository at this point in the history
  • Loading branch information
jshvarts committed Nov 9, 2022
1 parent e2c7769 commit e39585d
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 24 deletions.
92 changes: 72 additions & 20 deletions app/src/main/java/com/jshvarts/conditionalbottomnav/MainActivity.kt
@@ -1,18 +1,19 @@
package com.jshvarts.conditionalbottomnav

import android.content.res.Resources
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.remember
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
Expand All @@ -25,11 +26,14 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.jshvarts.conditionalbottomnav.model.SnackbarManager
import com.jshvarts.conditionalbottomnav.navigation.BottomBarTab
import com.jshvarts.conditionalbottomnav.navigation.HOME_GRAPH
import com.jshvarts.conditionalbottomnav.navigation.HomeDestinations.HOME_ITEM_ROUTE
import com.jshvarts.conditionalbottomnav.navigation.navGraph
import com.jshvarts.conditionalbottomnav.ui.theme.ConditionalBottomNavTheme
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
Expand Down Expand Up @@ -58,18 +62,26 @@ class MainActivity : ComponentActivity() {
@Composable
fun App() {
ConditionalBottomNavTheme {
val appState = rememberAppState()
Scaffold(
bottomBar = {
if (appState.shouldShowBottomBar) {
BottomBar(
tabs = appState.bottomBarTabs,
currentRoute = appState.currentRoute!!,
navigateToRoute = appState::navigateToBottomBarRoute
)
}
}
) { innerPaddingModifier ->
val appState = rememberAppState()
Scaffold(
bottomBar = {
if (appState.shouldShowBottomBar) {
BottomBar(
tabs = appState.bottomBarTabs,
currentRoute = appState.currentRoute!!,
navigateToRoute = appState::navigateToBottomBarRoute
)
}
},
snackbarHost = {
SnackbarHost(
hostState = it,
modifier = Modifier.systemBarsPadding(),
snackbar = { snackbarData -> Snackbar(snackbarData) }
)
},
scaffoldState = appState.scaffoldState
) { innerPaddingModifier ->
NavHost(
navController = appState.navController,
startDestination = HOME_GRAPH,
Expand All @@ -86,16 +98,45 @@ fun App() {

@Composable
fun rememberAppState(
navController: NavHostController = rememberNavController()
scaffoldState: ScaffoldState = rememberScaffoldState(),
navController: NavHostController = rememberNavController(),
snackbarManager: SnackbarManager = SnackbarManager,
resources: Resources = resources(),
coroutineScope: CoroutineScope = rememberCoroutineScope()
) =
remember(navController) {
AppState(navController)
remember(scaffoldState, navController, snackbarManager, resources, coroutineScope) {
AppState(scaffoldState, navController, snackbarManager, resources, coroutineScope)
}

@Stable
class AppState(
val navController: NavHostController
val scaffoldState: ScaffoldState,
val navController: NavHostController,
private val snackbarManager: SnackbarManager,
private val resources: Resources,
coroutineScope: CoroutineScope
) {
// Process snackbars coming from SnackbarManager
init {
coroutineScope.launch {
snackbarManager.messages.collect { currentMessages ->
if (currentMessages.isNotEmpty()) {
val message = currentMessages[0]
val text = resources.getText(message.messageId)

// Display the snackbar on the screen. `showSnackbar` is a function
// that suspends until the snackbar disappears from the screen
scaffoldState.snackbarHostState.showSnackbar(text.toString())
// Once the snackbar is gone or dismissed, notify the SnackbarManager
snackbarManager.setMessageShown(message.id)
}
}
}
}

// ----------------------------------------------------------
// BottomBar state source of truth
// ----------------------------------------------------------
val bottomBarTabs = BottomBarTab.values()
private val bottomBarRoutes = bottomBarTabs.map { it.route }

Expand Down Expand Up @@ -140,6 +181,17 @@ private tailrec fun findStartDestination(graph: NavDestination): NavDestination
return if (graph is NavGraph) findStartDestination(graph.startDestination!!) else graph
}

/**
* A composable function that returns the [Resources]. It will be recomposed when `Configuration`
* gets updated.
*/
@Composable
@ReadOnlyComposable
private fun resources(): Resources {
LocalConfiguration.current
return LocalContext.current.resources
}

@Composable
fun BottomBar(
tabs: Array<BottomBarTab>,
Expand Down
16 changes: 12 additions & 4 deletions app/src/main/java/com/jshvarts/conditionalbottomnav/Screens.kt
Expand Up @@ -16,6 +16,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.jshvarts.conditionalbottomnav.model.SnackbarManager
import kotlin.random.Random

@Composable
fun HomeScreen(
Expand Down Expand Up @@ -98,10 +100,14 @@ fun HomeItemDetailScreen(
.padding(innerPaddingModifier)
.fillMaxSize()
) {
Text(
text = "Home Item $itemId",
style = MaterialTheme.typography.h6
)
if (ifRandomlyFailed()) {
SnackbarManager.showMessage(R.string.item_lookup_failed_error)
} else {
Text(
text = "Home Item $itemId",
style = MaterialTheme.typography.h6
)
}
}
}
}
Expand All @@ -128,3 +134,5 @@ fun HomeItemCard(
)
}
}

private fun ifRandomlyFailed() = Random.nextBoolean()
@@ -0,0 +1,35 @@
package com.jshvarts.conditionalbottomnav.model

import androidx.annotation.StringRes
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import java.util.*


data class Message(val id: Long, @StringRes val messageId: Int)

/**
* Class responsible for managing Snackbar messages to show on the screen
*/
object SnackbarManager {

private val _messages: MutableStateFlow<List<Message>> = MutableStateFlow(emptyList())
val messages: StateFlow<List<Message>> get() = _messages.asStateFlow()

fun showMessage(@StringRes messageTextId: Int) {
_messages.update { currentMessages ->
currentMessages + Message(
id = UUID.randomUUID().mostSignificantBits,
messageId = messageTextId
)
}
}

fun setMessageShown(messageId: Long) {
_messages.update { currentMessages ->
currentMessages.filterNot { it.id == messageId }
}
}
}
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Expand Up @@ -5,4 +5,5 @@
<string name="chat">Chat</string>
<string name="item_detail">Item Detail</string>
<string name="close">Close</string>
<string name="item_lookup_failed_error">Item lookup failed</string>
</resources>

0 comments on commit e39585d

Please sign in to comment.