Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android full-screen dialog callback issue

I am having trouble wrapping my head around something but let me first describe my setup:

flow

I have an activity that references 3 fragments, each one of them get shown at the correct time. This is how the ChildrenSpecificationFragment looks:

If the user clicks the floating action button the following DialogFragment opens:

I found the following information in the new material design guidelines: https://www.google.com/design/spec/components/dialogs.html#dialogs-full-screen-dialogs

Avoid dialogs that: Open additional dialogs from within a dialog. Contain scrolling content, particularly alerts. Instead, consider alternate containers or layouts that are optimized for reading or interacting with significant amounts of content.

Exceptions include:Full-screen dialogs may open additional dialogs, such as pickers, because their design accommodates additional layers of material without significantly increasing the app’s perceived z-depth or visual noise.

This is where my problems begin. The 'add child' dialog has scrollable content (in landscape mode) and when the user clicks 'Birth date' a date picker opens.

I am trying to find a way to implement a full screen dialog (as in the guidelines) that has a callback to the ChildrenSpecificationFragment, so that I can add the child to the RecyclerView .

I hope that my questing is clear and would greatly appreciate any input that would lead me to the solution. Thanks in Advance!

like image 712
Jdruwe Avatar asked Jun 06 '15 15:06

Jdruwe


2 Answers

TL;DR - DialogFragment is insufficient for anything other than completely full-screen. Use an Activity instead.

It is possible to make a DialogFragment full-screen (with the ActionBar shown), but it comes with lots of irritations.

A DialogFragment is, as the name suggests, a Dialog and a Fragment rolled into one: it can be treated as both a Dialog, using show() and dismiss(), or as a Fragment, using it with a FragmentManager.

As the official documentation suggests, making a dialog completely full-screen (overlaying everything) is achieved by attaching the dialog to the root view android.R.id.content:

public void showDialog() {
    FragmentManager fragmentManager = getSupportFragmentManager();
    CustomDialogFragment newFragment = new CustomDialogFragment();

    if (mIsLargeLayout) {
        // The device is using a large layout, so show the fragment as a dialog
        newFragment.show(fragmentManager, "dialog");
    } else {
        // The device is smaller, so show the fragment fullscreen
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        // For a little polish, specify a transition animation
        transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
        // To make it fullscreen, use the 'content' root view as the container
        // for the fragment, which is always the root view for the activity
        transaction.add(android.R.id.content, newFragment)
                   .addToBackStack(null).commit();
    }
}

To get the dialog to appear below the ActionBar, a FrameLayout is required which is used instead of the root layout:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/drawer_layout"
    android:layout_height="match_parent"
    android:layout_width="match_parent"
    android:fitsSystemWindows="true">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <android.support.design.widget.CoordinatorLayout
            android:id="@+id/toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <!-- Use ThemeOverlay to make the toolbar and tablayout text
                 white -->
            <android.support.design.widget.AppBarLayout
                android:id="@+id/abl_top"
                android:layout_height="wrap_content"
                android:layout_width="match_parent"
                android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

                <android.support.v7.widget.Toolbar
                    android:id="@+id/toolbar"
                    android:fitsSystemWindows="true"
                    android:layout_height="wrap_content"
                    android:layout_width="match_parent"
                    app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
                    app:layout_scrollFlags="scroll|enterAlways"/>

            </android.support.design.widget.AppBarLayout>
        </android.support.design.widget.CoordinatorLayout>

        <FrameLayout
            android:id="@+id/content"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>

    </LinearLayout>

    <android.support.design.widget.NavigationView
        android:id="@+id/nav_view"
        android:layout_height="match_parent"
        android:layout_width="wrap_content"
        android:layout_gravity="start"
        android:fitsSystemWindows="true"
        app:headerLayout="@layout/nav_header"
        app:menu="@menu/nav_view"/>

</android.support.v4.widget.DrawerLayout>

Now comes the pain.

Depending on how the app's main navigation is setup, different hoops will need to be jumped through in order to get everything working perfectly.

The above example has a NavigationView. Since the home button android.R.id.home is handled in the main view, some logic is needed there to check if our dialog is shown so that the home button, which is now an X, will close the dialog. Returning false here allow the event to be handled in the dialog.

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case android.R.id.home:
            FragmentManager fm = getSupportFragmentManager();
            Fragment f = fm.findFragmentById(R.id.content);
            if (f instanceof MyDialogFragment) {
                return false;
            }
            mDrawerLayout.openDrawer(GravityCompat.START);
            return true;
    }
    return super.onOptionsItemSelected(item);
}

Also, the back button needs similar logic to determine whether the NavigationView needs closing or the ActionBar content resetting.

@Override
public void onBackPressed() {
    if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
        mDrawerLayout.closeDrawer(GravityCompat.START);
    } else {
        FragmentManager fm = getSupportFragmentManager();
        Fragment f = fm.findFragmentById(R.id.content);
        if (f instanceof MyDialogFragment) {
            final ActionBar ab = getSupportActionBar();
            ab.setHomeAsUpIndicator(R.drawable.ic_menu);
            ab.setTitle(R.string.app_name);
        }
        super.onBackPressed();
    }
}

In the DialogFragment itself, the logic for closing the dialog (and abusing the ActionBar) needs to be implemented.

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case android.R.id.home:
            if (mActionBar != null) {
                mActionBar.setHomeAsUpIndicator(R.drawable.ic_menu);
                mActionBar.setTitle(R.string.app_name);
            }
            getActivity().getSupportFragmentManager().popBackStack();
       case R.id.action_save:
            if (mOnAcceptListener != null) {
                mOnAcceptListener.onAccept();
            }
            if (mActionBar != null) {
                mActionBar.setHomeAsUpIndicator(R.drawable.ic_menu);
                mActionBar.setTitle(R.string.app_name);
            }
            getActivity().getSupportFragmentManager().popBackStack();
            return true;
    }
    return super.onOptionsItemSelected(item);
}

This alls feels really kludgy. Of course, if you're using a TabLayout, forget everything I've just said.

With a TabLayout you can just handle everything in the DialogFragment, but if you're using a ViewPager, it'll be impossible to get the dialog to cover the tabs but not the action bar. See Show DialogFragment over TabLayout.

That question (by me) has an answer that suggests the same as @Jdruwe, which is to forget the hopelessness of the DialogFragment and use an Activity instead.

like image 124
Jon Avatar answered Oct 07 '22 16:10

Jon


A solution described on my blog using startActivityForResult(...): http://jeroendruwe.be/full-screen-dialogs-in-android/

like image 40
Jdruwe Avatar answered Oct 07 '22 16:10

Jdruwe