shared element transition is not working when using toolbar home - android

I'm currently running into a strange problem, similar to here Shared element transition when using ActionBar Back button but with a fragment to fragment shared element transition. It works fine when using the back button. As soon as the toolbar home is triggered it's just blinking. So calling finishAfterTransition() is no option here.
I call the same method both times. The base for back navigation
override fun onBackPressed() {
if (coordinator != null) {
coordinator!!.back()
} else {
finish()
}
}
and the intercepted toolbar home click.
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
return when (item?.itemId) {
android.R.id.home -> {
onBackPressed()
true
}
else -> return super.onOptionsItemSelected(item)
}
}
Update: Now I noted like one out of ten tries it works correctly.
I hope anyone of you guys has a clue why this happens.
Regards coffeelord

Related

custom back button - Android Navigation

I have a form and want to get a confirmation message from the user before the user leaves it.
i want provide custom back button when user touch this button:
i try this:
val onBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
}
}
requireActivity().onBackPressedDispatcher.addCallback(this,onBackPressedCallback)
but only seems to work for providing custom back behavior to the built-in software/hardware back button and not the back arrow button
How can I do this?
Use onSupportNavigateUp, and replace yourCurrentFragmentID to your current fragment id. All these should be done in MainActivity.
navController = findNavController(R.id.nav_host_fragment)
setupActionBarWithNavController(navController!!)
override fun onSupportNavigateUp(): Boolean {
return when(navController?.currentDestination?.id) {
R.id.yourCurrentFragmentID -> {
showDialog()
true
}
else -> navController?.navigateUp()!!
}
}
Edit
If your fragment already use onOptionsItemSelected, you can handle the logic by checking itemId.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val id = item.itemId
if (id == R.id.save) {
// your save button code logic
}else if(id ==android.R.id.home){
// display confirmation message
}
return super.onOptionsItemSelected(item)
}
Use this code to set Activity on backpress.
override fun onBackPressed() {
if (isDiscardChanges) {
discardDialog()
} else {
super.onBackPressed()
}
}
If you only want to go back from Activity, then you can add within AndroidManifest.xml child class Activity.
android:parentActivityName="Parent_Activity_Name

Custom "navigate up" behavior for certain fragment using Navigation component

I want to add custom up navigation from fragment using Navigation component
In my build.gradle(app) I use androidx.appcompat:appcompat:1.1.0-alpha04 dependency to have access to onBackPressedDispatcher from activity.
So I implemented OnBackPressedCallback in my fragment and
registered callback to dispatcher:
requireActivity().onBackPressedDispatcher.addCallback(this)
I expected that pressing navigate up in toolbar will call it, but it doesn't.
Pressing device's back button calls it as expected.
Is there a similar way to add some callback in fragment on navigate up action?
UPDATE
overridden methods onOptionsItemSelected and onSupportNavigateUp doesn't invoked on pressing up button in toolbar
I found a solution
handleOnBackPressed() method invokes only on device's back button click.
I wonder, why neither onOptionsItemSelected() nor onSupportNavigateUp() methods haven't been called on pressing "up button" in toolbar. And the answer is I used
NavigationUI.setupWithNavController(toolbar, navController, appBarConfiguration)
in activity to setup toolbar with navigation component.
And that made toolbar responsive for work with navigation internally, pressing "up button" haven't invoked any of overridden methods in activity or fragments.
Instead should be used
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration)
That will make actionBar responsive for navigation, thus I can use overridden functions onOptionsItemSelected() and onSupportNavigateUp()
And best place (in my case) to add custom behavior on "up button" click for certain screen is
onSupportNavigateUp()
of hosted activity, like that
override fun onSupportNavigateUp(): Boolean {
val navController = this.findNavController(R.id.mainNavHostFragment)
return when(navController.currentDestination?.id) {
R.id.destinationOfInterest -> {
// custom behavior here
true
}
else -> navController.navigateUp()
}
}
But worth to say, that if you want implement custom behavior directly in fragment, answer of #Enzokie should work like a charm
You need to call onBackPressed() from onBackPressedDispatcher property. Assuming your Toolbar is properly setup you can use the code below in your Activity.
override fun onOptionsItemSelected(menuItem : MenuItem?) : Boolean {
if (menuItem.getItemId() == android.R.id.home) {
onBackPressedDispatcher.onBackPressed()
return true // must return true to consume it here
}
return super.onOptionsItemSelected(menuItem)
}
on Fragment override
override fun onAttach(context: Context) {
super.onAttach(context)
//enable menu
setHasOptionsMenu(true)
requireActivity()
.onBackPressedDispatcher
.addCallback(this){
//true means that the callback is enabled
this.isEnabled = true
exitDialog() //dialog to conform exit
}
}
What this does is :
Trigger a call to the currently added OnBackPressedCallback
callbacks in reverse order in which they were added. Only if the most
false from its OnBackPressedCallback#handleOnBackPressed()
will any previously added callback be called.
I am using AndroidX in my example therefore my import will look like
import androidx.appcompat.app.AppCompatActivity.
This set up also works and you won't need to override onSupportNavigateUp in your activity:
NavigationUI.setupWithNavController(toolbar, navController, appBarConfiguration)
toolbar.setNavigationOnClickListener {
if (navController.currentDestination?.id == R.id.destinationOfInterest) {
// Custom behavior here
} else {
NavigationUI.navigateUp(navController, configuration)
}
}
I prefer to set up the Toolbar since it will handle automatically the up navigation and open/close a DrawerLayout if you have one.
Add click event to toolbar back button in this way
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
// Toolbar back button pressed, do something you want
default:
return super.onOptionsItemSelected(item);
}
}
Another way
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
// Title and subtitle
toolbar.setNavigationOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// Toolbar back button pressed, do something you want
}
});
I customized (directly in Fragment) the backbress on Toolbar by using the following steps:
1. onCreate [Activity]:
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration)
2. onSupportNavigateUp [Activity]:
override fun onSupportNavigateUp(): Boolean {
onBackPressedDispatcher.onBackPressed()
return super.onSupportNavigateUp()
}
3. Customize or disable backpress [Fragment]:
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
isEnabled = false
// enable or disable the backpress
}

Replace, add, replace fragment, then the Navigation Back button not working for first fragment

I have a fragment stack, where I use replace and add together. The code (in my activity) to add or replace my fragment as below
private fun addFragment(fragment: Fragment, name: String) {
supportFragmentManager.beginTransaction().add(R.id.container, fragment)
.addToBackStack(name).commit()
}
private fun replaceFragment(fragment: Fragment, name: String) {
supportFragmentManager.beginTransaction().replace(R.id.container, fragment)
.addToBackStack(name).commit()
}
In my fragment, I do have a toolbar with a back-icon for home menu. Upon clicking, it should help pop up my fragment to the previous stack.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
toolbar_actionbar.setNavigationIcon(R.drawable.ic_arrow_back_black_24dp)
setHasOptionsMenu(true)
(activity as AppCompatActivity).setSupportActionBar(toolbar_actionbar)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return if (item.itemId == android.R.id.home) {
activity?.onBackPressed()
true
} else {
super.onOptionsItemSelected(item)
}
}
Just to be clear, I show activity onBackPressed is coded as below
override fun onBackPressed() {
if (supportFragmentManager.backStackEntryCount > 0) {
supportFragmentManager.popBackStackImmediate()
} else {
super.onBackPressed()
}
}
Now if I add fragment1, add fragment2, add fragment3, add fragment4, and then press back, back, back, back... all works fine.
Similarly, if I replace with fragment1, replace with fragment2, replace with fragment3, replace with fragment4, and then back, back, back, back, all works fine.
However if I do, replace with fragment 1, add fragment 2, and replace with fragment 3, then press back, back, back.... The third time press back no longer works. Why??
To illustrate this better, I have put my code in github as below
https://github.com/elye/issue_android_fragment_replace_add_replace
And recorded into a gif below (63 seconds gif) that show, the 4 adds works, 4 replaces works, but the mix of replace and add, will cause the toolbar back button not functioning after few backs.
Note the add fragment shows overlap number as the background is transparent. This is purposely done to easily distinguish add vs replace.
I suspect it's a google bug, but thought should share in case I miss anything important.
After investigation, it does seems like the issue are as describe below.
As each fragment would have it's own action bar set (in this stackoverflow, I simplify it to only R.id.home click for easier illustration of the issue). Each will call the below code when the fragment is added/removed.
(activity as AppCompatActivity).setSupportActionBar(toolbar_actionbar)
When we do add-add-replace (or replace-add-replace).... We have 3 fragments in backstack, but only one visible. Because the last replace, will remove the earlier 2 fragment.
When we click back button, the last replaced fragment will be pop out, and the system then restore the first 2 fragments.
During restoration of the first 2 fragments, the below code get called for both fragments in a short interval
(activity as AppCompatActivity).setSupportActionBar(toolbar_actionbar)
Some how I suspected this cause some behavior where the toolbar of the first fragment was not set fully (the Android SDK bug?).
In order to workaround the issue, when we pop the 2 fragment again, we have to force the 1st fragment to call
(activity as AppCompatActivity).setSupportActionBar(toolbar_actionbar)
With that my workaround for the above issue is to have that code above in onResume().
override fun onResume() {
super.onResume()
(activity as AppCompatActivity).setSupportActionBar(toolbar_actionbar)
}
And whenever a fragment backstack change, I will call the top fragment's onResume
supportFragmentManager.addOnBackStackChangedListener {
val currentFragment = supportFragmentManager.findFragmentById(R.id.container)
currentFragment?.onResume()
}
This now help to ensure regardless of replace-add mixture of stack, the toolbar menu will continue to work.
I have the sample workaround code in
https://github.com/elye/issue_android_fragment_replace_add_replace_workaround
This is really a workaround than a solution. I hope some better answer posted, or if this is really a Google Bug, a fix could be done to it in later SDK release.
Try with this
if (supportFragmentManager.backStackEntryCount > 1) {
supportFragmentManager.popBackStackImmediate()
} else {
super.onBackPressed()
}
Hi put your toolbar code and it's xml part into MainActivity and it's relevent layout and remove from all fargment and it's layout.
And put code like below:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
toolbar_actionbar.setNavigationIcon(R.drawable.ic_arrow_back_black_24dp)
setSupportActionBar(toolbar_actionbar)
}
And below code into MainActivity to solved it.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return if (item.itemId == android.R.id.home) {
if (supportFragmentManager.backStackEntryCount > 0) {
supportFragmentManager.popBackStackImmediate()
} else {
super.onBackPressed()
}
true
} else {
super.onOptionsItemSelected(item)
}
}
I already tested and working perfectly. hope it helps you.
if you have any doubt please comment/message into below i will explain you and solved your problem.

unsaved warning on back pressed in fragment

I want to show dialog when user press back or quit from fragment if there are some data unsaved. I am trying to override onbackpressed but unfortunately I got error lateinit property barcodeList has not been initialized. how to solve it?
here is my script on activity:
override fun onBackPressed() {
val theFragment = supportFragmentManager.fragments
for(i in 0 until theFragment.size)
{
if(theFragment[i].tag == "stocker_fragment")
{
StockerFragment().onBackPressed()
}
}
}
and this is in fragment:
fun onBackPressed() {
var check = false
// this barcodeList variable error.
for(i in 0 until barcodeList.size)
{
if(barcodeList[i].barcode.trim()=="")
{
check = true
break
}
}
if (check)
{
AlertHelper(context).onBackPressedAlert()
}
}
FYI: I have initialized barcodeList on onCreateView and everything is fine. only error in onBackPressed.
And my last question is, how do i know if user quit from fragment without pressing back button?
I think the problem is in your onBackPressed() implementation in the Activity. With the line StockerFragment().onBackPressed() you are creating a new instance of the StockerFragment and calling onBackPressed() on it, rather than calling it on the instance that is actively being used.
You should be able to adjust your Activity onBackPressed() like so:
override fun onBackPressed() {
val theFragment = supportFragmentManager.fragments
for(i in 0 until theFragment.size)
{
if(theFragment[i].tag == "stocker_fragment")
{
(theFragment[i] as StockerFragment).onBackPressed()
}
}
}
You can also make this a bit more kotliny like so:
override fun onBackPressed() {
supportFragmentManager.fragments.forEach { fragment ->
if (fragment is StockerFragment) {
fragment.onBackPressed()
}
}
}
You'll probably also want to figure out a way to decide whether the fragment's onBackPressed has determined that the Activity should stick around or not. Then, if the fragment is happy, you should call super.onBackPressed() in the Activity so that the expected back behavior (leave the Activity) happens.

Cannot custom Actionbar's navigate up method

In the activity class, I set up the actionbar as this:
MyActivity
setSupportActionBar(findViewById(R.id.toolbar_my))
supportActionBar?.apply {
setDisplayHomeAsUpEnabled(true)
setDisplayShowHomeEnabled(true)
}
Because I need to override onOptionsItemSelected(...) (in the fragment class), I didn't override onSupportNavigateUp() here.
This activity cotnains a fragment. What I want is, when click on the actionbar up button, besides pop back, also revoke a custom save() method.
So in the fragment's onOptionsItemSelected(...), write some code for the item.id == android.R.id.home case. However, I made a break point here, and found that when click on the up/home button, the code in the android.R.id.home case is never revoked. The other items' on selected methods work.
In the fragment class:
MyFragment
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when (item?.itemId) {
android.R.id.home -> {
// code here not gets called when click up/home button
mPresenter.save()
return true
}
R.id.edit-> {
// The code here is revoked when item selected.
}
else -> {
return super.onOptionsItemSelected(item)
}
}
}
I tried override another onOptionsItemSelected(...) method in the activity class, and write android.R.id.home case, still cannot invoke methods in it.
Why the code in item.id == android.R.id.home case is not called?
Read setHomeButtonEnabled
Enable or disable the "home" button in the corner of the action bar.
(Note that this is the application home/up affordance on the action
bar, not the systemwide home button.)
supportActionBar?.setHomeButtonEnabled(true)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
Then
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.getItemId()){
android.R.id.home -> {
mPresenter.save()
return true
}
R.id.edit-> {
// some code
}
}
return super.onOptionsItemSelected(item)
}

Categories

Resources