Showing posts with label Android Fragments. Show all posts
Showing posts with label Android Fragments. Show all posts

Thursday, 14 May 2015

Duplicate ID binary XML error in fragments

When dealing Google V2 maps in android tabs and Fragments which generally come across with a issue i.e Duplicate ID binary XML error.

To be more specific Error faced is below :
android.view.InflateException: Binary XML file line #7: Error inflating class fragment

Now why this happens :

Because you already have the fragment added in the FragmentManager, you can't add the same fragment twice which will cause duplication of ID i.e two fragments with same ID.

Now this generally happens when we are adding a fragment that contains a fragment in its layout as well, mostly in case of map fragments.

Our tendency is to add Mapfragment in XML layout like this :

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:orientation="vertical" >

    <fragment

        android:id="@+id/map"

        android:layout_width="match_parent"

        android:layout_height="match_parent"

        class="com.google.android.gms.maps.SupportMapFragment" />



</LinearLayout>


And our Fragment inflate this layout like this :

public class LocationFragment extends Fragment {



View mView;

private static GoogleMap map;

@Override

public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {

mView = inflater.inflate(R.layout.locationfragment, container, false);

return mView;

}

}


Now if we see closely what happening here. We will add this fragment in a tab, which contains a fragment in its layout as well.This is the reason of problem here.

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.

As per Andorid Documentation this approach is not recommended.We need to add map fragments Dynamically in order to accomplish nested fragments as per android guidelines.Check Nested fragments from below link :

http://developer.android.com/about/versions/android-4.2.html#NestedFragments

So Android-supported way is to add a fragment to another fragment is via a transaction from the child fragment manager as shown below :

XML Layout :

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:map="http://schemas.android.com/apk/res-auto"

   android:layout_width="match_parent"

   android:layout_height="match_parent" >

<!-- Lots of fancy layout -->  
<RelativeLayout
        android:id="@+id/map"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    </RelativeLayout>
</RelativeLayout>


Java Code Snippet :

public class MyFragment extends Fragment {

private SupportMapFragment fragment;
private GoogleMap map;

@Override

public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    return inflater.inflate(R.layout.layout_with_map, container, false);
}

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    FragmentManager fm = getChildFragmentManager();
    fragment = (SupportMapFragment) fm.findFragmentById(R.id.map);
    if (fragment == null) {
        fragment = SupportMapFragment.newInstance();
       fm.beginTransaction().replace(R.id.map, fragment).commit();
    }
}



@Override
public void onResume() {
    super.onResume();
    if (map == null) {
        map = fragment.getMap();
        map.addMarker(new MarkerOptions().position(new LatLng(0, 0)));
    }
}
}

There are other ways to resolve the errors as well if you are willing to you map fragment inside a XML layout.Below are the ways to solve the error :

1. Remove fragment on onDestroyView() :

@Override
public void onDestroyView() {
    super.onDestroyView();
    MapFragment f = (MapFragment) getFragmentManager()
                                         .findFragmentById(R.id.map);

    if (f != null)
        getFragmentManager().beginTransaction().remove(f).commit();
}



2. Remove the parent view if it exists in onCreateView() :

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    if (view != null) {
        ViewGroup parent = (ViewGroup) view.getParent();
        if (parent != null)
            parent.removeView(view);
    }
    try {
        view = inflater.inflate(R.layout.map, container, false);
    } catch (InflateException e) {
    }
    return view;
}
The above two methods will resolve our issue of duplicate ID in map fragment when dealing with fragments and tabs, but its kind of "Jugaad"(tricky way) as we say in HINDI.So this might be working now but might not working in upcoming versions of android.So better way to go is as Android Recommends.


References : http://developer.android.com/about/versions/android-4.2.html#NestedFragments , www.stackoverflow.com.

Happy Coding!!
Cheers.

Monday, 4 May 2015

FragmentStatePagerAdapter vs FragmentPagerAdapter

FragmentStatePagerAdapter

1. FragmentStatePagerAdapter destroy the fragment which is no longer needed i.e which is not visible to user.Similar to a recylerview/Listview which recyles the view memory      when goes out of sight.
2. FragmentStatePagerAdapter Saves the Fragment bundle from the SaveInstance and re-stores the fragment when user comes back to the destroyed fragment.

FragmentPagerAdapter

1. In FragmentPagerAdapter fragment of each page which user visited is kept in memory.Which causes memory issues in case of heavy fragments.
2. FragmentPagerAdapter calls detach(Fragment) on the transaction instead of remove(Fragment).This only removes the fragment view but fragment Instance remains in memory.


So, When to use FragmentPagerAdapter And FragmentStatePagerAdapter?

When we know the amount of data to be display or content is static or another case can when we know devices we are targetting never gonna run out of memory, Because this causes alot of memory consumption as it loads fragment in memory once visited by user.

And In case when we do not know the size, then we prefer FragmentStatePagerAdapter.Because it allows us adding fragments without causing memory issues.As it destroy the fragment which is no longer visible kinds of recyling the memory from those memory.

Code Snipnet for FragmentStatePagerAdapter:

class MyAdapter extends FragmentStatePagerAdapter {

        public MyAdapter(FragmentManager fm) {

            super(fm);

        }

        @Override

        public int getCount() {

            return NUM_ITEMS;

        }

        @Override

        public Fragment getItem(int position) {

            return new Fragment(params if any);

        }

    }



Code Snipnet for FragmentPagerAdapter:

class MyAdapter extends FragmentPagerAdapter {

        public MyAdapter(FragmentManager fm) {

            super(fm);

        }

        @Override

        public int getCount() {

            return NUM_ITEMS;

        }

        @Override

        public Fragment getItem(int position) {

            return new Fragment(params if any);

        }

    }





References Stackoverflow,http://developer.android.com/

Tuesday, 31 March 2015

Working with tabs in Android using Fragments

Hey Guys,

Today i worked on tabs using fragments. And Always try to keep things simple as a like that        way.
 So here we go...
 
Since we have seen alots of examples which maintain fragment stacks manually using hashmaps  and all . But i've read this from stackoverflow and other reference web sites about           
 fragmenttabhost so going to use this.
And thats how it works.
Base Classes :

 1. AppMainTabActivity.java
    
public class AppTabBSecondFragment extends BaseFragment {

Button mBackbtn;
View mView;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mView = inflater.inflate(R.layout.app_tab_b_second_screen, container,
false);
initializeWidgets();
return mView;
}

private void initializeWidgets() {
mBackbtn = (Button) mView.findViewById(R.id.backbtn);

mBackbtn.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {

mActivity.handleBackPress();

}
});
}

}

2. BaseFragment.java

import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;

public class BaseFragment extends Fragment {
public AppMainTabActivity mActivity;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

mActivity = (AppMainTabActivity) this.getActivity();
}

public boolean onBackPressed() {
return false;
}

public void onActivityResult(int requestCode, int resultCode, Intent data) {

}
}


Fragments which we are going to add in tabs and replace now 

1. AppTabAFirstFragment.java


public class AppTabAFirstFragment extends BaseFragment {
    private Button mGotoButton;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View view       =   inflater.inflate(R.layout.app_tab_a_first_screen, container, false);

        mGotoButton =   (Button) view.findViewById(R.id.id_next_tab_a_button);
        mGotoButton.setOnClickListener(listener);

        return view;
    }

    private OnClickListener listener        =   new View.OnClickListener(){
        @Override
        public void onClick(View v){
            /* Go to next fragment in navigation stack*/
            mActivity.mAddFragments(AppConstants.TAB_A, new AppTabASecondFragment(),true,true);
        }
    };
}


2. AppTabASecondFragment.java

public class AppTabASecondFragment extends BaseFragment {

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
        View view    =  inflater.inflate(R.layout.app_tab_a_second_screen, container, false);
        return view;
}

}

3. AppTabBFirstFragment.java

public class AppTabBFirstFragment extends BaseFragment {

    private Button mGotoButton;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View view       =   inflater.inflate(R.layout.app_tab_b_first_screen, container, false);

        mGotoButton =   (Button) view.findViewById(R.id.id_next_tab_b_button);
        mGotoButton.setOnClickListener(listener);

        return view;
    }

    private OnClickListener listener        =   new View.OnClickListener(){
        @Override
        public void onClick(View v){
            /* Go to next fragment in navigation stack*/
            mActivity.mAddFragments(AppConstants.TAB_B, new AppTabBSecondFragment(),true,true);
        }
    };
}


4. AppTabBSecondFragment.java

public class AppTabBSecondFragment extends BaseFragment {

Button mBackbtn;
View mView;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mView = inflater.inflate(R.layout.app_tab_b_second_screen, container,
false);
initializeWidgets();
return mView;
}

private void initializeWidgets() {
mBackbtn = (Button) mView.findViewById(R.id.backbtn);

mBackbtn.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {

mActivity.handleBackPress();

}
});
}

}

5. AppTabCFirstFragment.java

public class AppTabCFirstFragment extends BaseFragment {

    private Button mGotoButton;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View view       =   inflater.inflate(R.layout.app_tab_c_first_screen, container, false);

        mGotoButton =   (Button) view.findViewById(R.id.id_next_tab_C_button);
        mGotoButton.setOnClickListener(listener);

        return view;
    }

    private OnClickListener listener   =  new View.OnClickListener(){
        @Override
        public void onClick(View v){
            /* Go to next fragment in navigation stack*/
            mActivity.mAddFragments(AppConstants.TAB_C, new AppTabCSecondFragment(),true,true);
        }
    };
}


6. AppTabCSecondFragment.java

public class AppTabCSecondFragment extends BaseFragment {
Button mBackbtn;
View mView; 
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mView = inflater.inflate(R.layout.app_tab_c_second_screen,
container, false);
initializeWidgets();
return mView;
}

private void initializeWidgets() {
mBackbtn=(Button)mView.findViewById(R.id.backbtn);
mBackbtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {

mActivity.handleBackPress();
}
});

}
}


Now interface that i used to handle backpress normally :

1. BackButtonCallback.java

public interface BackButtonCallback {

void handleBackPress();
}


Now the constants class for this ....


1. AppConstants.java

public class AppConstants {
public static final String TAB_A = "tab_a_identifier";
public static final String TAB_B = "tab_b_identifier";
public static final String TAB_C = "tab_c_identifier";

}


And finally the XML layouts :  


1. app_main_tab_fragment_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/tabhost"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

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

        <TabWidget
            android:id="@android:id/tabs"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_weight="0"
            android:orientation="horizontal" />

        <FrameLayout
            android:id="@android:id/tabcontent"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_weight="0" />

        <FrameLayout
            android:id="@+id/realtabcontent"
            android:layout_width="fill_parent"
            android:layout_height="0dp"
            android:layout_weight="1" />
    </LinearLayout>

</TabHost>

2. tabs_icon.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tabsLayout" android:layout_width="fill_parent"
android:layout_height="fill_parent" android:background="@drawable/tab_bg_selector"
android:gravity="center" android:orientation="vertical">
    <ImageView android:layout_width="fill_parent" android:layout_height="fill_parent"
        android:id="@+id/tab_icon" />
</LinearLayout>

3. app_tab_a_first_screen.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#aa5500"
    android:gravity="center"
    android:orientation="vertical" >

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="TAB A FIRST SCREEN"
        android:textColor="@android:color/white"
        android:textSize="30sp"
        android:textStyle="bold" />

    <Button
        android:id="@+id/id_next_tab_a_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="10dip"
        android:text="Next" />

</LinearLayout>

4. app_tab_a_second_screen.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="match_parent"
    android:orientation="vertical"  android:background="#0055aa"
    android:gravity="center">
    
    <TextView android:layout_width="fill_parent" android:layout_height="wrap_content"
        android:gravity="center" android:text="TAB A SECOND SCREEN"
        android:textSize="30sp" android:textStyle="bold"
        android:textColor="@android:color/white"/>
    
</LinearLayout>

5. app_tab_b_first_screen.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="match_parent"
    android:orientation="vertical"  android:background="#000000"
    android:gravity="center">
    
    <TextView android:layout_width="fill_parent" android:layout_height="wrap_content"
        android:gravity="center" android:text="TAB B FIRST SCREEN"
        android:textSize="30sp" android:textStyle="bold"
        android:textColor="@android:color/white"/>
    
    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:text="Next" android:id="@+id/id_next_tab_b_button"
        android:padding="10dip"/>

</LinearLayout>

6. app_tab_b_second_screen.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#00ffff"
    android:gravity="center"
    android:orientation="vertical" >

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="TAB B SECOND SCREEN"
        android:textColor="@android:color/white"
        android:textSize="30sp"
        android:textStyle="bold" />

    <Button
        android:id="@+id/backbtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/back_button" />

</LinearLayout>

7.  app_tab_c_first_screen.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="match_parent"
    android:orientation="vertical"  android:background="@android:color/darker_gray"
    android:gravity="center">
    
    <TextView android:layout_width="fill_parent" android:layout_height="wrap_content"
        android:gravity="center" android:text="@string/tab_c_first_screen"
        android:textSize="30sp" android:textStyle="bold"
        android:textColor="@android:color/white"/>
    
    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:text="Next" android:id="@+id/id_next_tab_C_button"
        android:padding="10dip"/>

</LinearLayout>

8. app_tab_c_second_screen.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="match_parent"
    android:orientation="vertical"  android:background="@android:color/background_dark"
    android:gravity="center">
    
    <TextView android:layout_width="fill_parent" android:layout_height="wrap_content"
        android:gravity="center" android:text="@string/tab_c_second_screen"
        android:textSize="30sp" android:textStyle="bold"
        android:textColor="@android:color/white"/>
    
    
    <Button 
        android:id="@+id/backbtn"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:text="@string/back_button"
        />
</LinearLayout>


Output :