Observable/LiveData of ActionBar visibility? - android

I am trying to see if it is possible to create an observable, that would notify when action bar visibility changes.
Something in a way of
LiveData<Boolean> actionBarVisibility;
So that other UI can be updated when actionBar is shown/hidden?
I found this little trick to identify the view visibility change events, but cannot figure out how to apply it to actionBar, since its view is not accessible to me.

Yes, you can observe the state of Boolean!
Inside ViewModel
var isActionBarVisible: MutableLiveData<Boolean> = MutableLiveData()
set visibility
isActionBarVisible.postValue(true)
set invisibility
isActionBarVisible.postValue(false)
====
Inside View (Activity or Fragment)
viewProvider!!.isActionBarVisible.observe(this, Observer {
if (it!!) {
// on visible of action bar
} else {
// on invisible of action bar
}
})

Related

How to use main actvitiy floating action button from fragments in single activity?

I'm using BottomAppbar with floating action button in single activity.The Fab has different responsibility in different fragment.
HomeFragment -> adds new item
AddProblemFragment ->saves item(also icon changes)
DetailsFragment -> adds items to selected collections(also icon changes)
I have tried to use listener between activity and fragments. Icon and position changes perfectly but I couldn't handle calling methods that in fragments when clicked.
To solve clicking problem I'm getting fab reference from activity with
fab = requireActivity().findViewById(R.id.fab) in each fragment (in onViewCreated).
So, problem is after orientation changes gives
java.lang.NullPointerException: requireActivity().findViewById(R.id.fab) must not be null
Is there way to avoid getting this error after orientation changes? or Which lifecycle should choose to initialize fab?
Is it possible to initialize fab in BaseFragment and use it because almost each fragment have fab?
The Code Snippets:
//Each fragment same approach
private lateinit var fab:FloatingActionButton
override fun onViewCreated(view:View,savedInstanceState:Bundle?){
fab = requireActivity().findViewById(R.id.fab_btn_add)
fab.setImageResource(R.drawable.ic_related_icon)
fab.setOnClickListener {
//calls method
}
First you need to create an interface for you fab click listener for Example:
interface FabClickCallback {
void onFabClick()
}
and on your activity,
You Need create an instance from this Callback
private FabClickCallback fabCallback;
and you want create new method inside your Activity called for Example:
public void handleFabClickListener(FabClickCallback callback){
// TODO Here put callback
this.fabCallback = callback;
}
and you want trigger this callback when user click on Fab Button like This:
fab.setOnClickListener ( view -> {
if (this.fabCallback != null ) {
this.fabCallback.onFabClick();
}
});
finally on Each Fragment you want handle Fab Click you want call this function and pass your actual fab click code :
((YourActivity) requireActivity()).handleFabClickListener(this);
and your fragment must implements FabClickCallback interface

Navigation component with bottom navigation

I am working with navigation component and bottom navigation
val navController = indNavController(R.id.nav_host_fragment)
bottom_navigation.inflateMenu(R.menu.bottom_navigation_menu)
bottom_navigation.setupWithNavController(navController)
and I am facing the next issue:
When an item is selected in the bottom navigation, then a fragment is loaded. The problem comes when I press again the same item, then a new fragment will be loaded, which it does not make sense at all.
Example:
1- User selects menu item A, then FragmentA is loaded.
2- User selects again menu item A, then a new FragmentA will be loaded,
I was trying to use
bottom_navigation.setOnNavigationItemSelectedListener { }
But then the bottom navigation will not work with the navController.
So the question is: there is a way to handle this situation in order to load again a new fragment when the user is in that screen already?
Finally, I was able to fix this issue.
As I said before, this code:
bottom_navigation.setupWithNavController(navController)
is using
bottom_navigation.setOnNavigationItemSelectedListener { }
so every time I select / reselect an item, the navController will load a new fragment. I checked the javadoc for setOnNavigationItemSelectedListener() and it says:
Set a listener that will be notified when a bottom navigation item is selected. This listener * will also be notified when the
currently selected item is reselected, unless an {#link *
OnNavigationItemReselectedListener} has also been set.
So what I did is to add the next line to my code:
bottom_navigation.setOnNavigationItemReselectedListener { }
and that's all. The navController will load a fragment when an item is selected but not when an item is reselected in the bottom navigation.
I prefer to use the listener from navController:
navController.addOnDestinationChangedListener {
controller, destination, arguments ->
//destination.id for id fragment
}
So the listener is triggered when the destination changes - not by clicking bottom_navigation.
Because setOnNavigationItemSelectedListener is already used when setupWithNavController is declared.
Try this to ignore the user's click on the same selected item:
bottom_navigation.apply {
setOnNavigationItemSelectedListener {
if (it.itemId == bottom_navigation.selectedItemId) {
true
} else when (it.itemId) { ... }
when you use bottom_navigation.setOnNavigationItemSelectedListener { } before bottom_navigation.setupWithNavController(navController) the OnNavigationItemSelectedListener is overrided inside setupWithNavController function. So use
navController.addOnDestinationChangedListener {
controller, destination, arguments ->
//destination.id for id fragment
}

How to change a Menu of a NavigationView programmatically?

Hello I'm wondering how can I change a Menu ID from a NavigationView, in this case it is triggered when the user clicks on a RadioButton. I have that part already but I just need to know how to change the Menu, so in the menu it will display different options when RadioButton is clicked.
private var radioAdm:RadioButton? = null
private var radioAtle:RadioButton? = null
private var atletanav:BottomNavigationView? = null
private var menuopt = "#menu/admin_menu"
override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) {
// Here is where I need to put that code
}
I've tried this already but no success:
atleta_navbar.set(menu){menu=menuopt}
atleta_navbar.setTag(menuopt)
So in the NavigationView it will go from this:
To this:
In java you could try this to re-inflate NavigationView at runtime.
navigationView.getMenu().clear(); //clear old inflated items.
navigationView.inflateMenu(R.menu.new_navigation_drawer_items);
I don't know kotlin,but the thought is same.
this code work for me as explain by navylover answer
1- make sure you have different menu in my case i have R.menu.navigation and R.menu.navigation_normal
private fun User() {
// clear previous menu
binding.navi.menu.clear()
// add new menu item
binding.navi.inflateMenu(R.menu.navigation)
//add listener for new bottom naviagtion
binding.navi.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener)
// when using Navigation component
//set up bottom navigation bar with navigation graph
NavigationUI.setupWithNavController(binding.navi, navController)
}

How to sync action bar when back button pressed according fragment and activity

I'm studying menu and toolbar/actionbar. I'm trying the way: one activity container and many fragments. I created and setup a toolbar as actionbar for MainActivity (onCreate):
val myToolbar = this.findViewById<Toolbar>(R.id.myToolbar)
setSupportActionBar(myToolbar)
Then, I add itens by normal way with onCreateOptionMenu and handle click with onOptionsItemSelected
When a call Fragment 1, I change action bar and add back button like this (onCreate):
val actBar = (activity as AppCompatActivity).supportActionBar
actBar?.setDisplayHomeAsUpEnabled(true)
actBar?.setDisplayShowHomeEnabled(true)
actBar?.setDisplayUseLogoEnabled(false)
actBar?.title = "Fragment 1 toolbar"
actBar?.subtitle = ""
setHasOptionsMenu(true)
Then from Fragment 1, the Fragment 2 is called and setup as same way:
To handle back button click in fragments, in onOptionsItemSelected:
return if (item.itemId == android.R.id.home) {
activity?.onBackPressed()
true
} else return when (item?.itemId){
...
}
else -> super.onOptionsItemSelected(item)
}
And override onBackPressedin MainActivity:
override fun onBackPressed() {
if(supportFragmentManager.backStackEntryCount > 0){
supportFragmentManager.popBackStackImmediate()
}else{
super.onBackPressed()
}
}
The problem is: if I click on back button, it's backing as expected but the last object of action bar is showed. In MainActivity, only action itens are showed as expected:
How I can sync the bar according fragment and activity?
Note:
I'm using Kotlin, but Java solution are welcome (to convert to kotlin
later)
The fragments are added to back stack
I found a solution. I leave here for whoever interests:
I applied OnBackStackChangedListener that watch changes on back stack. Then, You can make any changes on UI.
supportFragmentManager.addOnBackStackChangedListener {
//UI changes
}
Inside, I check if has some fragment current using the fragment container:
supportFragmentManager.addOnBackStackChangedListener {
val currentFragment = supportFragmentManager.findFragmentById(R.id.you_fragment_container)
if (currentFragment == null){
//rebuild action bar here or make any another changes
}
}
In my case, I compare null that mean container has no fragment. So, if null, the root activity is on screen.
This can be used to make changes for any fragment you want to.
That's it.

Retain focus on previous fragment's view

Suppose I have 5 views (1,2,3,4,5) in a fragment A and each view is focusable.
Then, if the current focus in fragment A is at view 3 and clicking that view loads a new fragment, let's say fragment B.
Now, is there any way that when I press back button, the focus is retained on view 3 of fragment A automatically?
Note: I have added those fragments in the backstack. But my fragment contains recycler views. Clicking on an item of recycler view will load another fragment and on back press the Main Activity gets the default focus.
I can implement focusing on recycler view's first item by implementing fragment's onBackPressListener() method. But I have no idea on how to retain focus on a fragment as it was before.
You can always add these fragment in backstack of Fragment Manager.
If you are adding fragment on top of fragment A with add to backstack, it will resume in last focused item when you press back button
for those who still searching for the solution, I tested many ways but this one was the best for me (Kotlin):
Image
Fragment A
1- block fragment a descendant focusability in onStop() in FRAGMENT A to avoid giving focus to a random button by the activity on BACK
override fun onStop() {
println("**onStop")
(view as ViewGroup?)?.descendantFocusability = ViewGroup.FOCUS_BLOCK_DESCENDANTS
super.onStop()
}
2- make a lambda to do some things on a resume
private var doOnResume: (() -> Unit?)? = null
3- setup the fragment transition
// add a lambda here too
private fun gotoFragment(onResume: () -> Unit) {
//initialize doOnResume here
doOnResume = onResume
(requireActivity() as MainActivity).apply {
supportFragmentManager.commit {
replace(R.id.app_container, DetailsFragment())
addToBackStack(null)
}
}
}
4- I am using leanback RowSupportFragment it automatically set focus t the last item when you request focus to its verticalGridView
listFragment.setOnContentClickedListener {
gotoFragment {
listFragment.verticalGridView.requestFocus()
}
}
note you are able to give focus to any view you want
private fun onPlayButtonClicked() {
binding.playBTN.setOnClickListener {button->
gotoFragment {
button.requestFocus()
}
}
}
5- call your lambda in the onResume
override fun onResume() {
//unblock descendantFocusability again
(view as ViewGroup?)?.descendantFocusability = ViewGroup.FOCUS_AFTER_DESCENDANTS
//avoid crashing app when gridview is not initialized yet or any other reason
try {
doOnResume?.invoke()
} catch (_:Exception) {
}
super.onResume()
}

Categories

Resources