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
}
}
}
Related
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
In my application i am using some fragments, in a viewpager.
I want to show a dialog in a fragment like this:
final Dialog dialog = new Dialog(activity, R.style.DialogTheme);
dialog.show();
The activity is set in the onCreateView like this:
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
activity = getActivity();
}
This is working perfectly, but in some cases like if the app goes in background, and the user comes back to the app, i got an error "Fragment not attached to activity" in the line "dialog.show()".
So to prevent this error i use this:
if(!activity.isFinishing())
dialog.show();
else
Toast.makeText(activity, "Error", Toast.LENGTH_SHORT).show();
I think this is definitly not the best way...
Is there maybe a solution like reloading the app if the activity isFinishing or even a better solution?
override onAttach method
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (activity instanceof YourActiivty) {
//here is your code
} else {
}
}
public class MyFragment extends Fragment
#Override
public void setMenuVisibility(final boolean visible) {
super.setMenuVisibility(visible);
if (visible) {
...
}
}
Try to use DialogFragment rather than showing dialog by Dialog class, it will give you more stable view ,just extend your class by "DialogFragment"
public class DemoDialogFragment extends DialogFragment{
public DemoDialogFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View rootView = inflater.inflate(R.layout.fragment_demo_dialog, container, false);
-----------
return rootView;
}
and make function in you activity for calling it
public void showDialogFrag(DialogFragment dialogFragment, String tag) {
dialogFragment.show(getSupportFragmentManager(), tag);
}
Then call this function by this from any fragment
((MainActivity) getActivity()).showDialogFrag(new DemoDialogFragment(), Constant.FragmentTags.DemoDialogFragment);
I'm implementing an app in Xamarin Android that contains a page that once you click on an actionbar button, you get a new dialog that contains a toolbar.
The simplified code is something like:
public class MyDialogFragment : MvxDialogFragment<MyDialogViewModel>
{
public MvxDialogFragment()
{
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
this.EnsureBindingContextIsSet(inflater);
var view = this.BindingInflate(Resource.Layout.dialog_view, null);
SetupToolbar(view);
return view;
}
private void SetupToolbar(View view)
{
Dialog.RequestWindowFeature((int)WindowFeatures.NoTitle);
Android.Support.V7.Widget.Toolbar toolbar = view.FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.dialog_toolbar);
((AppCompatActivity)Activity).SetSupportActionBar(toolbar);
Android.Support.V7.App.ActionBar actionBar = ((AppCompatActivity)Activity).SupportActionBar;
actionBar.Title = null;
HasOptionsMenu = true;
}
public override void OnCreateOptionsMenu(IMenu menu, MenuInflater inflater)
{
menu.Clear();
inflater.Inflate(Resource.Menu.menu_dialog, menu);
base.OnCreateOptionsMenu(menu, inflater);
}
public override bool OnOptionsItemSelected(IMenuItem item)
{
switch (item.ItemId)
{
(...)
}
}
public override void OnPrepareOptionsMenu(IMenu menu)
{
(...)
base.OnPrepareOptionsMenu(menu);
}
The main workflow it works just fine, but there is a side effect when I rotate the screen while displaying the dialog.
If I do so, the actionbar buttons of the parent fragment(the one hosting the dialog), disappear till I recreate the view (ie: rotation).
Any ideas about how to solve this? I have tried several things like invalidate the parent menu after the dialog is closed, but it didn't work.
After some time investigating the problem, I don't think there is a way to make work 2 actionbars at the same time in a "native way".
My solution, in the end, was to manually handle the click events over the second actionbar.
public class MyDialogFragment : MvxDialogFragment<MyDialogViewModel>
{
public MvxDialogFragment()
{
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
this.EnsureBindingContextIsSet(inflater);
var view = this.BindingInflate(Resource.Layout.dialog_view, null);
SetupToolbar(view);
return view;
}
private void SetupToolbar(View view)
{
Dialog.RequestWindowFeature((int)WindowFeatures.NoTitle);
toolbar = view.FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.toolbar);
toolbar.Title = null;
toolbar.InflateMenu(Resource.Menu.menu);
toolbar.MenuItemClick += HandlerMenuItemClick;
HasOptionsMenu = true;
}
public override bool HandlerMenuItemClick(object sender, Android.Support.V7.Widget.Toolbar.MenuItemClickEventArgs e)
{
switch (e.item.ItemId)
{
(...)
}
}
}
Hope it can helps anybody
I have setup a very simple test project https://github.com/ArtworkAD/ViewPagerDialogTest to evaluate following situation: the main activity has a view pager which hosts a single fragment using support fragment manager:
public class MainActivity extends AppCompatActivity {
// ...
#Override
protected void onCreate(Bundle savedInstanceState) {
// ...
viewPager.setAdapter(new ViewPagerAdapter(getSupportFragmentManager()));
// ...
tabLayout.setupWithViewPager(viewPager);
}
#Override
protected void onResume() {
super.onResume();
MainActivity.CustomDialog dialog = (MainActivity.CustomDialog) getSupportFragmentManager().findFragmentByTag(MainActivity.CustomDialog.TAG);
if (dialog == null) {
new MainActivity.CustomDialog().show(getSupportFragmentManager().beginTransaction(), MainActivity.CustomDialog.TAG);
}
}
// ...
}
When the activity is resumed a dialog fragment is shown inside the main activity.
The single fragment inside the view pager is defined like this:
public class RootFragment extends Fragment {
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.root_fragment, container, false);
if (savedInstanceState == null) {
getFragmentManager().beginTransaction().add(R.id.root_frame, new FirstLevelFragment(), "ROOT").commit();
}
return root;
}
}
This root fragment allows us to stack other fragments on the "root_frame". So we stack another and another:
public class FirstLevelFragment extends Fragment {
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
setRetainInstance(true);
View root = inflater.inflate(R.layout.first_level_fragment, container, false);
root.findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
SecondLevelFragment f = (SecondLevelFragment) getActivity().getSupportFragmentManager().findFragmentByTag("NESTED");
if (f == null) {
getActivity().getSupportFragmentManager().beginTransaction().add(R.id.root_frame, new SecondLevelFragment(), "NESTED").addToBackStack(null).commit();
}
}
});
return root;
}
public static class SecondLevelFragment extends Fragment {
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
setRetainInstance(true);
return inflater.inflate(R.layout.second_level_fragment, container, false);
}
}
}
This works great! The stacking idea is taken from https://stackoverflow.com/a/21453571/401025 . However when dialog is shown and the users goes to the second level fragment and rotates the screen I get following exception:
E/AndroidRuntime: java.lang.RuntimeException: Unable to start activity
ComponentInfo{de.azzoft.viewpagerdialogtest/de.azzoft.viewpagerdialogtest.MainActivity}:
java.lang.IllegalArgumentException: No view found for id 0x7f0c0083
(de.azzoft.viewpagerdialogtest:id/root_frame) for fragment
SecondLevelFragment{15c0db38 #0 id=0x7f0c0083 NESTED}
E/AndroidRuntime: Caused by: java.lang.IllegalArgumentException: No
view found for id 0x7f0c0083
(de.azzoft.viewpagerdialogtest:id/root_frame) for fragment
SecondLevelFragment{15c0db38 #0 id=0x7f0c0083 NESTED}
Full stack trace: https://github.com/ArtworkAD/ViewPagerDialogTest/blob/master/README.md
Without the dialog appearing everything works great. You can test it by downloading the test project.
It seems that the dialog, which is actually a fragment, messes up fragment hierarchy when it is added to the activity. Any ideas how to fix this?
It is important that the second fragment is retained.
No view found for id 0x7f0c0083 (de.azzoft.viewpagerdialogtest:id/root_frame) for fragment SecondLevelFragment
When Activity recreates on rotate, the Activity FragmentManger tries to add the SecondLevelFragment into R.id.root_frame . But the root_frame view is not in Activity layout, its in FirstLevelFragment layout. Thats why the app crashes.
You have to make two changes to fix this issue.
Add the FirstLevelFragment into the RootFragment using the getChildFragmentManager
getChildFragmentManager().beginTransaction().add(R.id.root_frame, new FirstLevelFragment(), "ROOT").commit();
Add the SecondLevelFragment using FragmentManager
getFragmentManager().beginTransaction().add(R.id.root_frame, new SecondLevelFragment(), "NESTED").addToBackStack(null).commit();
Finally remove the setRetainInstance from FirstLevelFragment and SecondLevelFragment as nested fragments doesn't required to set retain.
If you need to pop back the SecondLevelFragment on back press you need to pass the back press the event to RootFragment and pop from back stack.
Override the back press on activity
#Override
public void onBackPressed() {
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.viewpager);
if(fragment instanceof RootFragment){
boolean handled = ((RootFragment)fragment).onBackPressed();
if(handled){
return;
}
}
super.onBackPressed();
}
And handle the back press on RootFragment
public boolean onBackPressed() {
int count = getChildFragmentManager().getBackStackEntryCount();
if(count > 0){
getChildFragmentManager().popBackStackImmediate();
return true;
}
return false;
}
I created a Pull request to your repository . please check
https://github.com/ArtworkAD/ViewPagerDialogTest/pull/1
Let me know if any questions.
If you override onDismiss so resolved crash. enjoy it.
#Override
protected void onResume() {
super.onResume();
DialogFragment dialog = (DialogFragment) getSupportFragmentManager().findFragmentByTag(TAG);
if(dialog == null){
CustomDialog.newInstance().show(getSupportFragmentManager(), TAG);
}
}
public static class CustomDialog extends DialogFragment {
public static CustomDialog newInstance() {
CustomDialog d = new CustomDialog();
return d;
}
#Override
public void onDismiss(DialogInterface dialog) {
// super.onDismiss(dialog);
Toast.makeText(getActivity(), "onDismiss", Toast.LENGTH_LONG).show();
}
#Override
public void onCancel(DialogInterface dialog) {
// super.onCancel(dialog);
Toast.makeText(getActivity(), "onCancel", Toast.LENGTH_LONG).show();
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle("Dialog");
builder.setMessage("This is a message!");
builder.setPositiveButton("Okay", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
Toast.makeText(getActivity(), "onClick", Toast.LENGTH_LONG).show();
}
});
builder.setNegativeButton("No", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
Toast.makeText(getActivity(), "onClick", Toast.LENGTH_LONG).show();
}
});
return builder.show();
}
}
If you want to keep the state of your Fragments you should use a FragmentStatePagerAdapter.
From the docs:
Implementation of PagerAdapter that uses a Fragment to manage each
page. This class also handles saving and restoring of fragment's
state.
If you use this you can also remove the setRetainInstance(true) calls.
Well, I had downloaded your Test app and it seems that I have fixed the problem.
In your FirstLevelFragment class, comment the following line
//if (nestedNestedFragment == null) {
getActivity().getSupportFragmentManager().beginTransaction().add(R.id.root_frame, new SecondLevelFragment(), "NESTED").addToBackStack(null).commit();
//}
And
Comment setRetainInstance(true); in SecondLevelFragment
I think you missed setContentView() in onCreate() of your Activity. See your Fragment can not be added without a View hierarchy. Your Fragment is hosted by an activity. So you need to set the content to the activity first.
Hope this Helps,
Thanks.
I have a splash screen Activity that shows a custom DialogFragment. When I press back, I want to close not only the dialogfragment but it's containing activity.
The DialogFragment lacks a onBackPressed(), otherwise I would put
getActivity().finish()
in that callback. I also don't want to put that in onDetach() because if the user clicks on an element in the dialogfragment, it should populate a listview in that same splash activity instead of closing the app.
Why use onDismiss callback? Like this:
#Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
getActivity().finish();
}
Or Override the onBackPressed in your activity:
#Override
public void onBackPressed() {
MyFragment myFrag = getFragmentManager().findFragmentByTag("myFrag");
if (myFrag != null && myFrag.mDialog.isShowing())
myFrag.mDialog.dismiss();
finish();
} else {
super.onBackPressed();
}
}
public class ShadowDialog extends DialogFragment {
#NonNull
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
View view = getActivity().getLayoutInflater().inflate(R.layout.shadow_loader,null);
final Dialog dialog = new Dialog(getActivity()){
#Override
public void onBackPressed() {
super.onBackPressed();
getActivity().onBackPressed();
}
};
dialog.setContentView(view);
dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
dialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
return dialog;
}
}
You could try overriding onKeyDown() to detect Back button press in your DialogFragment:
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
....
#Override
public Dialog onCreateDialog(#Nullable Bundle savedInstanceState) {
Dialog dialog = new Dialog(requireContext()){
#Override
public void onBackPressed() {
super.onBackPressed();
requireActivity().finish();
// or requireActivity().onBackPressed();
}
};
return dialog;
}