How to add conditions to BottomNavigationView + Navigation components? - android

I'm trying to set up conditional navigation for my Fragments using the Navigation components and a BottomNavigationView.
Current setup (without conditions):
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController
NavigationUI.setupWithNavController(bottom_navigation, navController)
General navigation is working fine, but I want to restrict user interaction with the bottom navigation based on conditions. If the condition is not met at the time of clicknig the menu item, this should only result in showing a Toast instead of navigating to the next fragment.
I already looked up this but the solution mentioned there involves navigating to the next fragment first and then check the conditions - but I want to avoid this.
Thank you very much.

Expose LiveData that will stream which #Ids are disabled.
class MainViewModel{
val disableNavigation = MutableLiveData<#Ids Int>()
fun invalidateNavigation() {
val canNavigate = ....
if(!canNavigate){
disableNavigation.value = R.id.bottom_nav_item_x
}
}
}
Then just observe this:
mainViewModel.observe(viewLifecycleOwner){
//disable the id here
//re-enable the rest of the items
}

Related

What to put in parameters of a Screen when putting it in a composable of nav host in jetpack navigation

I have a Screen ( composable function ) that gets It's data from view model ( a list and two function to remove and add data in it ).
#Composable
fun MainScreen(
notes: List<Note>,
onAddNote: (Note) -> Unit,
onRemoveNote: (Note) -> Unit
){}
Now when i call this function inside the composable of my Nav host, I get errors stating that i should fill the parameters.
#Composable
fun NotesNavigation(){
val navController = rememberNavController()
NavHost(navController = navController, startDestination = Navigation.MainScreen.name){
composable(Navigation.MainScreen.name){
MainScreen() // error here
}
}
}
Now I am wondering what is the best practice to sort it out, do i need to provide default values for my parameters like supplying an empty list
or
there is better way to get around it.
You can set default values to the MainScreenFunction, but since you are using navigation, this would become useless. I would suggest to set the viewmodel as a parameter. The viewmodel should still be passed through the navigation though.
I don't know if you use any dependency injection. If so, that would make it a bit easier. Then you can set it up like this:
#Composable
fun MainScreen(
viewModel: MainScreenViewModel = getViewModel() //If using Koin DI
){
...
}
This way, the navigation doesn't have to know about the viewmodel. You can still set it though, if you do need a different viewModel then the one injected for example.

Starting Activity using Compose Navigation

I'm trying to launch an Activity by clicking on a button set on a BottomNavBar. There's a Compose Navigation set up using NavGraphBuilder.navigation() with a composable() call for each Compose screen like:
navigation(
startDestination = "home",
route = "main"
) {
composable("home") {
HomeScreen(...)
}
// Several more Screens
}
I've found out about NavGraphBuilder.activity(), so I was thinking something like:
activity("tickets") {
this.activityClass = ExternalActivity::class
}
And it works, if ExternalActivity doens't need any data to be passed to it. But it does.
The only viable alternative that comes to mind is using a composable() and launching the activity from there:
composable("tickets") { backStackEntry ->
val config = // get config from arguments
context.startActivity(
Intent(context, ExternalActivity::class.java).apply {
putExtra("config", config)
}
)
}
Or something along those lines. But it's kind of messy and has some side effects, so I'd like to avoid it.
Is there any way to use the activity() call and pass data to the Activity being launched?
I'm limited by the architecture of the codebase in which I'm working, so yes, it needs to be an Activity (it's actually from an external library).
Thanks.
The only thing that remotely resembles what you are trying to do would be data.
activity("tickets") {
this.activityClass = ExternalActivity::class
this.data = "Hello World".toUri() <--
}
[ExternalActivity]
override fun onCreate(savedInstanceState: Bundle?) {
...
val data = intent.data
But data is Uri, so it might not suit your needs, especially if you are dealing with an external library. Then context.startActivity() would be the next choice, as in your second approach.
One thing to note is that when you use context.startActivity() (instead of NavGraphBuilder.activity()), you need to set the "current" destination correctly when the Activity closes (e.g. call navController.navigateUp() or navController.popBackStack()). If not, it will jump back to the Activity you just closed, because as far as NavController is concerned, the Activity you started (and now closed) is the current destination.

Reload fragment using Navigation Component

I am using the navigation component to load my fragments like given here:
https://developer.android.com/guide/navigation/navigation-getting-started
navController = findNavController(R.id.nav_host_fragment)
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
val appBarConfiguration = AppBarConfiguration(
setOf(
R.id.navigation_opt1,
R.id.navigation_opt2,
R.id.navigation_opt3,
R.id.navigation_more
)
)
setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)
I also have a Toolbar at the top with a Spinner. I want to reload/refresh the fragment & its view model when the spinner item is selected. I tried the below code for reloading but it isn't working. Also, I know we should not be using fragmentManager in Navigation Component
val ftr: FragmentTransaction = requireFragmentManager().beginTransaction()
ftr.detach(this).attach(this).commit()
Any help would be appreciated.
sorry for late reply. I am using below code for the same scenario.
That might help. call this method in your spinner ItemClick.
we have to use popbackstack for avoid adding same fragment again and again.
private fun refreshCurrentFragment(){
val id = navController.currentDestination?.id
navController.popBackStack(id!!,true)
navController.navigate(id)
}
Yes above answer https://stackoverflow.com/a/66171.. is right
if you want to reload/refresh you current fragment after perform specific operation
you have to destroy this current fragment and navigate here again
val fragmentId = findNavController().currentDestination?.id
findNavController().popBackStack(fragmentId!!,true)
findNavController().navigate(fragmentId)

set default destination in navigation architecture component based on user type

I'm working on navigation component architecture and I face problem in how to set start destination after I authenticate user since I have two type of users :admin and user and I made my app contains 4 activities
loginActivity ,signUpActivity userMainActivity : that use navigation component and contain all fragments related to user
and adminMainActivity :that use navigation component and contain all fragments related to admin
I'm not able to figure out how to handle this and start user or admin activity after login by set default
should I put user and admin in separate navigation graph or what to in this case
you can set start destination programmatically, just like:
val navHostFragment = nav_host_fragment as NavHostFragment
val graphInflater = navHostFragment.navController.navInflater
navGraph = graphInflater.inflate(R.navigation.nav_graph)
navController = navHostFragment.navController
val destination = if (intent.getBooleanExtra(IS_ADMIN,false))
R.id.adminhomeFragment
else R.id.userhomeFragment
navGraph.startDestination = destination
navController.graph = navGraph

Android Navigation Architecture Component - Programmatically

Looking for examples or anything similar that takes Swift (iOS) code like this:
let navController = UINavigationController(rootViewController: initialView)
and sets it up in Kotlin, via the new Navigation component. I've referenced the following examples, but it's not making complete sense to me:
val myNavHostController: NavHostFragment = nav_host_fragment as NavHostFragment
val inflater = myNavHostController.navController.navInflator
val graph = inflater.inflate(R.layout.nav_graph)
myNavHostController.navController.graph = graph
and
val finalHost = NavHostFragment.create(R.navigation.example_graph)
supportFragmentManager.beginTransaction()
.replace(R.id.nav_host, finalHost)
.setPrimaryNavigationFragment(finalHost) // this is the equivalent to app:defaultNavHost="true"
.commit()
It appears the Android examples I'm coming across, still require an Activity/Fragment to already be established in the XML file created by the navigation component ... but, what if I want this functionality to be dynamic? What if I need to set the 'host' activity for the nav component based on data passed in? I am in need of the ability to do this all via code, which is what the Swift line is doing (setting the 'initialView' UIViewController, as the 'host', which has the navigation controller embedded in it). None of it is done via storyboarding, which seems to be what Android wants me to do regardless...
I am certain the issue is me not fully understanding how this works in Android, which is what I really would like to learn.

Categories

Resources