Android full-screen dialog callback issue - android

I am having trouble wrapping my head around something but let me first describe my setup:
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!

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.

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

I don't see code from your post. So I am guessing your code structure as a start.
First build your dialog with a listener and process setPositiveButton() and the onClick event.
Code suggestion:
public class ChildrenSpecificationFragment extends Fragment {
...
public void passData(Object obj) {
}
class SubChildFragment extends Fragment {
AlertDialog.Builder builder = new AlertDialog.Builder(thisContext);
...
// Add the buttons...
builder.setPositiveButton("Save", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
...
passData(Object obj); // pass data to the outer fragment class
Notes:
SubChildFragment, for example, is an inner class derived from Fragment. It can call the public method passData() in the outer class ChildrenSpecificationFragment for passing any data you need.
I am using an inner class because I think this is what you meant in your diagram by
Add child full-screen fragment
This coding technique is easier than starting a new Activity and Intent.
For showing fullscreen dialogs, there is a good Google webpage I think # Dialog - Fullscreen. Search text for "Showing a Dialog Fullscreen or as an Embedded Fragment".

add this line to oncreate in your custom dialog fragment.
setStyle(DialogFragment.STYLE_NORMAL, android.R.style.Theme_Black_NoTitleBar_Fullscreen);
On the other hand, you can use content resolvers to store your children datas.
It has observer pattern. So each CursorAdapter attached to that content it refreshes itself without calling notifySetDataChanged();.
I think you are using RecyclerView.Adapter. You can use this class.
Another advice for implementing adding child feature is using startActivityForResult(activity);.
You can send back datas by using getIntent().getExtras().put(key,value); You can search for custom start activity for result.
Good luck

Related

Interacting with hidden fragment - Android

I'm adding fragments to my activity in way that I first hide current fragment and then add new one. My problem is that when I show my new fragment and start interacting with him, it also interacts with previous one.
The code which I use to add new and hide current fragment is:
public void add(int containerViewId, Fragment fragment, boolean addToBackStack){
FragmentTransaction ft = fragmentManager.beginTransaction().hide(currentFragment).add(containerViewId, fragment);
if (addToBackStack){
ft.addToBackStack(null);
}
ft.commit();
currentFragment = fragment;
backStackCount++;
}
What is going on, and how to hide fragment so I can interact only with the last one added? replace is not an option because I don't want to remove current fragment.
I also had similar problem. I don't know what possibly is creating this issue but what I did to resolve it is that I set an onclick listener to the outermost layout of my fragment.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/top_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFFFF"
android:clickable="false"
android:orientation="vertical"
tools:context=".Fragments.TopicsFragment">
...other components
</LinearLayout>
In fragment:
LinearLayout topLayout = (LinearLayout) fragmentView.findViewById(R.id.top_layout);
topLayout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//do nothing
}
});
Also you might see I have added a background #FFFFFF to this outermost layout, because in mycase the content of previous fragment was also visible behind the new one. So this solved that problem too.

Call other Fragment from dropDown of ActionBar

In my app I have added a DropDown Spinner in ActionBar and now am not able to make out - how to change the bottom part on each drop down selected. In my activity_main.xml layout (Home), contains only a Fragment that shows a list. I am looking for is, to create other UI screens also as Fragment and change the placed Fragment on every dropDown selected. I mean Home page has fragment_main.xml, dropDown setting is selected then show setting_fragment.xml and so on. My main is :
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
tools:ignore="MergeRootFrame" >
<fragment
android:layout_width="match_parent"
android:layout_height="match_parent"
class="terryconsulting.servicestation.AppointListFragment"
android:id="#+id/main_fragment"
/>
</FrameLayout>
In MainActivity class, I have implemented ActionBar.OnNavigationListener which called earlier PlaceholderFragment that just showed Hello World text on the screen.
#Override
public boolean onNavigationItemSelected(int position, long id) {
// When the given dropdown item is selected, show its contents in the
// container view.
//getFragmentManager().beginTransaction()
// .replace(R.id.container, PlaceholderFragment.newInstance(position + 1))
// .commit();
return true;
// http://www.vogella.com/tutorials/AndroidActionBar/article.html --- ACTIONBAR ACTION
}
// As AppointListFragment is added in this activity, so need to implement this listener here
#Override
public void onFragmentInteraction(Uri uri) {
System.out.println("Into onFragmentInteraction. URL - " + uri);
return;
}
WHERE I AM STUCK :- I am not able to make out how to call/set SettingFragment when setting is selected from drop down. And from setting when Home icon is pressed go back to home page with AppointListFragment on it ???
What I am thinking & asking of, is it possible & practical ? I think it should be, as calling Setting as an Activity with its own ActionBar would not be best solution.
Please guide me with this, I have searched many tutorials but couldn't find this situation or examples with NavigationSelection & Fragments any where.
fragments in XML layouts are meant to be static ones. I'm pretty it's possible somehow to make them work, but for dynamic generated/created/removed you want to do everything in Java.
said that, first you remove the <fragment from your XML, then on your activity creation:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(... your xml layout ... );
// fragments are auto-re-created on rotation,
// so only create/put in layout one if not rotating
if(savedInstanceState == null){
// I'm assuming here your first fragment is position 0
gotoFragment(0);
}
}
then on each selection is just call to the new one:
#Override
public boolean onNavigationItemSelected(int position, long id) {
gotoFragment(position);
return true;
}
and here is the general method to change the fragments on the screen.
private void gotoFragment(int position){
Fragment f = getFragmentManager().findFragmentByTag("position_" + position);
if(f == null){
switch(position){
case values:
f = // create here new object of your fragment for each position
}
}
getFragmentManager().beginTransaction()
.replace(R.id.container, f, "position_" + position)
.commit();
}

Is it possible to display a DialogFragment in a master-detail arragement without a new activity?

I'm looking for a way to display a DialogFragment in single-pane mode without creating a new activity.
I originally set up a DialogFragment as a popup dialog in my Android app, with the intent to eventually pursue the master-detail pattern for larger devices.
However, now that I'm looking to finally implement the master-detail setup I'm running into all sorts of UI complications. Basically, I'd like to have something like a 'contextual action mode' update to the action bar. That requires some planning in two-pane mode, but it doesn't work at all with a popup dialog (unless I'm missing some way to show the action bar and the popup dialog).
I'd rather not create a new activity to house the detail DialogFragment on non-tablet/large devices, since there is a lot of DB-related code in the existing activity. However, I have trouble just doing FragmentTransaction.replace because the main view is based on a modified FragmentTabsPager from the compatibility lib v4 demo. I don't have a fragment to replace, unless I wrap the entire pager in a fragment - and I'm worried that nesting fragments is a hack that will complicate, rather simplify things in the long run. Am I wrong?
I'm also using ActionBarCompat, which complicates things as there are some UI options that aren't ported. I'd consider going API 11+ if it meant finding a clean solution to this.
BTW I'm starting to look at Commonsware's master-detail library, but it's a bit of code to grok and ingest, and I think it would require a few possibly big changes to make my code compatible.
Any suggestions or comments? I think I'm too close to this one to see how to simplify it...
(1) You should definitely not have to create an entire new Activity whose sole purpose is to house a new DialogFragment. If you are going to create a new activity at all, you might as well just give it a dialog-theme and display that as your dialog instead... that would eliminate the need to show a DialogFragment entirely.
(2) Your question is a bit too general for me to give a confident answer... you mention "contextual action mode", "dialog fragment", "fragment tabs pager", "nested fragments", etc. and I'm not sure how it all fits together or what specifically you are trying to achieve. What I do know is that no matter the configuration, the host activity should always be the one in charge of performing fragment transactions (such as showing a DialogFragment) as this will significantly reduce code complexity (especially when the number of fragments displayed on the screen varies depending on the screen size). Do your fragments communicate with the activity via activity callback methods (as described here and here)? Where in your code do you show the DialogFragment: in your activity or in one of the master/detail fragments?
Try this:
This is the XML of the layout of your Activity.
What you need to do is enclose all your activity content in a layout (like i have done here in id = all_activity_content_id).
Now put two more views in the activity:- A RelativeLayout for your dialog and a View for making the background translucent.
Note: Make sure the root layout of your activity is a RelativeLayout
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="in.curium.testandroid.MainActivity" >
<!-- all your activity's existing code goes here -->
<RelativeLayout
android:id="#+id/all_activity_content_id"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#android:color/holo_red_light" >
<ToggleButton
android:id="#+id/toggle_dialog_box_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:padding="10dp"
android:textOff="#string/show_dialog"
android:textOn="#string/hide_dialog" />
</RelativeLayout>
<!-- translucent black background behind the dialog -->
<View
android:id="#+id/black_layer_id"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:alpha="0.6"
android:background="#android:color/black"
android:visibility="gone" />
<!-- your dialog layout -->
<RelativeLayout
android:id="#+id/dialog_id"
android:layout_width="200dp"
android:layout_height="150dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="10dp"
android:background="#00BFFF"
android:visibility="gone" >
<TextView
android:id="#+id/dialog_title_id"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#android:color/black"
android:text="#string/dialog_title"
android:textColor="#android:color/white" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="#id/dialog_title_id"
android:text="#string/dialog_description" />
</RelativeLayout>
In your activity class add two methods to show and hide the dialog layout.
Override the back button to dismiss the dialog when visible otherwise call super.
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ToggleButton toggleButton = (ToggleButton) findViewById(R.id.toggle_dialog_box_id);
toggleButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View toggleB) {
boolean isOn = ((ToggleButton) toggleB).isChecked();
if (isOn) {
showDialog();
} else {
hideDialog();
}
}
});
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
private void showDialog() {
View opaqueBackground = findViewById(R.id.black_layer_id);
RelativeLayout dialog = (RelativeLayout) findViewById(R.id.dialog_id);
opaqueBackground.setVisibility(View.VISIBLE);
dialog.setVisibility(View.VISIBLE);
}
private void hideDialog() {
View opaqueBackground = findViewById(R.id.black_layer_id);
RelativeLayout dialog = (RelativeLayout) findViewById(R.id.dialog_id);
opaqueBackground.setVisibility(View.GONE);
dialog.setVisibility(View.GONE);
}
#Override
public void onBackPressed() {
ToggleButton toggleB = (ToggleButton) findViewById(R.id.toggle_dialog_box_id);
boolean isOn = toggleB.isChecked();
if (isOn) {
toggleB.setChecked(false);
hideDialog();
} else {
super.onBackPressed();
}
}}
Now any code that you want to put in the dialog will have access to the database adapter that you have in the activity.
Positioning the dialog in the center of your detail fragment will be a bit tricky.
Hope this helps.
Source Code: https://github.com/testv200/DialogInsideAcitvity
It's not possible Because you are using Activity
If you extends FragmentActivity than it's only possible because if you want to show DialogFragment than it's required FragmentManager reference it's not possible in normal Activity. It's provide only inside FragmentActivity or above API level 13
This is my DialogFragment class
public class DetailedFragment extends DialogFragment {
private static final String ARG_SHOW_AS_DIALOG = "DetailedFragment.ARG_SHOW_AS_DIALOG";
public static DetailedFragment newInstance(boolean showAsDialog) {
DetailedFragment fragment = new DetailedFragment();
Bundle args = new Bundle();
args.putBoolean(ARG_SHOW_AS_DIALOG, showAsDialog);
fragment.setArguments(args);
return fragment;
}
public static DetailedFragment newInstance() {
return newInstance(true);
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args = getArguments();
if (args != null) {
setShowsDialog(args.getBoolean(ARG_SHOW_AS_DIALOG, true));
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.detailed_fragment, container, false);
}
}
This is my ActionBarActivity Class
This is the code for showing Dialog inside ActionBarActivity class.I think Please check your import statements
public class DetailedActivity extends ActionBarActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.detailed_activity);
if (savedInstanceState == null) {
DetailedFragment fragment = DetailedFragment.newInstance(false);
getSupportFragmentManager().beginTransaction().add(R.id.root_layout_details, fragment, "Some_tag").commit();
}
}
}

How do organize an app using fragments?

I am currently re-coding most of the back end of my android app in order to follow the design guidelines more closely. Currently I am using all activities and zero fragments. I am trying to switch to fragments in order to use the slide out navigation draw and eventually some sliding tabs.
For navigation right now I have this drop down menu which when an item is clicked launches a new activity:
The "Your Statistics" activity is kind of like the home page, where the user will enter the app too. I also want the user to be able to get back to that "page" from anywhere in the app.
My activity that I plan to run the draw from I have a draw layout called fragment_main:
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<FrameLayout
android:id="#+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</FrameLayout>
<ListView
android:id="#+id/drawer"
android:layout_width="240dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="#FFF"
android:choiceMode="singleChoice"/>
</android.support.v4.widget.DrawerLayout>
and my activity which loads the drawer layout is:
public class MainDraw extends FragmentActivity {
final String[] data ={"one","two","three"};
final String[] fragments ={
"com.beerportfolio.beerportfoliopro.FragmentOne",
"com.beerportfolio.beerportfoliopro.FragmentTwo",
"com.beerportfolio.beerportfoliopro.FragmentThree"};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_main);
//todo: load statistics
ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActionBar().getThemedContext(), android.R.layout.simple_list_item_1, data);
final DrawerLayout drawer = (DrawerLayout)findViewById(R.id.drawer_layout);
final ListView navList = (ListView) findViewById(R.id.drawer);
navList.setAdapter(adapter);
navList.setOnItemClickListener(new AdapterView.OnItemClickListener(){
#Override
public void onItemClick(AdapterView<?> parent, View view, final int pos,long id){
drawer.setDrawerListener( new DrawerLayout.SimpleDrawerListener(){
#Override
public void onDrawerClosed(View drawerView){
super.onDrawerClosed(drawerView);
FragmentTransaction tx = getSupportFragmentManager().beginTransaction();
tx.replace(R.id.main, Fragment.instantiate(MainDraw.this, fragments[pos]));
tx.commit();
}
});
drawer.closeDrawer(navList);
}
});
FragmentTransaction tx = getSupportFragmentManager().beginTransaction();
tx.replace(R.id.main,Fragment.instantiate(MainDraw.this, fragments[0]));
tx.commit();
}
}
IN my //todo: comment I should load my first "home" fragment there which is my statistics "page" ? And then all the other fragments will be transitioned in and out based on the draw clicks?
Thanks for your help in advance, I want to make sure I am doing this right, I used to code just to get things working which is why I am now re doing a huge chunk of my code. Please share any other fragment tips to that I might need!
First of all read the well written documentation, it answers to your doubts.
I would share my personal pattern to convert existing Activity to Fragment
Create your on abstract Fragment class from which derive all drawer fragments, this can help to group common attributes
Use a method like selectItem() on docs, it helps to explicit do a call at first run (showing the "home" fragment) and then from onItemClick
move inflating XML layout from Activity.onCreate() code to Fragment.onCreateView() (ie setContentView to inflater.inflate(R.layout.my_layout, container, false), in many cases you can copy all code from onCreate() to onCreateView
move initialization code from Activity.onCreate() to Fragment.onActivityCreated(), this is very useful when both Activity (including fragment) and the direct Fragment exist, for example if your app exposes a "Share with" action you continue to have the Activity that inside the XML includes a <fragment/> and the fragment can be created from the drawer, too
if you need to communicate from Activity to Fragment and viceversa I suggest to create an interface and store it inside the 'onAttach()' (see google example)
Action bar items must be hidden when drawer is open, again take a look at example used in doc, here is very useful the interface to communicate from activity to fragment, the main activity can tell if drawer is open and the fragment can call the interface
public interface FragmentActivityStatus {
public boolean isDrawerOpen();
}
The activity
public class MainActivity extends Activity implements FragmentActivityStatus {
#Override
public boolean isDrawerOpen() {
return drawerLayout.isDrawerOpen(drawerList);
}
}
The fragment
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
fragmentActivityStatus = (FragmentActivityStatus)activity;
}
#Override
public void onPrepareOptionsMenu(Menu menu) {
boolean isMenuVisible = !fragmentActivityStatus.isDrawerOpen();
menu.findItem(R.id.my_menu).setVisible(isMenuVisible);
super.onPrepareOptionsMenu(menu);
}
Not related to fragment, in your code you declare class names as string, consider to create a Class array if you refactor packages the code continue to work, then you can call the Class.getName() to obtain the string to pass to Fragment.instantiate()
final Class<?>[] fragments = {
FragmentOne.class,
FragmentTwo.class,
FragmentThree.class};
Then
FragmentTransaction tx = getSupportFragmentManager().beginTransaction();
tx.replace(R.id.main, Fragment.instantiate(MainDraw.this,
fragments[pos].getName()));
tx.commit();

Best practice for nested fragments in Android 4.0, 4.1 (<4.2) without using the support library

I'm writing an app for 4.0 and 4.1 tablets, for which I do not want to use the support libraries (if not needed) but the 4.x api only therefore.
So my target platform is very well defined as: >= 4.0 and <= 4.1
The app has a multi-pane layout (two fragments, one small on the left, one content fragment on the right) and an action bar with tabs.
Similar to this:
Clicking a tab on the action bar changes the 'outer' fragment, and the inner fragment then is a fragment with two nested fragments (1. small left list fragment, 2. wide content fragment).
I am now wondering what's the best practice to replace fragments and especially nested fragments.
The ViewPager is part of the support library, there's no native 4.x alternative for this class. Appear to be 'deprecated' in my sense.
- http://developer.android.com/reference/android/support/v4/view/ViewPager.html
Then I read the release notes for Android 4.2, regarding ChildFragmentManager, which would be a good fit, but I am targeting 4.0 and 4.1, so this can't be used either.
ChildFragmentManager is only available in 4.2
http://developer.android.com/about/versions/android-4.2.html#NestedFragments
http://developer.android.com/reference/android/app/Fragment.html#getChildFragmentManager()
Unfortunately, there are hardly any good examples out there that show best practices for fragments usages without the support library, even in the entire Android developer guides; and especially nothing regarding nested fragments.
So I am wondering: is it simply not possible to write 4.1 apps with nested fragments without using the support library and everything that comes with it? (need to use FragmentActivity instead of Fragment, etc.?)
Or what would be the best practice?
The problem that I am currently having in the development is exactly this statement:
http://developer.android.com/about/versions/android-4.2.html#NestedFragments
The Android Support Library also now supports nested fragments, so you
can implement nested fragment designs on Android 1.6 and higher.
Note: You cannot inflate a layout into a fragment when that layout
includes a <fragment>. Nested fragments are only supported when added
to a fragment dynamically.
Because I put define the nested fragments in XML, which apparently causes an error like:
Caused by: java.lang.IllegalArgumentException: Binary XML file line #15: Duplicate id 0x7f090009, tag frgCustomerList, or parent id 0x7f090008 with another fragment for de.xyz.is.android.fragment.CustomerListFragment_
At the moment, I conclude for myself: even on 4.1, when I don't even want to target the 2.x platform, nested fragments as shown in the screenshot are not possible without the support library.
(This might actually be more of a wiki entry than a question, but maybe somebody else has managed it before).
Update:
A helpful answer is at: Fragment Inside Fragment
Limitations
So nesting fragments inside another fragment is not possible with xml regardless of which version of FragmentManager you use.
So you have to add fragments via code, this might seem like a problem, but in the long run makes your layouts superflexible.
So nesting without using getChildFragmentManger? The essence behind childFragmentManager is that it defers loading until the previous fragment transaction has finished. And of course it was only naturally supported in 4.2 or the support library.
Nesting without ChildManager - Solution
Solution, Sure! I have been doing this for a long time now, (since the ViewPager was announced).
See below; This is a Fragment that defers loading, so Fragments can be loaded inside of it.
Its pretty simple, the Handler is a really really handy class, effectively the handler waits for a space to execute on the main thread after the current fragment transaction has finished committing (as fragments interfere with the UI they run on the main thread).
// Remember this is an example, you will need to modify to work with your code
private final Handler handler = new Handler();
private Runnable runPager;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
return inflater.inflate(R.layout.frag_layout, container, false);
}
#Override
public void onActivityCreated(Bundle savedInstanceState)
{
super.onActivityCreated(savedInstanceState);
runPager = new Runnable() {
#Override
public void run()
{
getFragmentManager().beginTransaction().addFragment(R.id.frag_container, MyFragment.newInstance()).commit();
}
};
handler.post(runPager);
}
/**
* #see android.support.v4.app.Fragment#onPause()
*/
#Override
public void onPause()
{
super.onPause();
handler.removeCallbacks(runPager);
}
I wouldn't consider it 'best practice', but I have live apps using this hack and I am yet to have any issues with it.
I also use this method for embedding view pagers - https://gist.github.com/chrisjenx/3405429
The best way to do this in pre-API 17 is to not do it at all. Trying to implement this behavior is going to cause issues. However that is not to say that it cannot be faked convincingly using the current API 14. What I did was the following:
1 - look at communication between fragments http://developer.android.com/training/basics/fragments/communicating.html
2 - move your layout xml FrameLayout from your existing Fragment to the Activity layout and hide it by giving a height of 0:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<FrameLayout android:id="#+id/content"
android:layout_width="300dp"
android:layout_height="match_parent" />
<FrameLayout android:id="#+id/lstResults"
android:layout_width="300dp"
android:layout_height="0dp"
android:layout_below="#+id/content"
tools:layout="#layout/treeview_list_content"/>
<FrameLayout android:id="#+id/anomalies_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_toRightOf="#+id/content" />
3 - Implement the interface in the parent Fragment
OnListener mCallback;
// Container Activity must implement this interface
public interface OnListener
{
public void onDoSomethingToInitChildFrame(/*parameters*/);
public void showResults();
public void hideResults();
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
mCallback = (OnFilterAppliedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnListener");
}
}
#Override
public void onActivityCreated(Bundle savedInstanceState)
{
super.onActivityCreated(savedInstanceState);
mCallback.showResults();
}
#Override
public void onPause()
{
super.onPause();
mCallback.hideResults();
}
public void onClickButton(View view)
{
// do click action here
mCallback.onDoSomethingToInitChildFrame(/*parameters*/);
}
4 - Implement the interface in the parent Activity
public class YourActivity extends Activity implements yourParentFragment.OnListener
{
public void onDoSomethingToInitChildFrame(/*parameters*/)
{
FragmentTransaction ft = getFragmentManager().beginTransaction();
Fragment childFragment = getFragmentManager().findFragmentByTag("Results");
if(childFragment == null)
{
childFragment = new yourChildFragment(/*parameters*/);
ft.add(R.id.lstResults, childFragment, "Results");
}
else
{
ft.detach(childFragment);
((yourChildFragment)childFragment).ResetContent(/*parameters*/);
ft.attach(childFragment);
}
ft.commit();
showResultsPane();
}
public void showResults()
{
FragmentTransaction ft = getFragmentManager().beginTransaction();
Fragment childFragment = getFragmentManager().findFragmentByTag("Results");
if(childFragment != null)
ft.attach(childFragment);
ft.commit();
showResultsPane();
}
public void showResultsPane()
{
//resize the elements to show the results pane
findViewById(R.id.content).getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
findViewById(R.id.lstResults).getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
}
public void hideResults()
{
//resize the elements to hide the results pane
findViewById(R.id.content).getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT;
findViewById(R.id.lstResults).getLayoutParams().height = 0;
FragmentTransaction ft = getFragmentManager().beginTransaction();
Fragment childFragment = getFragmentManager().findFragmentByTag("Results");
if(childFragment != null)
ft.detach(childFragment);
ft.commit();
}
}
5 - Enjoy, with this method you get the same fluid functionality as with the getChildFragmentManager() function in a pre-API 17 envoronment. As you may have noticed the child fragment is no longer really a child of the parent fragment but now a child of the activity, this really cannot be avoided.
I had to deal with this exact issue due to a combination of NavigationDrawer, TabHost, and ViewPager which had complications with usage of the support library because of TabHost. And then I also had to support min API of JellyBean 4.1, so using nested fragments with getChildFragmentManager was not an option.
So my problem can be distilled to...TabHost (for top level)
+ ViewPager (for just one of the top level tabbed fragments)
= need for Nested Fragments (which JellyBean 4.1 won't support)
My solution was to create the illusion of nested fragments without actually nesting fragments. I did this by having the main activity use TabHost AND ViewPager to manage two sibling Views whose visibility is managed by toggling layout_weight between 0 and 1.
//Hide the fragment used by TabHost by setting height and weight to 0
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 0);
mTabHostedView.setLayoutParams(lp);
//Show the fragment used by ViewPager by setting height to 0 but weight to 1
lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1);
mPagedView.setLayoutParams(lp);
This effectively allowed my fake "Nested Fragment" to operate as an independent view as long as I manually managed the relevant layout weights.
Here's my activity_main.xml:
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.ringofblades.stackoverflow.app.MainActivity">
<TabHost
android:id="#android:id/tabhost"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout android:id="#android:id/tabcontent"
android:background="#drawable/background_image"
android:layout_width="match_parent"
android:layout_weight="0.5"
android:layout_height="0dp"/>
<android.support.v4.view.ViewPager
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/pager"
android:background="#drawable/background_image"
android:layout_width="match_parent"
android:layout_weight="0.5"
android:layout_height="0dp"
tools:context="com.ringofblades.stackoverflow.app.MainActivity">
<FrameLayout
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.v4.view.ViewPager>
<TabWidget android:id="#android:id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</TabHost>
<fragment android:id="#+id/navigation_drawer"
android:layout_width="#dimen/navigation_drawer_width"
android:layout_height="match_parent"
android:layout_gravity="start"
android:name="com.ringofblades.stackoverflow.app.NavigationDrawerFragment"
tools:layout="#layout/fragment_navigation_drawer" />
</android.support.v4.widget.DrawerLayout>
Note that "#+id/pager" and "#+id/container" are siblings with 'android:layout_weight="0.5"' and 'android:layout_height="0dp"'. This is so that I can see it in the previewer for any screen size. Their weights will be manipulated in code during runtime, anyway.
Building on #Chris.Jenkins answer, this is the solution that has been working well for me, for removing fragment(s) during life cycle events (which have a tendency to throw IllegalStateExceptions). This uses a combination of the Handler approach, and an Activity.isFinishing() check (otherwise it will throw an error for "Can not perform this action after onSaveInstanceState).
import android.app.Activity;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
public abstract class BaseFragment extends Fragment {
private final Handler handler = new Handler();
/**
* Removes the {#link Fragment} using {#link #getFragmentManager()}, wrapped in a {#link Handler} to
* compensate for illegal states.
*
* #param fragment The {#link Fragment} to schedule for removal.
*/
protected void removeFragment(#Nullable final Fragment fragment) {
if (fragment == null) return;
final Activity activity = getActivity();
handler.post(new Runnable() {
#Override
public void run() {
if (activity != null && !activity.isFinishing()) {
getFragmentManager().beginTransaction()
.remove(fragment)
.commitAllowingStateLoss();
}
}
});
}
/**
* Removes each {#link Fragment} using {#link #getFragmentManager()}, wrapped in a {#link Handler} to
* compensate for illegal states.
*
* #param fragments The {#link Fragment}s to schedule for removal.
*/
protected void removeFragments(final Fragment... fragments) {
final FragmentManager fragmentManager = getFragmentManager();
final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
for (Fragment fragment : fragments) {
if (fragment != null) {
fragmentTransaction.remove(fragment);
}
}
final Activity activity = getActivity();
handler.post(new Runnable() {
#Override
public void run() {
if (activity != null && !activity.isFinishing()) {
fragmentTransaction.commitAllowingStateLoss();
}
}
});
}
}
Usage:
class MyFragment extends Fragment {
#Override
public void onDestroyView() {
removeFragments(mFragment1, mFragment2, mFragment3);
super.onDestroyView();
}
}
Although the OP may have special circumstances that prevent him from using the Support Library, most people should use it. The Android documentation recommends it, and it will make your app available to the widest audience possible.
In my fuller answer here I made an example demonstrating how to use nested fragments with the support library.

Categories

Resources