hide keyboard from remote's back button in android TV Leanback - android

I was trying to hide the keyboard when the back button is pressed but its actually going back to previous menu when the back button is pressed. What I have is a simple activity with SupportSearchFragment.
public class SearchActivity extends LeanbackActivity {
private SearchFragment mFragment;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.search);
mFragment = (SearchFragment) getSupportFragmentManager()
.findFragmentById(R.id.search_fragment);
}
#Override
public boolean onSearchRequested() {
mFragment.startRecognition();
return true;
}
}
The fragment is
public class SearchFragment extends SearchSupportFragment
implements SearchSupportFragment.SearchResultProvider {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setSearchResultProvider(this);
}
#Override
public ObjectAdapter getResultsAdapter() {
return null;
}
#Override
public boolean onQueryTextChange(String newQuery) {
loadQuery(newQuery);
return true;
}
#Override
public boolean onQueryTextSubmit(String query) {
loadQuery(query);
return true;
}
}
What I am looking for is, once the back button is pressed, it should hide the keyboard and then another press of back button, it should go to the previous menu.
What I have found is if the ArrayObjectAdapter has any items to show, then back button directly takes to the previous menu. However, if there is no item, it hides the keyboard.
Any suggestion? Thanks in Advance.

First override onBackPressed ,ref
override fun onBackPressed(){
if( view.keyboardIsVisible() )
this.hideKeyboard()
else
super.onBackPressed(); // optional depending on your needs
}
Implement your KeyIsVisible() , ref
fun View.keyboardIsVisible() = WindowInsetsCompat
.toWindowInsetsCompat(rootWindowInsets)
.isVisible(WindowInsetsCompat.Type.ime())
and Hide Keyboard by
/**
* Use only from Activities, don't use from Fragment (with getActivity) or from Dialog/DialogFragment
*/
fun Activity.hideKeyboard() {
val imm = getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
val view = currentFocus ?: View(this)
imm.hideSoftInputFromWindow(view.windowToken, InputMethodManager.HIDE_NOT_ALWAYS)
}
Ps , It was too late when I saw your language is java , I have attached refs which will help you converting .

Thanks #Anshul for your answer. I will just what I exactly needed because the back button always hides the keyboard.
in Activity
override fun onBackPressed() {
if (fragment?.isKeyBoardVisible() != true) super.onBackPressed()
}
in fragment
fun isKeyBoardVisible() = ViewCompat.getRootWindowInsets(requireView())?.isVisible(WindowInsetsCompat.Type.ime()) ?: true

Related

How to disable Back button on second Bottom Sheet Dialog?

I am creating a MainActivity. This activity has a button to open a Fragment and the Fragment has a button to open a bottom sheet dialog.
When I am on the Fragment, I can press Back button to return to MainActivity. However, when I have already opened the Bottom Dialog, I want to disable the Back button so that user can not press Back button when the Bottom Dialog is showing. So how can I do this? Thank you.
MainActivity
public class MainActivity extends SdwBaseActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void startAction(View view){
LoadCashTutorialScreen loadCashTutorialScreen = LoadCashTutorialScreen.newInstance();
getSupportFragmentManager().beginTransaction().replace(R.id.container, loadCashTutorialScreen, "LoadCashTutorialScreen").addToBackStack("LoadCashTutorialScreen").commit();
}
#Override
public void onBackPressed(){
super.onBackPressed()
//do something so that the back button is disable when the Bottom Dialog is showing
}
}
Fragment:
public class LoadCashTutorialScreen extends Fragment{
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = initView(inflater, container, R.layout.load_cash_tutorial_screen);
Button startDialog = view.findViewById(R.id.dialogButton);
startDialog.setOnClickListener(view1 -> {
MyBottomDialog dialog = new MyBottomDialog();
dialog.show(activity.getSupportFragmentManager(), "BottomDialog");
});
return view;
}
}
Dialog:
public class MyBottomDialog extends BottomSheetDialogFragment {
#Override
public void setupDialog(#NonNull Dialog dialog, int style) {
super.setupDialog(dialog, style);
View contentView = View.inflate(getContext(), R.layout.load_cash_bottom_dialog, null);
dialog.setContentView(contentView);
dialog.setCanceledOnTouchOutside(false);
dialog.setCancelable(false);
}
}
Note: I used to write some methods on onBackPressed() to prevent dialog disappears, however, the dialog always disappear when pressing Back. After debugging, I realize that onBackPressed() is not reached when the bottom dialog is showing. I do not know why.
Since your Bottom sheet is a Fragment, you have to listen it in a separate way. Use this:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// ...
setupBackPressListener()
}
private fun setupBackPressListener() {
this.view?.isFocusableInTouchMode = true
this.view?.requestFocus()
this.view?.setOnKeyListener { _, keyCode, _ ->
if (keyCode == KeyEvent.KEYCODE_BACK) {
// Do what you want to do on back press
true
} else
false
}
}
This will override default onbackpress of bottom sheet fragment, So, if you add this to bottomsheet fragment and leave it empty nothing will happen when you press back button.
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return object : BottomSheetDialog(requireContext(), theme) {
override fun onBackPressed() {
// Handle backpress in here
}
}
}

How to close a SearchView programmatically on back pressed?

I have the same question which I found here which I will re-iterate because the solution is not 100% exactly what I need:
I currently have a SearchView in the action bar of my app. When I click the search icon, the SearchView expands and the keyboard pops up as expected. Clicking the "X" in the SearchView box closes the SearchView as expected. However, when the SearchView is activated and I press the "back" button, my app is exited. This is the correct behavior, but what I am trying to do now is to capture back button press and just have it close the SearchView (not my app) when the SearchView is visible. Is there a way to invoke the SearchView OnCloseListener() programmatically on a back button press? For example, something like this:
Here is the solution:
#Override
public void onBackPressed() {
if (!searchView.isIconified()) {
searchView.setIconified(true);
} else {
super.onBackPressed();
}
}
The problem is the solution requires the user to press back not once, but twice: once* to exit the keyboard and **once to close the SearchView.
How can I close the keyboard AND the SearchView at the same time by pressing back once?
edit:
Somone had a similar problem with EditText and the solution was to subclass the EditText view.
#Override
public void onBackPressed() {
if (!searchView.isIconified()) {
searchView.setIconified(true);
searchView.onActionViewCollapsed()
} else {
super.onBackPressed();
}
}
below method is Clear your text Only
searchView.setIconified(true);
below This methods is close your search view
searchView.onActionViewCollapsed()
try
#Override
public void onBackPressed() {
View view = this.getCurrentFocus();
if (view != null) {
InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
if (!searchView.isIconified()) {
searchView.setIconified(true);
} else {
super.onBackPressed();
}
}
Add a method to close the keyboard within onBackPressed()
here is the code to hide keyboard.
private void hideKeyboard() {
InputMethodManager imm = (InputMethodManager)
getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(mInputField.getWindowToken(), 0);
}
Based on #Archana answer, the onBackPressed() should be like :
#Override
public void onBackPressed() {
hideSoftKeyboard();
if (!searchView.isIconified()) {
searchView.setIconified(true);
} else {
super.onBackPressed();
}
}
private void hideSoftKeyboard(){
View view = activity.getCurrentFocus ();
if (view != null) {
InputMethodManager imm = (InputMethodManager) activity.getSystemService (Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow (view.getWindowToken (), 0);
}
}
or you can also override the onKeyDown()
#Override
public boolean onKeyDown (int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
hideSoftKeyboard ();
if (! searchView.isIconified ()) {
searchView.setIconified (true);
}
return true;
}
return super.onKeyDown (keyCode, event);
}
private void hideSoftKeyboard() {
View view = activity.getCurrentFocus ();
if (view != null) {
InputMethodManager imm = (InputMethodManager) activity.getSystemService (Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow (view.getWindowToken (), 0);
}
}
An interesting answer!
#Override
public void onBackPressed() {
if (!searchView.isIconified()) {
searchView.setIconified(true);
searchView.setIconified(true);
} else {
super.onBackPressed();
}
}
public class MainActivity extends AppCompatActivity {
MenuItem menuItemSearch;
#Override
protected void onResume() {
if(menuItemSearch!=null)
MenuItemCompat.collapseActionView(menuItemSearch); // while activity begins, searchView is closed if remained open.
}
#Override
public boolean onPrepareOptionsMenu(Menu menu) {
menuItemSearch = menu.findItem(R.id.menuSearch);
}
}
I did it like this in the activity where the SearchView is located
#Override
public void onBackPressed() {
super.onBackPressed();
if (!searchView.isIconified()) {
searchView.setIconified(true);
}
}
Instead of hide soft keyboard manually, try clearFocus() method
mSearchView.setIconified(true);
mSearchView.clearFocus();

Using onBackPressed() in Android Fragments

I am working on a project and I need to be able to use the back button in each fragment to navigate between previous fragments, I have methods written to do so by using a back arrow in the action bar, however, I want to be able to use the same functionality on the back button pressed. I don't want to use the back stack. Is there a way to do this?
EDIT
Rather than using the back stack I want to be able to call the go back to previous method below when the user clicks the back button. I need to used the gobackpressed method within fragments. Is this possible? I hope this is clear and concise. Apologies for any confusion caused above.
Go Back to Previous
public void gobackToPreviousFragment(String preFragmentTag, Fragment preFragment){
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.setCustomAnimations(R.animator.close_slide_in,R.animator.close_slide_out);
ft.show(preFragment);
//**BY REMOVING FRAGMENT, WHEN USER TRIES TO REVISIT, FRAGMENT IS BLACK**
ft.remove(fm.findFragmentByTag(Misc.currentContentFragmentTag));
ft.addToBackStack(null);
ft.commit();
Misc.currentContentFragmentTag = preFragmentTag;
createBar(preFragment);
}
Go Forward
public void gotoNextFragment(String nextTag, Fragment nextFragment){
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.setCustomAnimations(R.animator.enter_slide_in, R.animator.enter_slide_out);
boolean newlyCreated = false;
if(nextFragment == null){
nextFragment = Fragment.instantiate(this, nextTag);
newlyCreated = true;
}
//hide current fragment
ft.hide(fm.findFragmentByTag(Misc.currentContentFragmentTag));
if(newlyCreated){
ft.add(R.id.content_frame, nextFragment, nextTag);
}
else{
ft.show(nextFragment);
}
ft.addToBackStack(null);
ft.commit();
Misc.currentContentFragmentTag = nextTag;
createBar(nextFragment);
}
These are how I navigate back and forth, and I'd like to be able to implement the go back method on the onBackPressed(). Does this make sense?
I didn't find any good answer about this problem, so this is my solution.
If you want to get backPress in each fragment do the following.
create interface OnBackPressedListener
public interface OnBackPressedListener {
void onBackPressed();
}
That each fragment that wants to be informed of backPress implements this interface.
In parent activity , you can override onBackPressed()
#Override
public void onBackPressed() {
List<Fragment> fragmentList = getSupportFragmentManager().getFragments();
if (fragmentList != null) {
//TODO: Perform your logic to pass back press here
for(Fragment fragment : fragmentList){
if(fragment instanceof OnBackPressedListener){
((OnBackPressedListener)fragment).onBackPressed();
}
}
}
}
Why don't you want to use the back stack? If there is an underlying problem or confusion maybe we can clear it up for you.
If you want to stick with your requirement just override your Activity's onBackPressed() method and call whatever method you're calling when the back arrow in your ActionBar gets clicked.
EDIT: How to solve the "black screen" fragment back stack problem:
You can get around that issue by adding a backstack listener to the fragment manager. That listener checks if the fragment back stack is empty and finishes the Activity accordingly:
You can set that listener in your Activity's onCreate method:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FragmentManager fm = getFragmentManager();
fm.addOnBackStackChangedListener(new OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
if(getFragmentManager().getBackStackEntryCount() == 0) finish();
}
});
}
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
Log.w("a","")
}
})
In the fragment where you would like to handle your back button you should attach stuff to your view in the oncreateview
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.second_fragment, container, false);
v.setOnKeyListener(pressed);
return v;
}
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
// TODO Auto-generated method stub
if( keyCode == KeyEvent.KEYCODE_BACK ){
// back to previous fragment by tag
myfragmentclass fragment = (myfragmentclass) getActivity().getSupportFragmentManager().findFragmentByTag(TAG);
if(fragment != null){
(getActivity().getSupportFragmentManager().beginTransaction()).replace(R.id.cf_g1_mainframe_fm, fragment).commit();
}
return true;
}
return false;
}
};
This works for me :D
#Override
public void onResume() {
super.onResume();
if(getView() == null){
return;
}
getView().setFocusableInTouchMode(true);
getView().requestFocus();
getView().setOnKeyListener(new View.OnKeyListener() {
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK){
// handle back button's click listener
return true;
}
return false;
}
});}
You can try to override onCreateAnimation, parameter and catch enter==false. This will fire before every back press.
#Override
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
if(!enter){
//leaving fragment
Log.d(TAG,"leaving fragment");
}
return super.onCreateAnimation(transit, enter, nextAnim);
}
For a Fragment you can simply add
getActivity().onBackPressed();
to your code
I found a new way to do it without interfaces. You only need to add the below code to the Fragment’s onCreate() method:
//overriding the fragment's oncreate
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//calling onBackPressedDispatcher and adding call back
requireActivity().onBackPressedDispatcher.addCallback(this) {
//do stuff here
}
}
Use this:
#Override
public void onBackPressed() {
int fragments = getFragmentManager().getBackStackEntryCount();
if (fragments == 1) {
finish();
}
super.onBackPressed();
}

actionbar up navigation with fragments

I have a tabbed Actionbar/viewpager layout with three tabs say A, B, and C. In tab C tab(fragment),I am adding another fragment say fragment D. with
DFragment f= new DFragment();
ft.add(android.R.id.content, f, "");
ft.remove(CFragment.this);
ft.addToBackStack(null);
ft.commit();
I modify actionbar in DFragment's onResume to add up button:
ActionBar ab = getActivity().getActionBar();
ab.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
ab.setDisplayHomeAsUpEnabled(true);
ab.setDisplayShowHomeEnabled(true);
Now in DFragment, when I press hardware(phone) Back button, I return to the original Tabbed(ABC) layout with CFragment selected. How can I achieve this functionality with actionbar up button?
Implement OnBackStackChangedListener and add this code to your Fragment Activity.
#Override
public void onCreate(Bundle savedInstanceState) {
//Listen for changes in the back stack
getSupportFragmentManager().addOnBackStackChangedListener(this);
//Handle when activity is recreated like on orientation Change
shouldDisplayHomeUp();
}
#Override
public void onBackStackChanged() {
shouldDisplayHomeUp();
}
public void shouldDisplayHomeUp(){
//Enable Up button only if there are entries in the back stack
boolean canGoBack = getSupportFragmentManager().getBackStackEntryCount()>0;
getSupportActionBar().setDisplayHomeAsUpEnabled(canGoBack);
}
#Override
public boolean onSupportNavigateUp() {
//This method is called when the up button is pressed. Just the pop back stack.
getSupportFragmentManager().popBackStack();
return true;
}
I got it. just override onOptionsItemSelected in hosting activity and popup the backstack, e.g.
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home: {
FragmentManager fm = getSupportFragmentManager();
if (fm.getBackStackEntryCount() > 0) {
fm.popBackStack();
return true;
}
break;
}
}
return super.onOptionsItemSelected(item);
}
Call getActionBar().setDisplayHomeAsUpEnabled(boolean); and getActionBar().setHomeButtonEnabled(boolean); in onBackStackChanged() as explained in an answer below.
If you have one parent activity and want this up button to work as a back button, you can use this code:
add this to the onCreate in your main activity class
getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
int stackHeight = getSupportFragmentManager().getBackStackEntryCount();
if (stackHeight > 0) { // if we have something on the stack (doesn't include the current shown fragment)
getSupportActionBar().setHomeButtonEnabled(true);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
} else {
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
getSupportActionBar().setHomeButtonEnabled(false);
}
}
});
and then add onOptionsItemSelected like so:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
getSupportFragmentManager().popBackStack();
return true;
....
}
I generally use this all the time and seems pretty legit
you can go back with up button like back button ;
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
super.onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
I used a combination of Roger Garzon Nieto's and sohailaziz's answers. My app has a single MainActivity, and fragments A, B, C that are loaded into it. My "home" fragment (A) implements OnBackStackChangedListener, and checks the size of the backStack; if it's less than one, then it hides the UP button. Fragments B and C always load the back button (in my design, B is launched from A, and C is launched from B). The MainActivity itself just pops the backstack on UP button tap, and has methods to show/hide the button, which the fragments call:
MainActivity:
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
// Respond to the action bar's Up/Home button
case android.R.id.home:
getSupportFragmentManager().popBackStack();
return true;
}
return super.onOptionsItemSelected(item);
}
public void showUpButton() { getSupportActionBar().setDisplayHomeAsUpEnabled(true); }
public void hideUpButton() { getSupportActionBar().setDisplayHomeAsUpEnabled(false); }
fragmentA (implements FragmentManager.OnBackStackChangedListener):
public void onCreate(Bundle savedinstanceSate) {
// listen to backstack changes
getActivity().getSupportFragmentManager().addOnBackStackChangedListener(this);
// other fragment init stuff
...
}
public void onBackStackChanged() {
// enable Up button only if there are entries on the backstack
if(getActivity().getSupportFragmentManager().getBackStackEntryCount() < 1) {
((MainActivity)getActivity()).hideUpButton();
}
}
fragmentB, fragmentC:
public void onCreate(Bundle savedinstanceSate) {
// show the UP button
((MainActivity)getActivity()).showUpButton();
// other fragment init stuff
...
}
I know this question is old, but may be someone (like me) also needs it.
If your Activity extends AppCompatActivity, you can use a simpler (two-step) solution:
1 - Whenever you add a non-home fragment just show the up button, right after commiting the fragment transaction. Like this:
// ... add a fragment
// Commit the transaction
transaction.commit();
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
2 - Then when UP button is pressed, you hide it.
#Override
public boolean onSupportNavigateUp() {
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
return true;
}
That's it.
This worked for me. Override onSupportNavigateUp and onBackPressed, for example (code in Kotlin);
override fun onBackPressed() {
val count = supportFragmentManager.backStackEntryCount
if (count == 0) {
super.onBackPressed()
} else {
supportFragmentManager.popBackStack()
}
}
override fun onSupportNavigateUp(): Boolean {
super.onSupportNavigateUp()
onBackPressed()
return true
}
Now in the fragment, if you display the up arrow
activity.supportActionBar?.setDisplayHomeAsUpEnabled(true)
Clicking on it takes you back the previous activity.
Kotlin:
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
...
supportFragmentManager.addOnBackStackChangedListener { setupHomeAsUp() }
setupHomeAsUp()
}
private fun setupHomeAsUp() {
val shouldShow = 0 < supportFragmentManager.backStackEntryCount
supportActionBar?.setDisplayHomeAsUpEnabled(shouldShow)
}
override fun onSupportNavigateUp(): Boolean =
supportFragmentManager.popBackStack().run { true }
...
}
This is a very good and reliable solution: http://vinsol.com/blog/2014/10/01/handling-back-button-press-inside-fragments/
The guy has made an abstract fragment that handles the backPress behaviour and is switching between the active fragments using the strategy pattern.
For some of you there maybe a little drawback in the abstract class...
Shortly, the solution from the link goes like this:
// Abstract Fragment handling the back presses
public abstract class BackHandledFragment extends Fragment {
protected BackHandlerInterface backHandlerInterface;
public abstract String getTagText();
public abstract boolean onBackPressed();
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(!(getActivity() instanceof BackHandlerInterface)) {
throw new ClassCastException("Hosting activity must implement BackHandlerInterface");
} else {
backHandlerInterface = (BackHandlerInterface) getActivity();
}
}
#Override
public void onStart() {
super.onStart();
// Mark this fragment as the selected Fragment.
backHandlerInterface.setSelectedFragment(this);
}
public interface BackHandlerInterface {
public void setSelectedFragment(BackHandledFragment backHandledFragment);
}
}
And usage in the activity:
// BASIC ACTIVITY CODE THAT LETS ITS FRAGMENT UTILIZE onBackPress EVENTS
// IN AN ADAPTIVE AND ORGANIZED PATTERN USING BackHandledFragment
public class TheActivity extends FragmentActivity implements BackHandlerInterface {
private BackHandledFragment selectedFragment;
#Override
public void onBackPressed() {
if(selectedFragment == null || !selectedFragment.onBackPressed()) {
// Selected fragment did not consume the back press event.
super.onBackPressed();
}
}
#Override
public void setSelectedFragment(BackHandledFragment selectedFragment) {
this.selectedFragment = selectedFragment;
}
}
If you want to go back to your previous activity if this activity has an empty stack of fragments:
This could be useful if you have a MainActivity and you are navigating to e.g. a SettingsActivity with nested prefernceScreens. NavigateUp will pop fragments until you can finish the SettingsActivity to go back to parentActivity/root.
/**
* On actionbar up-button popping fragments from stack until it is empty.
* #return true if fragment popped or returned to parent activity successfully.
*/
#Override
public boolean onSupportNavigateUp() {
//Pop back stack if the up button is pressed.
boolean canGoBack = getSupportFragmentManager().getBackStackEntryCount()>0;
if (canGoBack) {
getSupportFragmentManager().popBackStack();
} else {
finish();
return super.onSupportNavigateUp();
}
return true;
}
Note: setDisplayHomeAsUpEnabled(true); in fragment activities onCreate()

Android Fragment handle back button press [duplicate]

This question already has answers here:
How to implement onBackPressed() in Fragments?
(58 answers)
Closed 6 years ago.
I have some fragments in my activity
[1], [2], [3], [4], [5], [6]
And on Back Button Press I must to return from [2] to [1] if current active fragment is [2], or do nothing otherwise.
What is the best practise to do that?
EDIT: Application must not return to [2] from [3]...[6]
When you are transitioning between Fragments, call addToBackStack() as part of your FragmentTransaction:
FragmentTransaction tx = fragmentManager.beginTransation();
tx.replace( R.id.fragment, new MyFragment() ).addToBackStack( "tag" ).commit();
If you require more detailed control (i.e. when some Fragments are visible, you want to suppress the back key) you can set an OnKeyListener on the parent view of your fragment:
//You need to add the following line for this solution to work; thanks skayred
fragment.getView().setFocusableInTouchMode(true);
fragment.getView().requestFocus();
fragment.getView().setOnKeyListener( new OnKeyListener()
{
#Override
public boolean onKey( View v, int keyCode, KeyEvent event )
{
if( keyCode == KeyEvent.KEYCODE_BACK )
{
return true;
}
return false;
}
} );
I'd rather do something like this:
private final static String TAG_FRAGMENT = "TAG_FRAGMENT";
private void showFragment() {
final Myfragment fragment = new MyFragment();
final FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragment, fragment, TAG_FRAGMENT);
transaction.addToBackStack(null);
transaction.commit();
}
#Override
public void onBackPressed() {
final Myfragment fragment = (Myfragment) getSupportFragmentManager().findFragmentByTag(TAG_FRAGMENT);
if (fragment.allowBackPressed()) { // and then you define a method allowBackPressed with the logic to allow back pressed or not
super.onBackPressed();
}
}
if you overide the onKey method for the fragment view you're gonna need :
view.setFocusableInTouchMode(true);
view.requestFocus();
view.setOnKeyListener(new View.OnKeyListener() {
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
Log.i(tag, "keyCode: " + keyCode);
if( keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
Log.i(tag, "onKey Back listener is working!!!");
getFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
return true;
}
return false;
}
});
Use addToBackStack method when replacing one fragment by another:
getFragmentManager().beginTransaction().replace(R.id.content_frame, fragment).addToBackStack("my_fragment").commit();
Then in your activity, use the following code to go back from a fragment to another (the previous one).
#Override
public void onBackPressed() {
if (getParentFragmentManager().getBackStackEntryCount() > 0) {
getParentFragmentManager().popBackStack();
} else {
super.onBackPressed();
}
}
If you want to handle hardware Back key event than you have to do following code in your onActivityCreated() method of Fragment.
You also need to check Action_Down or Action_UP event. If you will not check then onKey() Method will call 2 times.
Also, If your rootview(getView()) will not contain focus then it will not work. If you have clicked on any control then again you need to give focus to rootview using getView().requestFocus(); After this only onKeydown() will call.
getView().setFocusableInTouchMode(true);
getView().requestFocus();
getView().setOnKeyListener(new OnKeyListener() {
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
Toast.makeText(getActivity(), "Back Pressed", Toast.LENGTH_SHORT).show();
return true;
}
}
return false;
}
});
Working very well for me.
Create interfaces:
BackButtonHandlerInterface
public interface BackButtonHandlerInterface {
void addBackClickListener (OnBackClickListener onBackClickListener);
void removeBackClickListener (OnBackClickListener onBackClickListener);
}
OnBackClickListener
public interface OnBackClickListener {
boolean onBackClick();
}
In Activity:
public class MainActivity extends AppCompatActivity implements BackButtonHandlerInterface {
private ArrayList<WeakReference<OnBackClickListener>> backClickListenersList = new ArrayList<>();
#Override
public void addBackClickListener(OnBackClickListener onBackClickListener) {
backClickListenersList.add(new WeakReference<>(onBackClickListener));
}
#Override
public void removeBackClickListener(OnBackClickListener onBackClickListener) {
for (Iterator<WeakReference<OnBackClickListener>> iterator = backClickListenersList.iterator();
iterator.hasNext();){
WeakReference<OnBackClickListener> weakRef = iterator.next();
if (weakRef.get() == onBackClickListener){
iterator.remove();
}
}
}
#Override
public void onBackPressed() {
if(!fragmentsBackKeyIntercept()){
super.onBackPressed();
}
}
private boolean fragmentsBackKeyIntercept() {
boolean isIntercept = false;
for (WeakReference<OnBackClickListener> weakRef : backClickListenersList) {
OnBackClickListener onBackClickListener = weakRef.get();
if (onBackClickListener != null) {
boolean isFragmIntercept = onBackClickListener.onBackClick();
if (!isIntercept) isIntercept = isFragmIntercept;
}
}
return isIntercept;
}
}
In Fragment:
public class MyFragment extends Fragment implements OnBackClickListener{
private BackButtonHandlerInterface backButtonHandler;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
backButtonHandler = (BackButtonHandlerInterface) activity;
backButtonHandler.addBackClickListener(this);
}
#Override
public void onDetach() {
super.onDetach();
backButtonHandler.removeBackClickListener(this);
backButtonHandler = null;
}
#Override
public boolean onBackClick() {
//This method handle onBackPressed()! return true or false
return false;
}
}
Update
Provide custom back navigation
class MyFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// This callback will only be called when MyFragment is at least Started.
val callback = requireActivity().onBackPressedDispatcher.addCallback(this) {
// Handle the back button event
}
// The callback can be enabled or disabled here or in the lambda
}
}
The most ideal way of doing this is found here:
Fragment: which callback invoked when press back button & customize it
public class MyActivity extends Activity
{
//...
//Defined in Activity class, so override
#Override
public void onBackPressed()
{
super.onBackPressed();
myFragment.onBackPressed();
}
}
public class MyFragment extends Fragment
{
//Your created method
public static void onBackPressed()
{
//Pop Fragments off backstack and do your other checks
}
}
#Override
public void onResume() {
super.onResume();
getView().setFocusableInTouchMode(true);
getView().requestFocus();
getView().setOnKeyListener(new View.OnKeyListener() {
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK){
if (mDrawerLayout.isDrawerOpen(GravityCompat.START)){
mDrawerLayout.closeDrawer(GravityCompat.START);
}
return true;
}
return false;
}
});
}
After looking at all solutions, I realised there is a much simpler solution.
In your activity's onBackPressed() that is hosting all your fragments, find the fragment that you want to prevent back press. Then if found, just return. Then popBackStack will never happen for this fragment.
#Override
public void onBackPressed() {
Fragment1 fragment1 = (Fragment1) getFragmentManager().findFragmentByTag(“Fragment1”);
if (fragment1 != null)
return;
if (getFragmentManager().getBackStackEntryCount() > 0){
getFragmentManager().popBackStack();
}
}
We created tiny library for handling back press across multiple fragments and/or in Activity. Usage is as simple as adding dependency in your gradle file:
compile 'net.skoumal.fragmentback:fragment-back:0.1.0'
Let your fragment implement BackFragment interface:
public abstract class MyFragment extends Fragment implements BackFragment {
public boolean onBackPressed() {
// -- your code --
// return true if you want to consume back-pressed event
return false;
}
public int getBackPriority() {
return NORMAL_BACK_PRIORITY;
}
}
Notify your fragments about back presses:
public class MainActivity extends AppCompatActivity {
#Override
public void onBackPressed() {
// first ask your fragments to handle back-pressed event
if(!BackFragmentHelper.fireOnBackPressedEvent(this)) {
// lets do the default back action if fragments don't consume it
super.onBackPressed();
}
}
}
For more details and other use-cases visit GitHub page:
https://github.com/skoumalcz/fragment-back
Or you could use getSupportFragmentManager().getBackStackEntryCount() to check what to do:
#Override
public void onBackPressed() {
logger.d("###### back stack entry count : " + getSupportFragmentManager().getBackStackEntryCount());
if (getSupportFragmentManager().getBackStackEntryCount() != 0) {
// only show dialog while there's back stack entry
dialog.show(getSupportFragmentManager(), "ConfirmDialogFragment");
} else if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
// or just go back to main activity
super.onBackPressed();
}
}
If you manage the flow of adding to back stack every transaction, then you can do something like this in order to show the previous fragment when the user presses back button (you could map the home button too).
#Override
public void onBackPressed() {
if (getFragmentManager().getBackStackEntryCount() > 0)
getFragmentManager().popBackStack();
else
super.onBackPressed();
}
For Those Who Use Static Fragment
In a case if you have a static fragment then It would be preferable.
Make an instance object of your fragment
private static MyFragment instance=null;
in onCreate() of MyFragment initialize that instance
instance=this;
also make a function to get Instance
public static MyFragment getInstance(){
return instance;
}
also make functions
public boolean allowBackPressed(){
if(allowBack==true){
return true;
}
return false;
}
//allowBack is a boolean variable that will be set to true at the action
//where you want that your backButton should not close activity. In my case I open
//Navigation Drawer then I set it to true. so when I press backbutton my
//drawer should be get closed
public void performSomeAction(){
//.. Your code
///Here I have closed my drawer
}
In Your Activity You can do
#Override
public void onBackPressed() {
if (MyFragment.getInstance().allowBackPressed()) {
MyFragment.getInstance().performSomeAction();
}
else{
super.onBackPressed();
}
}
Working Code:
package com.example.keralapolice;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentManager.OnBackStackChangedListener;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
public class ChiefFragment extends Fragment {
View view;
// public OnBackPressedListener onBackPressedListener;
#Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle args) {
view = inflater.inflate(R.layout.activity_chief, container, false);
getActivity().getActionBar().hide();
view.setFocusableInTouchMode(true);
view.requestFocus();
view.setOnKeyListener(new View.OnKeyListener() {
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
Log.i(getTag(), "keyCode: " + keyCode);
if (keyCode == KeyEvent.KEYCODE_BACK) {
getActivity().getActionBar().show();
Log.i(getTag(), "onKey Back listener is working!!!");
getFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
// String cameback="CameBack";
Intent i = new Intent(getActivity(), home.class);
// i.putExtra("Comingback", cameback);
startActivity(i);
return true;
} else {
return false;
}
}
});
return view;
}
}
I think the easiest way is to create an interface, and in the Activity check if the fragment is of the interface type, and if so, call its method to handle the pop. Here's the interface to implement in the fragment.
public interface BackPressedFragment {
// Note for this to work, name AND tag must be set anytime the fragment is added to back stack, e.g.
// getActivity().getSupportFragmentManager().beginTransaction()
// .replace(R.id.fragment_container, MyFragment.newInstance(), "MY_FRAG_TAG")
// .addToBackStack("MY_FRAG_TAG")
// .commit();
// This is really an override. Should call popBackStack itself.
void onPopBackStack();
}
Here's how to implement it.
public class MyFragment extends Fragment implements BackPressedFragment
#Override
public void onPopBackStack() {
/* Your code goes here, do anything you want. */
getActivity().getSupportFragmentManager().popBackStack();
}
And in your Activity, when you handle the pop (likely in both onBackPressed and onOptionsItemSelected), pop the backstack using this method:
public void popBackStack() {
FragmentManager fm = getSupportFragmentManager();
// Call current fragment's onPopBackStack if it has one.
String fragmentTag = fm.getBackStackEntryAt(fm.getBackStackEntryCount() - 1).getName();
Fragment currentFragment = getSupportFragmentManager().findFragmentByTag(fragmentTag);
if (currentFragment instanceof BackPressedFragment)
((BackPressedFragment)currentFragment).onPopBackStack();
else
fm.popBackStack();
}
I'm working with SlidingMenu and Fragment, present my case here and hope helps somebody.
Logic when [Back] key pressed :
When SlidingMenu shows, close it, no more things to do.
Or when 2nd(or more) Fragment showing, slide back to previous Fragment, and no more things to do.
SlidingMenu not shows, current Fragment is #0, do the original [Back] key does.
public class Main extends SherlockFragmentActivity
{
private SlidingMenu menu=null;
Constants.VP=new ViewPager(this);
//Some stuff...
#Override
public void onBackPressed()
{
if(menu.isMenuShowing())
{
menu.showContent(true); //Close SlidingMenu when menu showing
return;
}
else
{
int page=Constants.VP.getCurrentItem();
if(page>0)
{
Constants.VP.setCurrentItem(page-1, true); //Show previous fragment until Fragment#0
return;
}
else
{super.onBackPressed();} //If SlidingMenu is not showing and current Fragment is #0, do the original [Back] key does. In my case is exit from APP
}
}
}
This is a very good and reliable solution: http://vinsol.com/blog/2014/10/01/handling-back-button-press-inside-fragments/
The guy has made an abstract fragment that handles the backPress behaviour and is switching between the active fragments using the strategy pattern.
For some of you there maybe a little drawback in the abstract class...
Shortly, the solution from the link goes like this:
// Abstract Fragment handling the back presses
public abstract class BackHandledFragment extends Fragment {
protected BackHandlerInterface backHandlerInterface;
public abstract String getTagText();
public abstract boolean onBackPressed();
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(!(getActivity() instanceof BackHandlerInterface)) {
throw new ClassCastException("Hosting activity must implement BackHandlerInterface");
} else {
backHandlerInterface = (BackHandlerInterface) getActivity();
}
}
#Override
public void onStart() {
super.onStart();
// Mark this fragment as the selected Fragment.
backHandlerInterface.setSelectedFragment(this);
}
public interface BackHandlerInterface {
public void setSelectedFragment(BackHandledFragment backHandledFragment);
}
}
And usage in the activity:
// BASIC ACTIVITY CODE THAT LETS ITS FRAGMENT UTILIZE onBackPress EVENTS
// IN AN ADAPTIVE AND ORGANIZED PATTERN USING BackHandledFragment
public class TheActivity extends FragmentActivity implements BackHandlerInterface {
private BackHandledFragment selectedFragment;
#Override
public void onBackPressed() {
if(selectedFragment == null || !selectedFragment.onBackPressed()) {
// Selected fragment did not consume the back press event.
super.onBackPressed();
}
}
#Override
public void setSelectedFragment(BackHandledFragment selectedFragment) {
this.selectedFragment = selectedFragment;
}
}
rootView.setFocusableInTouchMode(true);
rootView.requestFocus();
rootView.setOnKeyListener(new View.OnKeyListener() {
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
Fragment NameofFragment = new NameofFragment;
FragmentTransaction transaction=getFragmentManager().beginTransaction();
transaction.replace(R.id.frame_container,NameofFragment);
transaction.commit();
return true;
}
return false;
}
});
return rootView;
Add addToBackStack() to fragment transaction and then use below code for Implementing Back Navigation for Fragments
getSupportFragmentManager().addOnBackStackChangedListener(
new FragmentManager.OnBackStackChangedListener() {
public void onBackStackChanged() {
// Update your UI here.
}
});
if you are using FragmentActivity. then do like this
first call This inside your Fragment.
public void callParentMethod(){
getActivity().onBackPressed();
}
and then Call onBackPressed method in side your parent FragmentActivity class.
#Override
public void onBackPressed() {
//super.onBackPressed();
//create a dialog to ask yes no question whether or not the user wants to exit
...
}
You can use from getActionBar().setDisplayHomeAsUpEnabled() :
#Override
public void onBackStackChanged() {
int backStackEntryCount = getFragmentManager().getBackStackEntryCount();
if(backStackEntryCount > 0){
getActionBar().setDisplayHomeAsUpEnabled(true);
}else{
getActionBar().setDisplayHomeAsUpEnabled(false);
}
}
Add this code in your Activity
#Override
public void onBackPressed() {
if (getFragmentManager().getBackStackEntryCount() == 0) {
super.onBackPressed();
} else {
getFragmentManager().popBackStack();
}
}
And add this line in your Fragment before commit()
ft.addToBackStack("Any name");
in fragment class put this code for back event:
rootView.setFocusableInTouchMode(true);
rootView.requestFocus();
rootView.setOnKeyListener( new OnKeyListener()
{
#Override
public boolean onKey( View v, int keyCode, KeyEvent event )
{
if( keyCode == KeyEvent.KEYCODE_BACK )
{
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.frame_container, new Book_service_provider()).commit();
return true;
}
return false;
}
} );
Checking the backstack works perfectly
#Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
if (keyCode == KeyEvent.KEYCODE_BACK)
{
if (getFragmentManager().getBackStackEntryCount() == 1)
{
// DO something here since there is only one fragment left
// Popping a dialog asking to quit the application
return false;
}
}
return super.onKeyDown(keyCode, event);
}
In your oncreateView() method you need to write this code and in KEYCODE_BACk condition you can write whatever the functionality you want
View v = inflater.inflate(R.layout.xyz, container, false);
//Back pressed Logic for fragment
v.setFocusableInTouchMode(true);
v.requestFocus();
v.setOnKeyListener(new View.OnKeyListener() {
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
getActivity().finish();
Intent intent = new Intent(getActivity(), MainActivity.class);
startActivity(intent);
return true;
}
}
return false;
}
});

Categories

Resources