https://kotlinlang.org logo
#compose-android
Title
# compose-android
c

Colton Idle

02/26/2024, 4:36 AM
Feel like I'm missing something basic here but... I'm trying to support multiple backstacks with bottom nav. I'm folliwing https://developer.android.com/jetpack/compose/navigation#bottom-nav and everything works fine. But now, I actually have to convert my tabs to each be a webview (im using a fork from accompanist) but when i navigate a few links deep on each tab and then switch tabs, the webview reloads completely instead of maintaining its state.
video of it in action: 1. on tab 1, click a link and let the link load 2. go to tab 2 3. click on tab 1, expecting the last webpage loaded to still be there, but its not
Copy code
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.annotation.StringRes
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.BottomNavigation
import androidx.compose.material.BottomNavigationItem
import androidx.compose.material.Button
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.example.multibackstack.ui.ComposeWebView
import com.example.multibackstack.ui.rememberWebViewState

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            MaterialTheme {
                val navController = rememberNavController()
                Scaffold(
                    bottomBar = {
                        BottomNavigation {
                            val navBackStackEntry by navController.currentBackStackEntryAsState()
                            val currentDestination = navBackStackEntry?.destination
                            items.forEach { screen ->
                                BottomNavigationItem(
                                    icon = {
                                        Icon(
                                            Icons.Filled.Favorite,
                                            contentDescription = null
                                        )
                                    },
                                    label = { Text(screen.resourceId) },
                                    selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
                                    onClick = {
                                        navController.navigate(screen.route) {
                                            // Pop up to the start destination of the graph to
                                            // avoid building up a large stack of destinations
                                            // on the back stack as users select items
                                            popUpTo(navController.graph.findStartDestination().id) {
                                                saveState = true
                                            }
                                            // Avoid multiple copies of the same destination when
                                            // reselecting the same item
                                            launchSingleTop = true
                                            // Restore state when reselecting a previously selected item
                                            restoreState = true
                                        }
                                    }
                                )
                            }
                        }
                    }
                ) { innerPadding ->
                    NavHost(
                        navController,
                        startDestination = Screen.Profile.route,
                        Modifier.padding(innerPadding)
                    ) {
                        composable(Screen.Profile.route) { Profile(navController) }
                        composable(Screen.FriendsList.route) { FriendsList(navController) }
                    }
                }
            }
        }
    }
}

@Composable
fun Profile(navController: NavController) {
    Column {
        val state = rememberWebViewState("<https://www.nytimes.com/>")

        ComposeWebView(
            state
        )
    }
}


@Composable
fun FriendsList(navController: NavController) {
    Column {
        val state = rememberWebViewState("<https://cnn.com>")

        ComposeWebView(
            state
        )
    }
}


sealed class Screen(val route: String, val resourceId: String) {
    object Profile : Screen("profile", "R.string.profile")
    object FriendsList : Screen("friendslist", "R.string.friends_list")
}

val items = listOf(
    Screen.Profile,
    Screen.FriendsList,
)
FWIW, I tried pulling the state up to the navHost and still the same issue
Copy code
val state1 = rememberWebViewState("<https://www.nytimes.com/>")
                        val state2 = rememberWebViewState("<https://cnn.com>")
                    NavHost(
                        navController,
                        startDestination = Screen.Profile.route,
                        Modifier.padding(innerPadding)
                    ) {
b

Ben Trengrove [G]

02/26/2024, 5:03 AM
The WebViews are being thrown away when you change tabs and so when it comes back it is reloading the initial url
I assume it still applies to your fork
c

Colton Idle

02/26/2024, 5:19 AM
Interesting. It did not work. it never loads the first time (even though the breakpoint for navigating is hit from within the launchedEffect). Let me try just copying webview again from main
interesting. copying main into my app again didn't work. weird. still never loads the first tab.
ah. i wasn't passing the new navigator needed into the webview.
awesome. thank you so much Ben. cheers! can't believe i missed the sample 🤦
b

Ben Trengrove [G]

02/26/2024, 5:42 AM
All good! Glad it worked!
c

Colton Idle

03/06/2024, 7:26 PM
Following up on this... I have a sorta weird issue. I need to add an auth header wtih a token. so this, becomes
Copy code
LaunchedEffect(navigator) {
        val bundle = webViewState.viewState
        if (bundle == null) {
            // This is the first time load, so load the home page.
            navigator.loadUrl("<https://bbc.com>")
        }
    }
something like this
Copy code
LaunchedEffect(navigator) {
        val bundle = webViewState.viewState
        if (bundle == null) {
            // This is the first time load, so load the home page.
            navigator.loadUrl("<https://bbc.com>", "AUTH" to currentTokenSnapshotState)
        }
    }
but when the token changes... the url is (of course) not reloaded since its inside of that LaunchedEffect + bundle == null. What would be your best guess on how to have a dynamic token in the header?
3 Views