Add custom layout to standard Toolbar - android

I want to add my custom layout to the side of the existing Toolbar, keeping all features with NavigationUI (Up button, animations, placing up buttons and drawers, etc)
How can i achieve this?

After some research i`ve found out the solution
MainActivity.kt
class MainActivity : AppCompatActivity() {
private val navController by lazy { findNavController(R.id.main_nav_host_fragment) }
private val appBarConfiguration by lazy { AppBarConfiguration(navController.graph) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navController = this.findNavController(R.id.main_nav_host_fragment)
NavigationUI.setupActionBarWithNavController(this, navController)
NavigationUI.setupWithNavController(
findViewById<NavigationView>(R.id.navigation_view),
navController
)
}
override fun onSupportNavigateUp(): Boolean {
val navController = this.findNavController(R.id.main_nav_host_fragment)
return NavigationUI.navigateUp(navController, appBarConfiguration)
}
}
FragmentToolbar.kt interface
interface FragmentToolbar {
fun clearActionBar()
fun setCustomActionBar()
}
MainFragment.kt
Next class MainFragment.kt is abstract with methods clearActionBar() and setCustomActionBar() that can be overridden. They are defined in FragmentToolbar.kt interface, because if you set custom layout in first fragment, you will see it also in all other fragments. So, you will almost always have to clear your ActionBar and this class is responsible for standard realisation. setCustomActionBar() is up to you\
abstract class MainFragment : Fragment(), FragmentToolbar {
val actionBar: ActionBar? by lazy { (requireActivity() as AppCompatActivity).supportActionBar }
override fun clearActionBar() {
actionBar?.apply {
setDisplayShowCustomEnabled(false)
setDisplayShowTitleEnabled(true)
}
}
override fun setCustomActionBar() {}
}
MyFragment.kt
class MyFragment : MainFragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
clearActionBar() // defined in MainFragment
// Your code
}
override fun setCustomActionBar() {
actionBar?.apply {
setDisplayShowCustomEnabled(true)
setCustomView(R.layout.view_cart_and_bill)
}
actionBar?.customView?.apply { /* Here use findViewById to find necessary views inside your custom layout */ }
}
}
Hope my answer helps you. It also would be great, if ViewBinding could be used with this layout added in runtime, but i have not solved this issue yet

Related

How to persist navigating component destination after minimizing the app

I have an app with 3 main destinations which can be accessed by a bottom nav view. Each destination has its own navigation graph.
The problem is that when I minimize and reopen my app, the navigation components reset to the default destination. Why does this happen?
My main activity: (Irrelevant code omitted)
class MainActivity : AppCompatActivity() {
// List of base host containers
val fragments = listOf(
HomeHostFragment(),
CoursesHostFragment(),
SearchHostFragment()
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val viewPager: ViewPager = findViewById(R.id.main_activity_pager)
viewPager.adapter = ViewPagerAdapter()
}
inner class ViewPagerAdapter : FragmentPagerAdapter(supportFragmentManager) {
override fun getItem(position: Int): Fragment =
fragments[position]
override fun getCount(): Int =
fragments.size
}
}
HomeHostFragment.kt:
class HomeHostFragment : Fragment() {
private lateinit var navController: NavController
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
inflater.inflate(R.layout.fragment_home_host, container, false)
override fun onStart() {
super.onStart()
navController = requireActivity().findNavController(R.id.nav_host_home)
navController.setGraph(R.navigation.nav_graph_home)
NavigationUI.setupWithNavController(toolbar_home, navController)
}
fun onBackPressed(): Boolean {
return navController.navigateUp()
}
}
Whenever onStart() is called, NavigationUI.setupWithNavController() is called again which resets the navigation. Move this call to onViewCreated() so that the navigation setup is not done every time the fragment pauses and restarts.

android Navigation controller go back one level

I'm using navigation controller.
I was wondering should I add an additional destination to go back? or there is a builtin call that does that.
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_sign_up, container, false).apply {
val navController = NavHostFragment.findNavController(this#SignUpFragment)
this.btnSignUp.setOnClickListener {
navController.navigate(R.id.action_signUpFragment_to_homeActivity)
}
this.btnGotoLogin.setOnClickListener {
navController.navigate(R.id.action_signUpFragment_to_loginFragment)
}
this.back_to_auth_selection.setOnClickListener {
// should I add another action ?
}
}
}
this.back_to_auth_selection.setOnClickListener
{
activity?.onBackPressed()
}
You can also include an Up-button on each fragment other than the home fragment
To do this in main activity`
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// use nav controller to add Up-Button to the app
val navController = this.findNavController(R.id.nav_host_fragment)
// link the navigation controller to the app bar
NavigationUI.setupActionBarWithNavController(this, navController)
}
//override onSupportNavigateUp() to call navigateUp() in the navigation controller
override fun onSupportNavigateUp(): Boolean {
val navController = this.findNavController(R.id.nav_host_fragment)
return navController.navigateUp()
}
}`
Sorry my code is in Kotlin

Pass click event on a view in a Fragment to the Activity

I am trying to pass click event on a view in a Fragment to the Activity. I was following documentation guide from here.
As I understand it and I could be wrong but we need to create an interface, add a method declaration to it and trigger the method from when the click event is received on the Fragment. The Activity should then implement the interface defined in the fragment, so that the activity receives that event.
I have my Fragment:
class MoreFragment : Fragment() {
internal var callback: OnMoreItemClickedListener
fun setOnMoreItemClickedListener(callback: OnMoreItemClickedListener) {
this.callback = callback
}
interface OnMoreItemClickedListener {
fun onAddClothClicked()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_more, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
}
override fun onStart() {
super.onStart()
btn_add_clothes.setOnClickListener {
callback.onAddClothClicked()
}
}
}
And I have my Activity:
class MainActivity : AppCompatActivity(), MoreFragment.OnMoreItemClickedListener {
override fun onAddClothClicked() {
Log.d("MainActivity", "HERE")
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navView: BottomNavigationView = findViewById(R.id.nav_view)
val 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_clothes,
R.id.navigation_seasons,
R.id.navigation_notifications,
R.id.navigation_more
)
)
setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)
}
override fun onAttachFragment(fragment: Fragment) {
super.onAttachFragment(fragment)
if(fragment is MoreFragment){
fragment.setOnMoreItemClickedListener(this)
}
}
}
Now the error that I am getting is:
MoreFragment.kt: (16, 5): Property must be initialized or be abstract
MoreFragment.kt: (16, 5) is internal var callback: OnMoreItemClickedListener.The doc link above does suggest the same/ similar code but it does not work.
Things that I have tried
I have tried putting a lateinit var for the callback: OnMoreItemClickedListener variable there but no luck. As it never gets initialized.
I put internal var callback: OnMoreItemClickedListener? = null and referenced callback using the null safe operator ?. but was not able to get the click event.
I am fairly new to Kotlin and Fragments too so please help me figure how to do this. Thanks
In Fragment override onAttach method
private lateinit var callback: OnMoreItemClickedListener
override onAttach(context : Context){
callback = context as OnMoreItemClickedListener
}
then your callback will be init. as onAttach will be first. so no Property must be initialized or be abstract errror will occur.
also in Activity implement your interface(OnMoreItemClickedListener) and override its method. You will get callback
Your both way is looks OK
I have tried putting a lateinit var for the callback:
I put internal var callback: OnMoreItemClickedListener? =
null
I guess issue is here just debug below code. is it going inside if
if(fragment is MoreFragment){
fragment.setOnMoreItemClickedListener(this)
}

Android Navigation Component - Navigate up opens the same fragment

I'm having a problem where when executing
findNavController(R.id.main_nav_host).navigateUp()
or
findNavController(R.id.main_nav_host).popBackStack()
Instead of going back to the last fragment in the backstack, it reopens/navigates to the same/current fragment.
Can somebody point me in the right direction why this is happening?
Navigation graph:
<navigation 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/main_navigation_root"
app:startDestination="#+id/dest_main">
<fragment
android:id="#+id/dest_main"
android:name="com.example.popularmovies.ui.main.views.MainMoviesFragment"
android:label="#string/home"
tools:layout="#layout/fragment_main_movies">
<action
android:id="#+id/action_dest_main_to_dest_movie_details"
app:destination="#+id/dest_movie_details"
app:enterAnim="#anim/slide_in_right"
app:exitAnim="#anim/slide_out_left"
app:popEnterAnim="#anim/slide_in_left"
app:popExitAnim="#anim/slide_out_right" />
</fragment>
<fragment
android:id="#+id/dest_movie_details"
android:name="com.example.popularmovies.ui.details.movie.view.MovieDetailsFragment"
android:label="#string/movie_details"
tools:layout="#layout/fragment_movie_details"/>
</navigation>
MainActivity layout:
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<fragment
android:id="#+id/main_nav_host"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="#navigation/main_navigation"/>
</FrameLayout>
MainActivity:
class MainActivity : AppCompatActivity(), HasSupportFragmentInjector {
#Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>
private lateinit var appBarConfiguration: AppBarConfiguration
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
initNavUi()
}
override fun onBackPressed() {
findNavController(R.id.main_nav_host).popBackStack()
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the menu; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.menu_main, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
return when (item.itemId) {
R.id.action_settings -> true
else -> super.onOptionsItemSelected(item)
}
}
override fun onSupportNavigateUp(): Boolean {
return findNavController(R.id.main_nav_host).navigateUp()
}
override fun supportFragmentInjector(): AndroidInjector<Fragment> {
return dispatchingAndroidInjector
}
private fun initNavUi() {
val navController = Navigation.findNavController(this, R.id.main_nav_host)
appBarConfiguration = AppBarConfiguration(
setOf(R.id.dest_main)
)
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration)
}
}
Destination home fragment:
class MainMoviesFragment : Fragment(), Injectable, MovieViewHolder.MovieClickListener {
#Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
private lateinit var fragmentViewModel: MainMoviesFragmentViewModel
private lateinit var moviesRv: RecyclerView
private lateinit var moviesAdapter: MainMoviesAdapter
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_main_movies, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initViews(view)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
fragmentViewModel = ViewModelProviders.of(this,viewModelFactory).get(MainMoviesFragmentViewModel::class.java)
fragmentViewModel.start()
observe()
}
override fun onMovieClicked(position: Int) {
fragmentViewModel.onMovieClicked(position)
}
private fun initViews(view: View) {
moviesRv = view.findViewById<RecyclerView>(R.id.fragment_main_movies_rv).apply{
layoutManager = LinearLayoutManager(context)
setHasFixedSize(true)
moviesAdapter = MainMoviesAdapter(this#MainMoviesFragment)
adapter = moviesAdapter
}
}
private fun observe() {
fragmentViewModel.moviesLiveData.observe(this, Observer { moviesAdapter.submitList(it) })
fragmentViewModel.onMovieClickedLiveEvent.observe(this, Observer { handleMovieClickedEvent(it) })
}
private fun handleMovieClickedEvent(movieModel: MovieModel?){
val action = MainMoviesFragmentDirections.actionDestMainToDestMovieDetails()
findNavController().navigate(action)
}
}
Destination target fragment:
class MovieDetailsFragment : Fragment() {
private lateinit var viewModel: MovieDetailsFragmentViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_movie_details, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(this).get(MovieDetailsFragmentViewModel::class.java)
}
}
The project code on GitHub can be found here
Your onMovieClickedLiveEvent, used in your MainMoviesFragmentViewModel, is firing every time you go back to your MainMoviesFragment since MutableLiveData saves the current value. This means that popBackStack() works just fine, but then you instantly get navigated back to the detail page (note: you'll still want to remove your code in onBackPressed() since right now you can't exit the app by hitting the back button).
It seems like, particularly with the name of the variable, that you should be using the SingleLiveEvent class, instead of MutableLiveData directly, as per this blog post.
Of course, there's no particular reason to use a LiveData or go through the ViewModel at all in this case. Your MovieViewHolder could pass the MovieModel directly to onMovieClicked, which could call handleMovieClickedEvent directly. That would avoid the use of LiveData (which is designed to store state, not events) and better model what you actually want to achieve: an event listener.

How do I create a customized action bar in Kotlin fragment activity class

I am trying to customise a fragment class and an activity class from Android Studio's Bottom Navigation Activity template to produce a customized action bar but the customized action bar does not appear when I run the app on the emulator.
Here is the customized activity class with the initializeCustomActionBar() method:
class CountriesActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_countries)
val navView: BottomNavigationView = findViewById(R.id.nav_view)
navView.setItemIconTintList(null);
val 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_england, R.id.navigation_scotland, R.id.navigation_wales, R.id.navigation_nireland
)
)
setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)
}
fun initializeCustomActionBar() {
val actionBar: android.app.ActionBar? = getActionBar()
actionBar?.displayOptions = DISPLAY_SHOW_CUSTOM
actionBar?.setCustomView(R.layout.custom_action_bar)
actionBar?.setDisplayShowCustomEnabled(true)
actionBar?.show()
}
}
And here is the customized fragment class which calls the initializeCustomActionBar() method:
class EnglandFragment : Fragment() {
private lateinit var englandViewModel: EnglandViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
englandViewModel =
ViewModelProviders.of(this).get(EnglandViewModel::class.java)
val root = inflater.inflate(R.layout.fragment_england, container, false)
val textView: TextView = root.findViewById(R.id.text_england)
englandViewModel.text.observe(this, Observer {
textView.text = it
var mContext : FragmentActivity? = FragmentActivity()
mContext = this!!.getActivity() as CountriesActivity
mContext.initializeCustomActionBar()
})
return root
}
}
You just need to use an interface that the activity implements.
First define the interface in its own file, for example CountriesActivityListener.kt:
interface CountriesActivityListener {
fun initializeCustomActionBar()
}
Implement the interface in the activity:
class CountriesActivity : AppCompatActivity(), CountriesActivityListener {
//........
override fun initializeCustomActionBar() {
val actionBar: android.app.ActionBar? = actionBar
actionBar?.let {
it.displayOptions = DISPLAY_SHOW_CUSTOM
it.setCustomView(R.layout.custom_action_bar)
it.show()
}
}
}
Use the interface in the fragment:
class EnglandFragment : Fragment() {
private lateinit var englandViewModel: EnglandViewModel
// Define the reference to the interface listener:
private lateinit var contriesActivityListener: CountriesActivityListener
// Setup the interface listener in the onAttach override:
override fun onAttach(context: Context) {
contriesActivityListener = activity as CountriesActivityListener
super.onAttach(context)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
englandViewModel =
ViewModelProviders.of(this).get(EnglandViewModel::class.java)
val root = inflater.inflate(R.layout.fragment_england, container, false)
val textView: TextView = root.findViewById(R.id.text_england)
englandViewModel.text.observe(this, Observer {
textView.text = it
// Use the interface method:
contriesActivityListener.initializeCustomActionBar()
})
return root
}
}
I edited initializeCustomActionBar() to:
fun initializeCustomActionBar() {
val actionBar: ActionBar? = this.supportActionBar
actionBar?.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM)
actionBar?.setDisplayShowCustomEnabled(true)
actionBar?.setCustomView(R.layout.custom_action_bar)
}

Categories

Resources