Navigate to another app with navigation component and composable - android

I'm creating an app with navigation component and composable. Imagine that I have an Android app with navHost like this:
NavHost(
navController = navController,
startDestination = RallyScreen.Overview.name,
modifier = Modifier.padding(innerPadding)
) {
composable(RallyScreen.Overview.name) {
Text(RallyScreen.Overview.name)
}
composable(RallyScreen.Accounts.name) {
Text(RallyScreen.Accounts.name)
}
composable(RallyScreen.Bills.name) {
Text(RallyScreen.Bills.name)
}
}
I wonder how can I define navigation to another app like Gmail, or any other apps.

Maybe someone more experienced will correct me, but I think this is not the correct way to approach the problem.
In a nutshell, the NavHost is used to link routes to composables functions of your app, and handle the in-app navigation. Why you want to create a link to another app here? What are you trying to do ?
Rather, in one of your composables functions, you will have for example a button, which starts an activity with an intent.
Button(
onClick = { startActivity(intent) }
) {
Text(text = "Button")
}
You can also do this with the BottomNavigation by using the onClick property of the BottomNavigationItem.

Related

Jetpack Compose Navigation Include start destination screen on navigation stack

I'm recently struggling a lot with some complex logic for deeplink & navigation management with Jetpack Compose Navigation. My actual problem is that when I handle my deeplinks and call navController.navigate(uri) the navigation is working properly but the startDestination screen from the graph is never added to the stack. So its created as:
Actual: Deeplink arrives > Deeplink Screen
Expected: Deeplink arrives > Open Home > Deeplink Screen
My Home screen is the startDestination of the graph where Deeplink Screen belongs to.
Some important notes:
I can't use navController.handleDeeplink because im doing my own deeplink management and this one doesn't work well with dynamic links(firebase) or external web uris. For this reason I always use navController.navigate(uri)
This solution is a workaround but I dont want to use it because im looking for a generic solution to work with nested graphs. Not a huge when clause. It's weird that compose don't support a synthetic stack and this is the "best" workaround. So possibly im missing something.
I don't want to copy/paste and re-implement the NavController. This is a solution aswell but a disgusting one.
Im attaching the code where I call my navigate method with the navOptions. But its more a theory question about Jetpack compose or what I could be missing from the library:
LaunchedEffect(pendingDeeplinkUri.value ) { // Avoid multiple executions using the intent as key
pendingDeeplinkUri.value?.let { uri ->
Timber.d("[DeepLinkManagement] LaunchedEffect pendingDeeplink $uri")
if (navController.graph.hasDeepLink(uri)) { //Check that deeplink exist
//find it in the graph
val match = navController.graph.matchDeepLink(NavDeepLinkRequest.Builder.fromUri(uri).build())
val destination = match!!.destination
navController.navigate(uri,
navOptions {
val changingGraphs = navController.graph.hierarchy.none { it == destination.parent }
Timber.d("[DeepLinkManagement] Changing graphs $changingGraphs")
if (changingGraphs) {
// If we are navigating to a 'sibling' graph (one that isn't part
// of the current destination's hierarchy), then we need to saveState
// to ensure that each graph has its own saved state that users can
// return to
Timber.d("[DeepLinkManagement] Parent ${destination.parent}")
popUpTo(destination.parent?.findStartDestination()?.id ?: 0) {
saveState = true
inclusive = true
}
// Note we specifically don't call restoreState = true
// as our deep link should support multiple instances of the
// same graph in a row
}
}
)
}
}
}
Hope that anyone can give me a hand with this. I'm possibly missing something obvious or maybe is something not supported at the moment. So any information or idea is totally welcome.
Thanks!

How to implement BottomSheet in Material 3 Jetpack Compose Android

I know how to implement BottomSheet in Material 2 Jetpack Compose using BottomSheetScaffold.
But there is no BottomSheetScaffold in Material 3. Also, there is nothing in official samples about BottomSheet.
So I was able to make it work!
It seems that, as of today, BottomSheetScaffold is not available yet on Material3, this is discussed in this issue I found digging around: https://issuetracker.google.com/issues/229839039
I quote the important part from the reply of a google dev:
we aren't in a super easy spot with Swipeable. It currently has a number of critical bugs that need to be addressed first (we are working on this), which is why we are limiting the surface we are exposing for Swipeable in M3 for the time. Our plan for the coming months is to focus on this specific area and improve developer experience.
Material 3 for Jetpack Compose is still in alpha - this means we
consider components production-ready, but the API shape is flexible
while in alpha. This gives us space to iterate while getting
real-world feedback from developers, which ultimately helps improve
your experience. Copy-pasting source code for components that are not
(fully) implemented or exposed in an alpha version can be a good thing
to do in the meantime! Owning the source code while the API shape is
still flexible gives you a number of benefits like ease of updating
dependencies, even if the APIs change, and allows you to evolve your
components in your own pace.
So I just followed the advice and I copy pasted BottomSheetScaffold into my project. Of course it did not work straight away because of a few missing classes and some minor API changes. At the end I was able to make it work by pulling and hacking the following classes and adding them to my project:
BottomSheetScaffold.kt
Drawer.kt
Strings.kt
Swipeable.kt
I have created a gist with the source code if you want to try:
https://gist.github.com/Marlinski/0b043968c2f574d70ee6060aeda54882
You will have to change the import to make it work on your project as well as add the "-Xjvm-default=all" option by adding the following into your gradle file in the android{} section:
android{
...
kotlinOptions {
freeCompilerArgs += ["-Xjvm-default=all"]
// "-Xjvm-default=all" option added because of this error:
// ... Inheritance from an interface with '#JvmDefault' members is only allowed with -Xjvm-default option
// triggered by porting BottomSheetScaffold for Material3 on Swipeable.kt:844
}
}
It works very well for me, will keep this solution until it is officially supported in material3.
Hope it helps!
I got pretty similar results using a fullscreen dialog with AnimatedVisibility, here is the code if interested:
// Visibility state for the dialog which will trigger it only once when called
val transitionState = remember {
MutableTransitionState(false).apply {
targetState = true
}
}
Dialog(
onDismissRequest = {} // You can set a visibility state variable to false in here which will close the dialog when clicked outside its bounds, no reason to when full screen though,
properties = DialogProperties(
// This property makes the dialog full width of the screen
usePlatformDefaultWidth = false
)
) {
// Visibility animation, more information in android docs if needed
AnimatedVisibility(
visibleState = transitionState,
enter = slideInVertically(
initialOffsetY = { it },
animationSpec = ...
),
exit = slideOutVertically(
targetOffsetY = { it },
animationSpec = ...
)
)
) {
Box(
modifier = Modifier.fillMaxSize().background(color = ...)
) {
// Your layout
// This can be any user interraction that closes the dialog
Button(
transitionState.apply { targetState = false }
) ...
}
}
All of this is in a composable function that gets called when a UI action to open said dialog is performed, it's not ideal but it works.
Hope I was able to help!
There is already a great answer by Marlinski, but i would like to add that there is also a ModalBottomSheetLayout which also does not have any implementation for Material 3.
I created a gist for people who need it in use right now:
https://gist.github.com/Pasha831/bdedcfee01acdc96cf3ae643da64f88a
We finally have ModalBottomSheet in Material3.
var openBottomSheet by rememberSaveable { mutableStateOf(false) }
val bottomSheetState = rememberSheetState(skipHalfExpanded = true)
// Sheet content
if (openBottomSheet) {
ModalBottomSheet(
onDismissRequest = { openBottomSheet = false },
sheetState = bottomSheetState,
) {
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
Button(
// Note: If you provide logic outside of onDismissRequest to remove the sheet,
// you must additionally handle intended state cleanup, if any.
onClick = {
scope.launch { bottomSheetState.hide() }.invokeOnCompletion {
if (!bottomSheetState.isVisible) {
openBottomSheet = false
}
}
}
) {
Text("Hide Bottom Sheet")
}
}
}
}
For more link here: https://developer.android.com/reference/kotlin/androidx/compose/material3/package-summary#ModalBottomSheet(kotlin.Function0,androidx.compose.ui.Modifier,androidx.compose.material3.SheetState,androidx.compose.ui.graphics.Shape,androidx.compose.ui.graphics.Color,androidx.compose.ui.graphics.Color,androidx.compose.ui.unit.Dp,androidx.compose.ui.graphics.Color,kotlin.Function0,kotlin.Function1)

Android Jetpack Compose Multiple Layer Navigation

I have multiple destination,and some of the destinations also have subsidiary destinations represented by a Navigation component to interact with.
NavigationBar {
page1 {
NavigationBar {
innerPager1
innerPager2
.....
}
}
page2 {....}
}
I want only the part excluding the navigationBar (innerPager1 & 2) switching not the entire page (page1) , is this possible with navController? if possible how to achieve?
I'm not sure if i fully understood your question but i would start from looking for the documents about Scaffold component. As you will see, you can integrate, among others, a BottomBar component that is the one you're looking for. To switch only the innerPage content you need to change the innerPadding content. This is a general schema:
#Composable
fun MyLayout() {
Scaffold(
bottomBar = { MyBottomBar() }
) { innerPadding ->
BodyContent(Modifier.padding(innerPadding))
}
}
There are many possible approaches to change the BodyContent. I'm going to cite a couple of them:
using State to switch the #Composable method called:
var selectedTab by rememberSaveable { mutableStateOf(BottomNavItemTab.TAB_1) }
...
...
innerPadding ->
Box(modifier = Modifier.padding(0.dp, 0.dp, 0.dp, innerPadding.calculateBottomPadding())) {
when (selectedTab) {
BottomNavItemTab.TAB_1 -> Tab1_Screen()
BottomNavItemTab.TAB_2 -> Tab2_Screen()
BottomNavItemTab.TAB_3 -> Tab3_Screen()
}
}
using Navigation component. In this case i suggest you to read the official docs because they provide well detailed example.
Please keep in mind that you are not forced to use only one of these solutions but you can integrate both of them. I'm saying this because it's not trivial to achieve navigation for tabs with and without BottomBar together.

Laggy/Slow Navigation between BottomNavigation composables - Jetpack Compose

I am using a BottomNavigation with 4 composables. All of them have a LazyColumn with each item in the LazyColumn having an image populated from the network using Coil for Jetpack Compose. Similar to Twitter/YouTube.
When I navigate between these items, the composables get destroyed and recompose only when navigated back to them. Even the coil images are cleared and re-fetched (from memory or local storage) when navigated between these composables. This is of course the expected behavior.
The problem is that this is causing the navigation between them to be too slow. Coil images take about 400ms to 700ms to load the image for every navigation. Apps like YouTube/LinkedIn are literally instant in their BottomBar navigations.
When I was using XML for this, I would make the fragments(used as bottom nav items ) with an appear/disappear logic to avoid this time delay while navigating between them.
How do I achieve the same with Compose ?
I am using the following versions:
//compose navigation
implementation "androidx.navigation:navigation-compose:2.4.0-beta01"
implementation "com.google.accompanist:accompanist-navigation-animation:0.21.0-beta"
Well I had the same problem using emulator or phone both work well with small simplistic composables. And when I create more complex Composable and try animating the navigation, using the accompanist animation library it gets very laggy.
But then I tried building the release APK file, since it is usually optimized and way faster, and that will significantly speed up your app. Here is link on how you can generate signed APK then install it on your phone or emulator and see if that fixes your problem!
You could also check if you accidentally disabled the hardware acceleration from the manifest. Make sure you set android:hardwareAccelerated="true" in you activity tag.
In case that does not help either, then you have to implement your own animation and use Shared ViewModel, for communication and triggering the transition from one Composable to another. The idea is that you can use modifier offset property to show/hide the Composable by placing it outside the screen.
First set your ViewModel, and add mutable state variable, that will trigger the transition from Home to Settings and vice versa.
This is not the best practice, since there is no way to directly pass data from one composable to another as you would normally do with the normal navigation. But you can still share data using the Shared ViewModel. Using this method it will not recompose your Composable, and thus be really fast. So far I have no problem with any out of memory exceptions even on some very old/slow devices with 2GB RAM.
class SharedViewModel : ViewModel() {
// changing this mutable state will trigger the transition animation
private val _switchHomeToSetting = mutableStateOf(true)
val switchHomeToSetting: State<Boolean> = _switchHomeToSetting
fun switchHomeToSettings() {
_switchHomeToSetting.value = !_switchHomeToSetting.value
}
}
Now create your two Composable functions Home and Settings respectively
#Composable
fun HomeScreen(viewModel: SharedViewModel) {
// draw your subcomponents
}
#Composable
fun SettingsScreen(viewModel: SharedViewModel) {
// draw your subcomponents
}
And finally initialize the animation in you main activity
class MainActivity : ComponentActivity() {
val viewModel by viewModels<CityWeatherViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
// set your offset animation
val density = LocalDensity.current
val width = with(density) { (1.adw).toPx() }
val animatedOffsetX: Float by animateFloatAsState(
targetValue = if (!viewModel.switchHomeToSetting.value) 0f else width,
animationSpec = tween(1200)
)
// Home screen
Box(
modifier = Modifier
.fillMaxSize()
.offset { IntOffset((-width + animatedOffsetX).toInt(), 0) }
) {
HomeScreen(viewModel = viewModel)
}
// Settings screen
Box(
modifier = Modifier
.fillMaxSize()
.offset { IntOffset(animatedOffsetX.toInt(), 0) }
) {
SettingsScreen(viewModel = viewModel)
}
}
}
}
Here is the result using the Composable Navigation, together with the Accompanist Animation. As you can see it is very laggy indeed
And now here is the result using our custom animation, where it is very smooth since no Composable is recomposed.

Jetpack Compose expanding BottomSheet of BottomSheetScaffold only works after recomposition

I am trying to have a way to show a BottomSheet from everywhere within my app, for that I use the BottomSheetScaffold and a LiveData object that holds the current composable function which is observed as state:
val sheetContent by MusicHub.state.bottomSheet.getContent().observeAsState()
BottomSheetScaffold(
modifier = Modifier
.fillMaxSize(),
scaffoldState = bottomSheetScaffoldState,
sheetPeekHeight = 0.dp,
sheetContent = sheetContent!!
)
I use the BottomSheet as a context menu in my app. For example when i longClick on a playlist in my app it sets the content of the BottomSheet and shows it like this:
PlaylistItem(
// ...
onLongClick = {
// Set the LiveData composable
MusicHub.state.bottomSheet.setContent {
PlaylistContextMenuTest(playlist!!, viewModel)
}
// Expand BottomSheet
scope.launch {
MusicHub.state.bottomSheet.expand()
}
}
)
In general this works but the first time the BottomSheet gets expanded it shows for a split second before it disappears at the bottom again. Here is a small GIF:
My guess is that somehow the size of the BottomSheet is not recalculated yet and hence it only works in the next recomposition. Coming from web dev i would say its a typical case of requestAnimationFrame but i don't quite know how to solve this issue in compose.
Edit:
PlaylistContextMenuTest code:
#Composable
fun PlaylistContextMenuTest(playlist: Playlist, viewModel: LibraryViewModel = activityViewModel()){
val scrollState = rememberScrollState()
Column(
modifier = Modifier
.fillMaxWidth()
.navigationBarsPadding()
// TODO: Replace that with a percentage of the screen height
.heightIn(max = 384.dp)
.verticalScroll(scrollState),
content = {
ContextMenu {
repeat(4){
addOption(R.drawable.ic_delete_black_24dp, "Delete"){
Timber.d("Delete Playlist")
viewModel.deletePlaylist(playlist)
}
}
}
}
)
}
Full ContextMenu source: (https://pastebin.com/sg4ed96L)
You seem to be right. The first time it seems to be re-calculating the BottomSheet's height.
As this does not change on the second try, it will then be displayed.
Does this answer help you?
It seems to work out quite okay for BottomSheetScaffold, but not for ModalBottomSheetLayout (the thing I was looking for as you can see in the comments of the linked answer).
Edit: Accompanist's Navigation Material is also worth a look.
This issue has been resolved with a new release of jetpack compose. I am not sure if it was beta 8 or 9 but everything works as intended now.

Categories

Resources