Question

I have an application with a tab bar across the top. When one of the tabs is clicked it loads a ListView. Then when one of the list view item is clicked and it loads a details page. The details page has another toolbar at the top below the tab bar with a back button to go back to the list. One of the list options though is 'Directions'. Instead of the normal details page this loads a view that contains the same tool bar at the top (including a get directions button) and a google map in the rest centered on the location with a marker. This works great initially. If I click the back button it goes to the list just fine and I can go back to the directions. However, there are two things that break this.

  1. I'm on the directions page. I click another tab. Then I click back to the previous tab. Now the list is presented again (as it should). Then I click the 'directions' button. Instead of showing the toolbar and map I get a blank screen below the tab bar.

  2. Again, I'm on the directions page. I click back to go back to the list. Then I click another tab. Then I click back to the previous tab. I click 'Directions' again. The app crashes throwing a NullPointerException. The exception is on the line where I try to retrieve the map by it's id.

Here is the code that gets called anytime the directions list item is clicked and the directions page loads:

    try {
            mLocationClient.connect();
            v = inflater.inflate(R.layout.directions, container, false);
            GoogleMap mMap;
            mMap = ((SupportMapFragment) getFragmentManager().findFragmentById(R.id.directionsmap)).getMap();
            Geocoder coder = new Geocoder(getActivity());
            List<Address> address;

            try {
                String myAddress = "1 Monument Cir Indianapolis, IN 46204";
                address = coder.getFromLocationName(myAddress, 1);
                Address museumLocation = address.get(0);
                mMap.addMarker(new MarkerOptions()
                        .position(new LatLng(museumLocation.getLatitude(), museumLocation.getLongitude()))
                        .title(getResources().getString(R.string.museumTitle)));
            } catch(Exception e) {
                //
            }
            Button backButton = (Button)v.findViewById(R.id.backtoexhibits);
            backButton.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    //Fragment fragment = (getFragmentManager().findFragmentById(R.id.directionsmap));   
                    FragmentTransaction ft = getActivity().getSupportFragmentManager().beginTransaction();
                    //ft.remove(fragment);

                    InfoTab infoTab = new InfoTab();
                    ft.replace(R.id.realtabcontent, infoTab);
                    ft.commit();
                }
            });

            Button directionsButton = (Button)v.findViewById(R.id.getdirections);
            directionsButton.setOnClickListener(new OnClickListener() {

                @Override
                public void onClick(View v) {
                    try {
                        openDirectionsDialog();
                    } catch (UnsupportedEncodingException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            });
        } catch (InflateException e){
            /* map is already there, just return view as it is */
            Log.d("DEBUG",e.getLocalizedMessage());
        }

Here is xml for my directions fragment:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:orientation="vertical" >
<LinearLayout
    android:id="@+id/directionsheader"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/tcmblue"
    android:orientation="horizontal" >
    <Button
        android:id="@+id/backtoexhibits"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/backtext"
        android:layout_margin="5dp"
        android:textColor="@color/white"
        android:background="@drawable/buttonselector"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:paddingTop="5dp"
        android:paddingBottom="5dp" />
    <Button
        android:id="@+id/getdirections"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/getdirections"
        android:layout_margin="5dp"
        android:textColor="@color/white"
        android:background="@drawable/buttonselector"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:paddingTop="5dp"
        android:paddingBottom="5dp" />

</LinearLayout>
<fragment 
    class="com.google.android.gms.maps.SupportMapFragment"
    android:id="@+id/directionsmap"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    map:cameraTargetLat="39.810166"
    map:cameraTargetLng="-86.156708"
    map:cameraZoom="15"
    map:mapType="normal" />

</LinearLayout>

Edit

Here's the code for my tab activty.

import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentTabHost;
import android.support.v4.app.FragmentTransaction;
import android.widget.TabHost.OnTabChangeListener;
import android.widget.TabWidget;
import android.widget.TextView;

public class TabsActivity extends FragmentActivity {
    private FragmentTabHost mTabHost;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.tabs);
        mTabHost = (FragmentTabHost)findViewById(android.R.id.tabhost);
        mTabHost.setup(this, getSupportFragmentManager(), R.id.realtabcontent);

        mTabHost.addTab(mTabHost.newTabSpec("Home").setIndicator("Home", getResources().getDrawable(R.drawable.hometab)),HomeTab.class, null);
        mTabHost.addTab(mTabHost.newTabSpec("Explore").setIndicator("Explore", getResources().getDrawable(R.drawable.exploretab)),ExploreTab.class, null);
        mTabHost.addTab(mTabHost.newTabSpec("Info").setIndicator("Info", getResources().getDrawable(R.drawable.infotab)),InfoTab.class, null);
        mTabHost.addTab(mTabHost.newTabSpec("Social").setIndicator("Social", getResources().getDrawable(R.drawable.socialtab)),SocialTab.class, null);
        mTabHost.addTab(mTabHost.newTabSpec("Contact").setIndicator("Contact", getResources().getDrawable(R.drawable.contacttab)),ContactTab.class, null);

        TabWidget tabWidget = mTabHost.getTabWidget();
        tabWidget.setStripEnabled(false);
        for(int i=0; i < tabWidget.getChildCount(); i++){
            tabWidget.getChildAt(i).setBackgroundResource(R.drawable.tab_bg);
            TextView tv = (TextView)tabWidget.getChildAt(i).findViewById(android.R.id.title);
            tv.setTextColor(this.getResources().getColor(R.color.white));
        }

        mTabHost.setOnTabChangedListener(new OnTabChangeListener() {

            @Override
            public void onTabChanged(String tabId) {
                FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
                if(tabId=="Home"){
                    finish();
                } else if(tabId=="Explore") {
                    ExploreTab exploreTab = new ExploreTab();
                    ft.replace(R.id.realtabcontent, exploreTab);
                } else if(tabId=="Info") {
                    InfoTab infoTab = new InfoTab();
                    ft.replace(R.id.realtabcontent, infoTab);
                } else if(tabId=="Social") {
                    SocialTab socialTab = new SocialTab();
                    ft.replace(R.id.realtabcontent, socialTab);
                } else if(tabId=="Contact") {
                    ContactTab contactTab = new ContactTab();
                    ft.replace(R.id.realtabcontent, contactTab);
                }
                ft.commit();
                TabWidget tw = mTabHost.getTabWidget();
                for(int i=0; i < tw.getChildCount(); i++){
                    TextView tabText = (TextView)tw.getChildAt(i).findViewById(android.R.id.title);
                    if(tabText.getText()==tabId){
                        tabText.setTextColor(TabsActivity.this.getResources().getColor(R.color.tcmgreen));
                    } else {
                        tabText.setTextColor(TabsActivity.this.getResources().getColor(R.color.white));
                    }

                }
            }
        });

        Intent intent = getIntent();
        String tag = intent.getStringExtra("tab");
        mTabHost.setCurrentTabByTag(tag);

    }

    }

Edit 2:

I've commented out the section on the back button that removes the fragment. The app no longer crashes ever. However, I can go to directions once. After that if I hit back or another tab, then go back to directions I get a blank screen where the map should be. Here's what comes out of the stack trace:

12-09 16:29:56.056: W/System.err(16474): android.view.InflateException: Binary XML file line #39: Error inflating class fragment
12-09 16:29:56.066: W/System.err(16474):    at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:713)
12-09 16:29:56.066: W/System.err(16474):    at android.view.LayoutInflater.rInflate(LayoutInflater.java:755)
12-09 16:29:56.066: W/System.err(16474):    at android.view.LayoutInflater.inflate(LayoutInflater.java:492)
12-09 16:29:56.066: W/System.err(16474):    at android.view.LayoutInflater.inflate(LayoutInflater.java:397)
12-09 16:29:56.066: W/System.err(16474):    at org.childrensmuseum.visittcmindy.PageDetails.onCreateView(PageDetails.java:117)
12-09 16:29:56.066: W/System.err(16474):    at android.support.v4.app.Fragment.performCreateView(Fragment.java:1478)
12-09 16:29:56.066: W/System.err(16474):    at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:927)
12-09 16:29:56.066: W/System.err(16474):    at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1104)
12-09 16:29:56.066: W/System.err(16474):    at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:682)
12-09 16:29:56.066: W/System.err(16474):    at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1460)
12-09 16:29:56.066: W/System.err(16474):    at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:440)
12-09 16:29:56.066: W/System.err(16474):    at android.os.Handler.handleCallback(Handler.java:733)
12-09 16:29:56.066: W/System.err(16474):    at android.os.Handler.dispatchMessage(Handler.java:95)
12-09 16:29:56.066: W/System.err(16474):    at android.os.Looper.loop(Looper.java:137)
12-09 16:29:56.066: W/System.err(16474):    at android.app.ActivityThread.main(ActivityThread.java:4998)
12-09 16:29:56.066: W/System.err(16474):    at java.lang.reflect.Method.invokeNative(Native Method)
12-09 16:29:56.066: W/System.err(16474):    at java.lang.reflect.Method.invoke(Method.java:515)
12-09 16:29:56.066: W/System.err(16474):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:777)
12-09 16:29:56.066: W/System.err(16474):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:593)
12-09 16:29:56.066: W/System.err(16474):    at dalvik.system.NativeStart.main(Native Method)
12-09 16:29:56.066: W/System.err(16474): Caused by: java.lang.IllegalArgumentException: Binary XML file line #39: Duplicate id 0x7f050058, tag null, or parent id 0x0 with another fragment for com.google.android.gms.maps.SupportMapFragment
12-09 16:29:56.066: W/System.err(16474):    at android.support.v4.app.FragmentActivity.onCreateView(FragmentActivity.java:290)
12-09 16:29:56.066: W/System.err(16474):    at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:685)

Edit 3

I have it fixed now so that you can go back to the map after clicking back. The fix was fairly simple. What you don't see is, outside the try statement, my view 'v' is declared in the onCreateView function. I took this out, and made it a private static variable in the class. Now it doesn't get lost. I used this thread for the answer: Duplicate ID, tag null, or parent id with another fragment for com.google.android.gms.maps.MapFragment

A new problem has developed though where if you go to the map, click back, then go back into it the back button causes a crash. It triggers a null pointer exception where it creates the FragmentTransaction. I'll start a new thread for that though.

Was it helpful?

Solution 2

As mentioned in Edit 3 above. The fix required moving the declaration of 'v' outside the onCreateView method. I put it as a private static variable at the top of the class. The inspiration for this came from this topic: Duplicate ID, tag null, or parent id with another fragment for com.google.android.gms.maps.MapFragment

A new problem has developed, but it has nothing to do with the map though.

OTHER TIPS

This is my first attempt at answering a SO question, so I apologize if I'm way off base here!

I'm not entirely familiar with how you're using the FragmentTransaction in this case, but my suspicion is that the call to ft.remove(fragment); is actually breaking subsequent calls to look up the fragment.

To elaborate, it looks like you're doing the following (from a high level) when pressing the back button:

  • Finding the map fragment
  • Starting a transaction
  • Removing the map fragment from the fragment manager (I think this is where the problem is)
  • Doing the swap of fragment content (as I would expect you do)
  • Commit the transaction

If you're removing the map fragment, and you feel that you do need to remove it, when do you recreate the fragment and add it back to the manager for future use?

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top