There are three types of toolbar used in this app. (toolbar, toolbar2, toolbar3)
The MainActivity is connected to four fragments through the bottom navigation bar. (home, community, calendar, mypage)
toolbar - used in "MainActivity" (home, community, calendar, mypage)
toolbar2 - used when navigating to MainActivity -> "NoticeFragment"
toolbar3 - used when navigating to MainActivity -> CommunityFragment -> "CommunityItemFragment"
I want to put the option menu in toolbar3 only.
However, if I write onCreateOptionsMenu in the MainActivity, an option menu created only in toolbar.
How do I solve this problem?
<activity_main.xml>
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/white"
android:minHeight="?attr/actionBarSize"
android:theme="?attr/actionBarTheme"
android:visibility="visible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"></androidx.appcompat.widget.Toolbar>
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/white"
android:minHeight="?attr/actionBarSize"
android:theme="?attr/actionBarTheme"
android:visibility="invisible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"></androidx.appcompat.widget.Toolbar>
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/white"
android:minHeight="?attr/actionBarSize"
android:theme="?attr/actionBarTheme"
android:visibility="invisible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"></androidx.appcompat.widget.Toolbar>
<MainActivity.kt>
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
private val fl: FrameLayout by lazy {
findViewById(R.id.main_frm)
}
private var backPressedTime : Long = 0
#SuppressLint("MissingInflatedId")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
replaceFragment(NaviCommunityFragment())
val main_bnv = findViewById<BottomNavigationView>(R.id.main_bnv)
setSupportActionBar(toolbar)
supportActionBar?.setDisplayShowTitleEnabled(false)
var noticeitem = findViewById<ImageView>(R.id.noticeitem)
var toolbar = findViewById<Toolbar>(R.id.toolbar)
var toolbar2 = findViewById<Toolbar>(R.id.toolbar2)
var toolbar3 = findViewById<Toolbar>(R.id.toolbar3)
close_notice.setOnClickListener {
val transaction = supportFragmentManager.popBackStack()
toolbar.visibility = View.VISIBLE
toolbar2.visibility = View.INVISIBLE
toolbar3.visibility = View.INVISIBLE
}
noticeitem.setOnClickListener{
val transaction = supportFragmentManager.beginTransaction()
.replace(R.id.main_frm, NoticeFragment())
.addToBackStack(null)
transaction.commit()
toolbar.visibility = View.INVISIBLE
toolbar2.visibility = View.VISIBLE
toolbar3.visibility = View.INVISIBLE
}
btn_back.setOnClickListener {
val transaction = supportFragmentManager.popBackStack()
toolbar.visibility = View.VISIBLE
toolbar2.visibility = View.INVISIBLE
toolbar3.visibility = View.INVISIBLE
}
main_bnv.setOnItemSelectedListener { item ->
changeFragment(
when (item.itemId) {
R.id.navigation_home -> {
main_bnv.itemIconTintList = null
main_bnv.itemTextColor = null
NaviHomeFragment()
}
R.id.navigation_community -> {
main_bnv.itemIconTintList = null
NaviCommunityFragment()
}
R.id.navigation_calendar -> {
main_bnv.itemIconTintList = null
Calendar_fragment()
}
else -> {
main_bnv.itemIconTintList = null
NaviMypageFragment()
}
}
)
true
}
main_bnv.selectedItemId = R.id.navigation_home
}
private fun changeFragment(fragment: Fragment) {
supportFragmentManager.popBackStackImmediate()
supportFragmentManager
.beginTransaction()
.replace(R.id.main_frm, fragment)
.commit()
}
private fun replaceFragment(naviCommunityFragment: Fragment){
val fragmentManager = supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction()
fragmentTransaction.replace(R.id.main_frm, naviCommunityFragment)
fragmentTransaction.commit()
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.toolbar3_menu, menu)
return super.onCreateOptionsMenu(menu)
}
}
<NaviCommunityAdapter.kt>
override fun onBindViewHolder(holder: NaviCommunityViewHolder, position: Int) {
holder.title.text = itemList[position].title
holder.nickname.text = itemList[position].nickname
holder.itemView.setOnClickListener(object : View.OnClickListener{
override fun onClick(p0: View?) {
val activity=p0!!.context as AppCompatActivity
val communityItemFragment = CommunityItemFragment()
activity.supportFragmentManager.beginTransaction().replace(R.id.main_frm, communityItemFragment)
.addToBackStack(null).commit()
activity.toolbar.visibility = View.INVISIBLE
activity.toolbar3.visibility = View.VISIBLE
}
})
}
I thought that using onCreateOptionsMenu in MainActivity would have an option menu in all toolbars.
So I wanted to make the option menu only when toolbar3 is visible.
However, option menus are created only in toolbar, and not in toolbar2 or toolbar3.
For my solution first you have to create a static Menu in your main activity:
public static Menu your_menu;
After that you have to saved that menu with your menu
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.toolbar3_menu, menu);
your_menu = menu;
return super.onCreateOptionsMenu(menu);
}
Then in your fragment if you want to show it just call it and set its visible to true
Like if you want your menu to show only in CommunityItemFragment then in that fragment set it
MainActivity.your_menu.setVisible(true);
and set it to false in other fragements or just set it false with check fragment condition in your MainActivity
Related
My activity has a FrameLayout container for fragments and a BottomNavigationView to navigate between the fragments: Home, shop, account and cart. Navigating between these fragments works fine, but when navigating to new fragment (SignUpFragment) from AccountFragment when pushing the button 'signup' it crashes.
using supportFragmentManager didnt work like in MAinActivity so i am trying to use childFragmentManager instead, but the app crashes because it can not find the FrameLayout container in MainActivity.
MainAct:
class MainActivity : AppCompatActivity() {
lateinit var homeFragment: HomeFragment;
lateinit var shopFragment: ShopFragment;
lateinit var accountFragment: AccountFragment;
lateinit var cartFragment: CartFragment;
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
homeFragment = HomeFragment()
makeCurrentFragment(homeFragment)
btm_nav.setOnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.home -> {
homeFragment = HomeFragment()
makeCurrentFragment(homeFragment)
}
R.id.shop -> {
shopFragment = ShopFragment()
makeCurrentFragment(shopFragment)
}
R.id.account -> {
accountFragment = AccountFragment()
makeCurrentFragment(accountFragment)
}
R.id.cart -> {
cartFragment = CartFragment()
makeCurrentFragment(cartFragment)
}
}
true
}
}
fun makeCurrentFragment(fragment: Fragment) =
supportFragmentManager.beginTransaction().replace(R.id.frame_layout, fragment)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) //open = adds a new fragment to the stack
.commit()
}
Layout main:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/colorwhite"
tools:context=".MainActivity"
tools:ignore="ExtraText">
<FrameLayout
android:id="#+id/frame_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="#+id/btm_nav"
android:layout_marginBottom="12dp" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/btm_nav"
android:layout_alignParentBottom="true"
app:itemBackground="#color/colorwhite"
app:menu="#menu/bottom_nav"/>
</RelativeLayout>
AccountFragmenet:
class AccountFragment : Fragment() {
lateinit var signUpFragment: SignUpFragment;
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
btn_forgotPass.setOnClickListener {
Toast.makeText(
activity, "btn forgot pressed",
Toast.LENGTH_SHORT
).show()
}
btn_signIn_google.setOnClickListener {
Toast.makeText(
activity, "btn google pressed.",
Toast.LENGTH_SHORT
).show()
}
btn_login.setOnClickListener {
Toast.makeText(
activity, "btn login pressed.",
Toast.LENGTH_SHORT
).show()
}
/********** The problem is here ****/
btn_signup.setOnClickListener {
signUpFragment = SignUpFragment();
childFragmentManager.beginTransaction().replace(R.id.frame_layout, signUpFragment)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.commit()
}
}
}
Errormessage in logcat is :
java.lang.IllegalArgumentException: No view found for id 0x7f0800b7 (no.store.maast:id/frame_layout) for fragment SignUpFragment{b0a086e (7c1e4b61-3b07-48dd-b700-83748b0714c6) id=0x7f0800b7}
you can't access your frame layout in account fragment .
you must replace your fragment with your account fragment root layout .
btn_signup.setOnClickListener {
signUpFragment = SignUpFragment();
childFragmentManager.beginTransaction()
// give an id to your layout root account fragment
.replace(R.id.accountFragmentId, signUpFragment)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.commit()
}
Using ChildFragmentMAnager did not work on mine application no matter what I did, but i found a useful link that solved my problem:
How do i open fragment from fragment (kotlin)
My working code :
btn_signup.setOnClickListener {
signUpFragment = SignUpFragment();
val transaction = activity?.supportFragmentManager?.beginTransaction()
transaction?.replace(R.id.frame_layout, signUpFragment)
transaction?.addToBackStack("BackToSignInPage")
transaction?.commit()
}
I have a four navigation in MainActivity.
home_navigation
(homeFragment -> homereportFragment)
device_navigation
(devicefragment -> deviceUserFragment -> deviceScanFragment)
and config_navigation, automation_navigation ..
I want to move to deviceUserFragment in homeFragment
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.main.MainActivity">
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottom_nav"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:menu="#menu/menu_bottom" />
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/slider_main"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/bottom_nav" />
</androidx.constraintlayout.widget.ConstraintLayout>
private val mOnNavigationItemselectedListener =
BottomNavigationView.OnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.home_navigation -> {
slider_main.currentItem = 0
return#OnNavigationItemSelectedListener true
}
R.id.device_navigation -> {
slider_main.currentItem = 1
return#OnNavigationItemSelectedListener true
}
R.id.automation_navigation -> {
slider_main.currentItem = 2
return#OnNavigationItemSelectedListener true
}
R.id.config_navigation -> {
slider_main.currentItem = 3
return#OnNavigationItemSelectedListener true
}
}
false
}
private val onpage = object : ViewPager2.OnPageChangeCallback() {
override fun onPageScrollStateChanged(state: Int) {
}
override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int
) {
}
override fun onPageSelected(position: Int) {
bottom_nav.menu.getItem(position).isChecked = true
}
}
private fun initViewPager() {
viewpagerAdapter = SliderAdapterMain(this)
slider_main.apply {
adapter = viewpagerAdapter
registerOnPageChangeCallback(onpage)
}
bottom_nav.setOnNavigationItemSelectedListener(mOnNavigationItemselectedListener)
}
MainActivity code with viewPager2 and bottomnavigation
home_reistdevice_button.setOnClickListener {
activity!!.bottom_nav.selectedItemId = R.id.device_navigation
}
It's button in homeFragment.
But button results deviceFragment.
I tried to add deviceUserFragment in home_navigation graph.
But It doesn't work bottom navigation Item.
I think I should pass value homeFragment to deviceFragment.
So if deiviceFragment get value, navigate to deviceUserFragment as soon as deviceFragment start.
But i don't know how to pass value in bottomnavigation item select.
Somebody tell me how to do it.
Or tell me the best way.
#Edit 2020-09-03
I found a solution.
Solution is onBacklistener adding in ContainerFragmnet
class HomeContainerFragment : Fragment() {
private var navController:NavController?=null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_home_container, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val navHostFragment = childFragmentManager.findFragmentById(R.id.home_nav_host) as NavHostFragment?
navController = navHostFragment?.navController
listenOnBackPressed()
}
private fun listenOnBackPressed(){
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner,callback)
}
override fun onResume() {
super.onResume()
callback.isEnabled = true
}
override fun onPause() {
super.onPause()
callback.isEnabled =false
}
private fun dialog_build(){
val builder = AlertDialog.Builder(ContextThemeWrapper(activity, R.style.AlertDialog))
builder.setTitle("App terminate")
builder.setMessage("Are you terminating APP?")
builder.setPositiveButton("terminate") { _, _ ->
activity?.finishAffinity()
}
builder.setNegativeButton("cancel") { _, _ ->
}
builder.show()
}
val callback = object : OnBackPressedCallback(false) {
override fun handleOnBackPressed() {
var backkeytime: Long = 0
lateinit var toast: Toast
if(navController?.currentDestination?.id == navController?.graph?.startDestination){
isEnabled = false
dialog_build()
isEnabled = true
}
else{
navController?.navigateUp()
}
}
}
}
My whole project structure is 1 Activity 4 ContainerFragment(4 navigation)
MainActivity (ViewPager2, BottomNavigation)
HomeContainerFragment ( FragmentContainerView - home_navigation )
(in navigation) - HomeFragment
- HomeReportFragment
- ..
DeviceContainerFragment
...
Normally, I use val navController: NavController = findNavController(R.id.nav_host_fragment) in Code A to find NavController, it's based R.id.nav_host_fragment.
Now I use view binding in the app just like Code B, how can I NavController if I use view binding ?
BTW, in my mind R.id.nav_host_fragment will not be available in view binding , right?
Code A
class TasksActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.tasks_act)
val navController: NavController = findNavController(R.id.nav_host_fragment)
}
}
Code B
class TasksActivity : AppCompatActivity() {
private lateinit var binding: TasksActBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = TasksActBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
//val navController: NavController = findNavController(R.id.nav_host_fragment)
}
}
tasks_act.xml
<androidx.drawerlayout.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".tasks.TasksActivity"
tools:openDrawer="start">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?attr/actionBarSize"
android:theme="#style/Toolbar"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light" />
</com.google.android.material.appbar.AppBarLayout>
<fragment
android:id="#+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="#navigation/nav_graph" />
</LinearLayout>
..
</androidx.drawerlayout.widget.DrawerLayout>
The code B should still be working fine .
I made a look into findNavController() . This is an extension function for simpifying the code , the code for extension function is
fun Activity.findNavController(#IdRes viewId: Int): NavController =
Navigation.findNavController(this, viewId)
Now looking in to the code of findNavController() inside Navigation we see below
#NonNull
public static NavController findNavController(#NonNull Activity activity, #IdRes int viewId) {
View view = ActivityCompat.requireViewById(activity, viewId);
NavController navController = findViewNavController(view);
if (navController == null) {
throw new IllegalStateException("Activity " + activity
+ " does not have a NavController set on " + viewId);
}
return navController;
}
The viewId we are passing in argument is used in the first line
View view = ActivityCompat.requireViewById(activity, viewId);
now looking into requireViewById() inside ActivityCompat we see
#NonNull
public static <T extends View> T requireViewById(#NonNull Activity activity, #IdRes int id) {
if (Build.VERSION.SDK_INT >= 28) {
return activity.requireViewById(id);
}
T view = activity.findViewById(id);
if (view == null) {
throw new IllegalArgumentException("ID does not reference a View inside this Activity");
}
return view;
}
for api 28 and plus the method which gets called is
#NonNull
public final <T extends View> T requireViewById(#IdRes int id) {
T view = findViewById(id);
if (view == null) {
throw new IllegalArgumentException("ID does not reference a View inside this Activity");
}
return view;
}
So as long as the view (inside which the nav_host_fragment is present) is attached to activity the code which you wrote for finding the nav controller should work completely fine .
class TasksActivity : AppCompatActivity() {
private lateinit var binding: TasksActBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = TasksActBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view) //you are attaching view to activity here , make sure you always call this before the next line , else you will get IllegalStateException
val navController: NavController = findNavController(R.id.nav_host_fragment)
}
}
I have not tested the code personally but from what I see it should work perfectly fine.
We are using ViewBinding to get a reference of view itself. If you are using viewbinding in TextView, you will get a reference of the View.
In the case of NavCotroller, If you use Viewbinding, you will get a reference to a fragment, while findNavController expect ViewId of type integer.
findNavController(#IdRes viewId: int)
The correct working way is as follows:
FragmentContainerView has to be accessed from the supportFragmentManager:
val navHostFragment = supportFragmentManager.findFragmentById(R.id.fragment) as NavHostFragment
val navController = navHostFragment.navController
I am doing a sign up procedure and it is divided into three steps, so i am using a shared view model between all the three Fragments and the register activity.
What i want to do is after i load the next fragment, i make some UI updates to show an other toolbar, what is working is the replace Fragment and he is acceding the update UI method successfully but no change is made on the UI
override fun navigateToSignUp() {
setFragment(
R.id.home_frame,
SignUpFragment()
)
showTitleInToolbarBackParam("Register", true)
}
private fun showTitleInToolbarBackParam(title: String, back: Boolean) {
Log.e(TAG, "showTitleInToolbarBackParam: ")
getDataBinding()!!.appbar.toolbar.visibility = VISIBLE
getDataBinding()!!.homeappbar.homeappbar.visibility = GONE
getDataBinding()!!.appbar.titleTextView.text = title
if (back)
getDataBinding()!!.appbar.backImageView.visibility = VISIBLE
else
getDataBinding()!!.appbar.backImageView.visibility = GONE
}
class SignUpFragment : BaseFragment<FragmentSignUpBinding>() {
private lateinit var viewModel: WelcomingViewModel
override fun setViewModel() {
viewModel = activity.run {
ViewModelProviders.of(this!!).get(WelcomingViewModel::class.java)
}
}
override fun init() {
getDataBinding().viewModel = viewModel
}
override fun getLayoutId(): Int {
return R.layout.fragment_sign_up
}
}
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewModel"
type="com.itchek.presentation.welcoming.WelcomingViewModel" />
</data>
And in the binding of all the fragments in the XML files i used the same view model
This is the module that i had with it the issue
The modern way of inflating fragments is navigation.
Use addOnDestinationChangedListener() function to change title or visibility of toolbar.
val navController = findNavController(R.id.nav_host_fragment)
navController.addOnDestinationChangedListener { _, destination, _ ->
when (destination.id) {
R.id.navigation_list -> {
navBar.visibility = View.GONE
supportActionBar!!.title = "List"
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
}
R.id.navigation_select -> {
navBar.visibility = View.GONE
supportActionBar!!.title = "Select"
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
}
else -> {
navBar.visibility = View.VISIBLE
supportActionBar!!.title = getString(R.string.app_name)
supportActionBar!!.setDisplayHomeAsUpEnabled(false)
}
}
}
I have two fragments and I want to create interaction between them using backward button in action bar. Ideally, I would like the state of previous fragment was saved.
I could find information for only activities.
For fragments I found this
private fun setupBackButton() {
if (activity is AppCompatActivity) {
(activity as AppCompatActivity?)?.supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
}
But it only displays back button, nothing happens by clicking.
EDIT
In the first fragment I call the second like:
val fragment = UserContentFragment()
fragment.setUser(item.user)
if (fragmentManager != null) {
fragmentManager!!
.beginTransaction()
.replace(R.id.main_layout, fragment)
.addToBackStack(null)
.commit()
}
This is my UserContentFragment second fragment:
class UserContentFragment : Fragment() {
private lateinit var user: SearchUser
fun setUser(user: SearchUser) {
this.user = user
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val root = inflater.inflate(R.layout.fragment_user_content, container, false)
val userImage = root.findViewById(R.id.user_img) as ImageView
if (context != null) {
Glide.with(context!!)
.load(user.profile_pic_url)
.circleCrop()
.into(userImage)
}
val userName: TextView = root.findViewById(R.id.user_name)
userName.text = user.full_name
val toolbar: Toolbar = root.findViewById(R.id.toolbar)
toolbar.setNavigationOnClickListener { requireActivity().onBackPressed() }
setupBackButton()
return root
}
private fun setupBackButton() {
if (activity is AppCompatActivity) {
(activity as AppCompatActivity?)?.supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
}
}
And this its .xml file:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/colorBlack">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/user_title"
android:layout_width="match_parent"
android:layout_height="100dp">
<ImageView
android:id="#+id/user_img"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_marginStart="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription" />
<TextView
android:id="#+id/user_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="16dp"
android:textColor="#color/colorWhite"
android:textSize="22sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/user_img"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</RelativeLayout>
In order to have a response when you hit the Home/up Button, here are a couple of options to solve this:
First Option
In the fragments that you show up the Home/UP button, override onOptionsItemSelected() method and call the activity's onBackPressed() for home button id
override fun onOptionsItemSelected(item: MenuItem): Boolean {
// Handle presses on the action bar menu items
when (item.itemId) {
android.R.id.home -> {
activity?.onBackPressed()
return true
}
}
return super.onOptionsItemSelected(item)
}
Side note:
Instead of showing the Home/Up button on the ActionBar using below, and you've to set the boolean value for each fragment that you need to show up the home button using the method below:
private fun setupBackButton() {
if (activity is AppCompatActivity) {
(activity as AppCompatActivity?)?.supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
}
You can instead setup the ActionBar with AppBarConfiguration in onCreate() method of the activity as below:
private lateinit var appBarConfiguration: AppBarConfiguration
override fun onCreate(savedInstanceState: Bundle?) {
val host: NavHostFragment = supportFragmentManager
.findFragmentById(R.id.my_nav_host_fragment) as NavHostFragment? ?: return
val navController = host.navController
appBarConfiguration = AppBarConfiguration(
setOf(R.id.second_fragment, R.id.third_fragment)) // IDs of fragments you want without the ActionBar home/up button
setupActionBarWithNavController(navController, appBarConfiguration)
}
By doing this, the up button will show up in all fragments, but R.id.second_fragment, R.id.third_fragment and you no longer need to set the setDisplayHomeAsUpEnabled() to each individual fragment to show up the home/up button. But still you need to override onOptionsItemSelected as mentioned above.
Second Option
Which is neater than the first option. First, you've to implement the above side node to allow the NavController auto controls/configure the ActionBar.
So, the past side note is a mandatory part of this option.
Then override onSupportNavigateUp() in the activity which allows NavigationUI to support proper ActionBar up navigation.
override fun onSupportNavigateUp(): Boolean {
return findNavController(R.id.my_nav_host_fragment).navigateUp(appBarConfiguration)
}
And then override onOptionsItemSelected() in activity to make Have Navigation UI Handle the OptionsMenu/ActionBar item selection
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return item.onNavDestinationSelected(findNavController(R.id.my_nav_host_fragment))
|| super.onOptionsItemSelected(item)
}
I would say that option 2 is neater than 1, because you write all the code in one place (activity) without touching fragments, and also it automatically configure all the fragments, no need to manually setDisplayHomeAsUpEnabled() or activity.onBackPressed() for individual fragments that you want the Home/Up button to show up.
In Kotlin Fragment with Navigation.
First, you add setHasOptionsMenu(true) in onCreate
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
Then override the onOptionsItemSelected and when R.id.home you can control BACK Button
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.getItemId()) {
android.R.id.home ->
findNavController().navigate(R.id.action_FragmentTwo_to_FragmentOne)
}
return true
}
You need to attach a click listener to the toolbar like :
toolbar.setNavigationOnClickListener { requireActivity().onBackPressed() }
class StartActivity : FragmentActivity() {
/**
* The pager widget, which handles animation and allows swiping horizontally to access previous
* and next wizard steps.
*/
private lateinit var mPager: ViewPager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.start_activity)
val loginButton = findViewById<Button>(R.id.login_button)
loginButton.setOnClickListener {
this.didTapLoginButton()
}
}
private fun didTapLoginButton() {
val i = Intent(this, LoginActivity::class.java)
startActivity(i)
}
}
class LoginActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.login_activity)
//actionbar
val actionbar = supportActionBar
//set actionbar title
actionbar!!.title = "New Activity"
//set back button
actionbar.setDisplayHomeAsUpEnabled(true)
actionbar.setDisplayHomeAsUpEnabled(true)
}
override fun onSupportNavigateUp(): Boolean {
onBackPressed()
return true
}
// it is important function, you need to write this function in which class/activity you want to show back arrow
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return super.onOptionsItemSelected(item)
}
}
Add this in the NavigationClickListener.
FragmentManager fm = getFragmentManager();
if (fm.getBackStackEntryCount() > 0) {
Log.i("MainActivity", "popping backstack");
fm.popBackStack();
} else {
Log.i("MainActivity", "nothing on backstack, calling super");
super.onBackPressed();
}
Zain's Second Option is very good, but I'll show a different way for the part made in onCreate, with usage of DataBinding:
ActivityMainBinding.inflate(layoutInflater).run {
setContentView(root)
setSupportActionBar(toolbar)
(supportFragmentManager.findFragmentById(R.id.my_nav_host_fragment)
as NavHostFragment).navController.let { navController ->
val appBarConfiguration = AppBarConfiguration(navController.graph)
toolbar.setupWithNavController(navController, appBarConfiguration)
}
}
It's a late answer but I hope it will help someone.
I did it in Kotlin and the way I managed to make it work is:
In the MainActivity I overrode the onSupportNavigateUp:
override fun onSupportNavigateUp(): Boolean {
val navController = findNavController(R.id.nav_host_fragment_activity_main)
return navController.navigateUp(appBarConfiguration)
|| super.onSupportNavigateUp()
}
And declared the AppBarConfiguration outside the onCreate()