How to replace a Fragment under LinearLayout in Tabbed application

Refresh

April 2019

Views

2k time

1

I have a tabbed application with 3 xml layout files. The fragments are created programmatically and added to the LinearLayouts.

However, one of the LinearLayouts looks like this :

map.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/map"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:weightSum="4" >

    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/primary"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:orientation="horizontal" >

        <Button
            android:id="@+id/button1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="YOLO" />
    </LinearLayout>

    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/secondary"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="3"
        android:orientation="horizontal" >
    </LinearLayout>

</LinearLayout>

This tab should be split into two layouts, one taking up 25% of the screen and the other taking up 75%.

This keeps my TabListener from replacing the whole .xml file and instead I can only replace the secondary LinearLayout, because that is where I add the Fragment during the onCreate method of the activity.

Thus I end up with 25% top of the app being static and never changing.

When I try to replace the parent layout map the application crashes.

What can I do?

package com.nfc.demo;

import android.app.ActionBar;
import android.app.ActionBar.Tab;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.res.Configuration;
import android.os.Bundle;

import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMapOptions;
import com.google.android.gms.maps.MapFragment;

public class NFCDemoActivity extends Activity {

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

        FragmentManager fm = getFragmentManager();
        FragmentTransaction ft = fm.beginTransaction();

        GoogleMapOptions options = new GoogleMapOptions();
        options.mapType(GoogleMap.MAP_TYPE_SATELLITE).compassEnabled(false)
                .rotateGesturesEnabled(false).tiltGesturesEnabled(false);

        MapFragment mMapFragment = MapFragment.newInstance(options);
        Fragment mSettingsFragment = new SettingsFragment();
        Fragment mAboutFragment = new AboutFragment();

        ft.add(R.id.secondary, mMapFragment);
        ft.commit();

        ActionBar bar = getActionBar();
        bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
        bar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE);

        ActionBar.Tab mapTab = bar.newTab().setText("Map");
        ActionBar.Tab settingsTab = bar.newTab().setText("Settings");
        ActionBar.Tab aboutTab = bar.newTab().setText("About");

        mapTab.setTabListener(new TabListener(mMapFragment));
        settingsTab.setTabListener(new TabListener(mSettingsFragment));
        aboutTab.setTabListener(new TabListener(mAboutFragment));

        bar.addTab(mapTab, 0);
        bar.addTab(settingsTab, 1);
        bar.addTab(aboutTab, 2);

        setContentView(R.layout.map);
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
    }
    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
    }

    protected class TabListener implements ActionBar.TabListener {
        private Fragment fragment;

        public TabListener(Fragment fragment) {
            this.fragment = fragment;
        }

        public void onTabSelected(Tab tab,
                FragmentTransaction fragmentTransaction) {
            fragmentTransaction.replace(R.id.secondary, this.fragment, null);
        }

        public void onTabUnselected(Tab tab,
                FragmentTransaction fragmentTransaction) {
            fragmentTransaction.remove(this.fragment);
        }

        public void onTabReselected(Tab tab,
                FragmentTransaction fragmentTransaction) {
            // do nothing
        }
    }
}

error log when instead of replacing R.id.secondary, R.id.map is attempted to be replaced

12-30 14:01:16.035: E/AndroidRuntime(12377): FATAL EXCEPTION: main
12-30 14:01:16.035: E/AndroidRuntime(12377): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.nfc.demo/com.nfc.demo.NFCDemoActivity}: java.lang.IllegalStateException: Can't change container ID of fragment MapFragment{41ca1660 id=0x7f04000c}: was 2130968588 now 2130968585
12-30 14:01:16.035: E/AndroidRuntime(12377):    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2180)
12-30 14:01:16.035: E/AndroidRuntime(12377):    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2230)
12-30 14:01:16.035: E/AndroidRuntime(12377):    at android.app.ActivityThread.access$600(ActivityThread.java:141)
12-30 14:01:16.035: E/AndroidRuntime(12377):    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1234)
12-30 14:01:16.035: E/AndroidRuntime(12377):    at android.os.Handler.dispatchMessage(Handler.java:99)
12-30 14:01:16.035: E/AndroidRuntime(12377):    at android.os.Looper.loop(Looper.java:137)
12-30 14:01:16.035: E/AndroidRuntime(12377):    at android.app.ActivityThread.main(ActivityThread.java:5039)
12-30 14:01:16.035: E/AndroidRuntime(12377):    at java.lang.reflect.Method.invokeNative(Native Method)
12-30 14:01:16.035: E/AndroidRuntime(12377):    at java.lang.reflect.Method.invoke(Method.java:511)
12-30 14:01:16.035: E/AndroidRuntime(12377):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
12-30 14:01:16.035: E/AndroidRuntime(12377):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
12-30 14:01:16.035: E/AndroidRuntime(12377):    at dalvik.system.NativeStart.main(Native Method)
12-30 14:01:16.035: E/AndroidRuntime(12377): Caused by: java.lang.IllegalStateException: Can't change container ID of fragment MapFragment{41ca1660 id=0x7f04000c}: was 2130968588 now 2130968585
12-30 14:01:16.035: E/AndroidRuntime(12377):    at android.app.BackStackRecord.doAddOp(BackStackRecord.java:407)
12-30 14:01:16.035: E/AndroidRuntime(12377):    at android.app.BackStackRecord.replace(BackStackRecord.java:429)
12-30 14:01:16.035: E/AndroidRuntime(12377):    at com.nfc.demo.NFCDemoActivity$TabListener.onTabSelected(NFCDemoActivity.java:77)
12-30 14:01:16.035: E/AndroidRuntime(12377):    at com.android.internal.app.ActionBarImpl.selectTab(ActionBarImpl.java:570)
12-30 14:01:16.035: E/AndroidRuntime(12377):    at com.android.internal.app.ActionBarImpl.addTab(ActionBarImpl.java:509)
12-30 14:01:16.035: E/AndroidRuntime(12377):    at com.android.internal.app.ActionBarImpl.addTab(ActionBarImpl.java:490)
12-30 14:01:16.035: E/AndroidRuntime(12377):    at com.nfc.demo.NFCDemoActivity.onCreate(NFCDemoActivity.java:47)
12-30 14:01:16.035: E/AndroidRuntime(12377):    at android.app.Activity.performCreate(Activity.java:5104)
12-30 14:01:16.035: E/AndroidRuntime(12377):    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1080)
12-30 14:01:16.035: E/AndroidRuntime(12377):    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2144)
12-30 14:01:16.035: E/AndroidRuntime(12377):    ... 11 more

1 answers

1

Ok, I've looked into BackStackRecord's source code and found that fragment instance can be attached only to one container's id.

So the solution for this problem is to programmatically create FrameLayout. Set some id to it. Add created layout to first LinearLayout. Add fragment to FrameLayout's id. And when needed remove created layout from one LinearLayout and add to another.

Example code

public void onCreate(Bundle savedInstanceState) {
    ...

    mLayout = new FrameLayout(this);
    mLayout.setId(R.id.tertiary);
    ((LinearLayout) findViewById(R.id.primary)).addView(mLayout, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.FILL_PARENT));
    ft.add(R.id.tertiary, mMapFragment);
    ft.commit();

    ...
}

...

    public void onTabSelected(Tab tab, FragmentTransaction fragmentTransaction) {
        ((LinearLayout) findViewById(R.id.primary)).removeView(mLayout);
        ((LinearLayout) findViewById(R.id.secondary)).addView(mLayout, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.FILL_PARENT));
    }