I know this already asked a few times, but I still don't get anything after all (I'm quite new in android development).
So i set up my back button in the MainActivity.kt like this:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
val navController = this.findNavController(R.id.myNavHostFragment)
NavigationUI.setupActionBarWithNavController(this, navController)
supportActionBar?.setDisplayHomeAsUpEnabled(false)
}
// Set up the back button on action bar
override fun onSupportNavigateUp(): Boolean {
val navController = this.findNavController(R.id.myNavHostFragment)
return navController.navigateUp()
}
}
What I want is that this back button is disabled in some fragments, so I tried to override the onBackPressed() function (It is what most people on the internet told) in one of the fragments:
class DashboardFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Declare that this fragment has menu
setHasOptionsMenu(true)
// Set action bar title to "Main Dashboard"
(activity as AppCompatActivity).supportActionBar?.title = "Main Dashboard"
// Binding object for this fragment and the layout
val binding: FragmentDashboardBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_dashboard, container, false)
//Some codes here//
return binding.root
}
// This is where the error occured
override fun onBackPressed() {
super.onBackPressed()
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater?.inflate(R.menu.nav_overflow_menu, menu)
}
}
But it returns an error saying:
"OnBackPressed" overrides Nothing
Am I missing something? I'm already searching for the solutions but still confused over this.
Who knew... onSupportNavigateUp() works only on 4.0 and above. For below onNavigateUp() is called.
so
override fun onNavigateUp(): Boolean {
val navController = this.findNavController(R.id.myNavHostFragment)
return navController.navigateUp()
}
What you could do is set your nonbackbutton fragments to backstack when inserting them, which is done by
val transaction = supportFragmentManager.beginTransaction()
transaction.addToBackStack(null)
transaction.replace(R.id.frame, fragment) //or add, whatever you use
transaction.commit()
(if you want back button functionality, just skip the addtoBackStack line)
Then, also in your activity, you override the onBackPressed() and check whether the backstack is empty or full. If it is empty, which is checked by
if(supportFragmentManager.backStackEntryCount() == 0)
then you are, for example, on a fragment that supports back button, and just do super.onBackPressed().
On the other hand, if it has something, you can do the navController.navigateUp() part, and when done, pop it using supportFragmentManager.popBackStackImmediate().
I think you could make something like this work, try it, and let us know :)
Related
I'm developing a simple app, with several static screens, just. I get working passing from first fragment, but can't go back to any screen ever.
I saw some videos, like shorturl.at/jFPRY, but in any cases makes work. In my MainActivity I have the OnCreate method, where I save the instance, set the layout, and add a Listener to options on my menu itens.
In the first click, when I open the app, it goes to correct destiny. However, when I click another item it does nothing, stucked. I need to set the Fragments bidirectional, in other words, the user can view any fragment in any moment from any fragment.
My OnCreate method:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(binding.appBarMain.toolbar)
val drawerLayout: DrawerLayout = binding.drawerLayout
val navView: NavigationView = binding.navView
navView.setNavigationItemSelectedListener {
when (it.itemId) {
R.id.nav_conheca_a_UFTM -> {
supportFragmentManager.beginTransaction()
.replace(R.id.drawer_layout, ConhecaUftmFragment())
.commit()
// Remove the other operations
supportFragmentManager.beginTransaction().addToBackStack(null);
}
R.id.nav_calendario_academico -> {
supportFragmentManager.beginTransaction()
.replace(R.id.drawer_layout, GalleryFragment())
.commit()
// Remove the other operations
supportFragmentManager.beginTransaction().addToBackStack(null);
}
R.id.nav_mural_de_informacoes -> {
supportFragmentManager.beginTransaction()
.replace(R.id.drawer_layout, CalendarioFragment())
.commit()
// Remove the other operations
supportFragmentManager.beginTransaction().addToBackStack(null);
}
}
it.isChecked = true
drawerLayout.closeDrawers()
true
}
supportActionBar?.apply {
setDisplayHomeAsUpEnabled(true)
}
navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main)
val appBarConfiguration = AppBarConfiguration(navController.graph, drawerLayout)
NavigationUI.setupWithNavController(binding.appBarMain.toolbar, navController, appBarConfiguration)
NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout)
}
I have also the method onSupportNavigateUp:
override fun onSupportNavigateUp(): Boolean {
val navController = this.findNavController(id.nav_host_fragment_content_main)
return navController.navigateUp()
}
And here is an example of Fragment that I have:
class GalleryFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_calendario_academico, container, false)
/* view.setOnClickListener(){ Navigation.findNavController(view).
navigate(R.id.conhecauftmToaprimorando)
}*/
return view
}
override fun onDestroyView() {
super.onDestroyView()
}
}
I will pass my GitHub link, if anyone can help to fix this issue.
First approach would be to not use FragmentTransactions when you using NavComponent.
If you have the MenuOptions on the MainActivity layout (which I don't recommend, it should go on every fragment, so the MenuOptions are correlated to the current fragment) you can define your navigations in the nav_graph.xml and use them in the MainActivity through NavigationDirections object.
Please visit https://developer.android.com/guide/navigation/navigation-getting-started for more info.
If you want to come back from GalleryFragment to where you have defined the destination of the Popback behaviour, you should call: findNavController.popBackStack()
I have a simple app for testing. It has a "basic activity" that Android Studio brings by default. Also a Google maps fragment shown in the basic activity (the code is at the end of the post).
As you all know, the basic activity has a floating button like this:
The main problem is that I'm completely new to working with fragments, and I don't quite understand how they work (I am working on it) but what I want to do is that: by clicking on the floating button, the map is "restarted", reloading again and cleaning it of markers that the user has placed.
What would be the correct way to do this?
I have found several suggestions but I can't get them to work or I can't see how to implement them correctly. One of the ways I have tried is to detach and attach the map fragment, but this causes it to crash and shows no results. The code is the following, which I add in the floating button listener:
val frg : Fragment? = supportFragmentManager.findFragmentById(R.id.map);
val frgTransac = supportFragmentManager.beginTransaction();
if (frg != null) {
frgTransac.detach(frg);
frgTransac.attach(frg);
frgTransac.commit();
}
Another option is to use "googleMap.clear" but I don't know exactly how to get access to that object from the floating button listener.
I hope you can help me with this and, above all, understand how the fragments work and what I am doing wrong.
Main activity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(findViewById(R.id.toolbar))
findViewById<FloatingActionButton>(R.id.fab).setOnClickListener { view ->
// CLEAR MAP FROM HERE
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_main, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_settings -> true
else -> super.onOptionsItemSelected(item)
}
}
}
Map Fragment
class FMaps : Fragment() {
private val callback = OnMapReadyCallback { googleMap ->
val sydney = LatLng(-34.0, 151.0)
googleMap.addMarker(MarkerOptions().position(sydney).title("Marker in Sydney"))
googleMap.moveCamera(CameraUpdateFactory.newLatLng(sydney))
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_f_maps, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val mapFragment = childFragmentManager.findFragmentById(R.id.map) as SupportMapFragment?
mapFragment?.getMapAsync(callback)
}
}
You can use custom interface defined in the fragment and then called from the activity when the button is pressed (example how to create interface here: Communicating between a fragment and an activity - best practices). When you press the button and the interface is called, you can just refresh the map using mapFragment?.getMapAsync(callback) inside the fragment.
Example of what i'm trying to achieve https://i.stack.imgur.com/lhp10.png
I'm stuck on the part where i need to switch to createUserFragment from defaultFragment
i attached this to my button, but nothing happens when i press it, not sure what's wrong there
MainActivity
class MainActivity : AppCompatActivity() {
lateinit var appBarConfiguration: AppBarConfiguration
lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
navController = findNavController(R.id.hostFragment)
appBarConfiguration = AppBarConfiguration(navController.graph,drawer_layout)
NavigationUI.setupActionBarWithNavController(this, navController,drawer_layout)
NavigationUI.setupWithNavController(navigation_drawer,navController)
}
}
override fun onSupportNavigateUp(): Boolean {
return NavigationUI.navigateUp(navController,appBarConfiguration)
}
defaultFragment
class defaultFragment : Fragment() {
private lateinit var binding: defaultFragmentBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
binding = defaultFragmentBinding.inflate(inflater)
return (binding.root)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.createNewBlankFAB.setOnClickListener{
val transaction = (activity as MainActivity).supportFragmentManager.beginTransaction()
transaction.replace(R.id.defaultFragment, createUserFragment())
transaction.disallowAddToBackStack()
transaction.commit()
}
}
}
My questions are:
1)How can i fix my thing?
2)Will it work properly? I.e no memory leaks or some other funky stuff
3)Do i have to use another fragment for data input or maybe there's another way?
UPDATE
I stil have no idea how this works, but apparently i was replacing wrong fragment, i switched this R.id.defaultFragment in transaction.replace to R.id.hostFragment from which, i assume, all other fragments spawn, but now it just spawn on top of my existing fragment and the drawer button doesn't change its state, i guess i have to either do all of this differently or somehow pass to the drawer navigation information that current fragment was changed?
This is how we navigate within fragments in Navigation component library . We use navigate to then id of the destination Fragment which is defined in navgraph
binding.createNewBlankFAB.setOnClickListener{
Navigation.findNavController(view).navigate(R.id.createUserFragment);
}
I think you have to call "transaction.add" instead of "replace" since you are calling your fragment from an activity. Replace is called when you call a fragment from another fragment, I believe.
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
I'm working on a project which lets users in either as guests or registerd users.
There is an application scope user object with LiveData of the current user type
private val _isGuest = MutableLiveData<Boolean>()
val isGuest: LiveData<Boolean>
get() = _isGuest
There is HomeFragment which needs to show logout menu item for registered users.
The fragment has a ViewModel bound to the global property
val isGuest: LiveData<Boolean> = MainApplication.user.isGuest
and the fragment observes the data
var menu: Menu? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
...
viewModel.isGuest.observe(viewLifecycleOwner, Observer {
menu?.findItem(R.id.action_logout)?.isVisible = !it
})
}
override fun onPrepareOptionsMenu(menu: Menu) {
this.menu = menu
menu.findItem(R.id.action_logout)?.isVisible = !isGuest
super.onPrepareOptionsMenu(menu)
}
I need to toggle the menu item in the observer because registered users can logout at runtime and the current screen will need to be updated respectively.
The problem is that I also have to duplicate the code in onPrepareOptionsMenu because the observer may get notified before menu is initilized at startup.
Definitely I can move that line of code into a separate function and call it from the two points but aren't there a better solution?
Use invalidateOptionsMenu() to trigger onPrepareOptionMenu()
var menu: Menu? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
...
viewModel.isGuest.observe(viewLifecycleOwner, Observer {
activity?.invalidateOptionsMenu()//This will trigger onPrepareOptionsMenu
})
}
override fun onPrepareOptionsMenu(menu: Menu) {
this.menu = menu
menu.findItem(R.id.action_logout)?.isVisible = !isGuest
super.onPrepareOptionsMenu(menu)
}