Search code examples
javaandroidxmlandroid-fragmentsfragmenttransaction

How do I dynamically make this child Fragment's TextView visible/invisible?


I have a main Activity containing a parent Fragment, which in turn contains a child Fragment. I am trying to make a TextView in the child Fragment visible/invisible dynamically.

activity_main.xml

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

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="This text belongs to the Activity"
        android:id="@+id/textView"/>

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

</LinearLayout>

fragment_parent.xml

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


    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="This text belongs to the parent fragment"
        android:id="@+id/textView"/>


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

</LinearLayout>

fragment_child.xml

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

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="This text belongs to the child fragment"
        android:id="@+id/textView"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="But make this text visible dynamically!"
        android:id="@+id/make_this_text_visible"
        android:visibility="invisible"/>
</LinearLayout>

MainActivity.java

public class MainActivity extends AppCompatActivity {
    public static final String PARENT_TAG = "parent_tag";
    private ParentFragment mParentFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (savedInstanceState == null) {
            mParentFragment = ParentFragment.newInstance();
            getSupportFragmentManager().beginTransaction().replace(R.id.parent_container, mParentFragment, PARENT_TAG).commit();
        }
        else {
            mParentFragment = (ParentFragment) getSupportFragmentManager().findFragmentByTag(PARENT_TAG);
        }
    }
}

ParentFragment.java

public class ParentFragment extends Fragment {
    public static final String CHILD_TAG = "child_tag";
    private ChildFragment mChildFragment;
    private List<Integer> mList;

    public static ParentFragment newInstance() {

        Bundle args = new Bundle();

        ParentFragment fragment = new ParentFragment();
        fragment.setArguments(args);
        return fragment;
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_parent, container, false);
        mList = new ArrayList<Integer>();

        if (savedInstanceState == null) {
            mChildFragment = ChildFragment.newInstance();
            getChildFragmentManager().beginTransaction().replace(R.id.child_container, mChildFragment, CHILD_TAG).commit();
        }
        else {
            mChildFragment = (ChildFragment) getChildFragmentManager().findFragmentByTag(CHILD_TAG);
        }
        getChildFragmentManager().executePendingTransactions(); //doesn't seem to do anything!
        doStuff();

        return view;
    }

    void doStuff() {
        mList.add(4); //pretend this is actually querying a database.
        //for simplicity it just receives a single 4.

        if (mList.size() > 0) { //the list is not empty, display the text!
            mChildFragment.setTextVisible(); //error! the textivew of the child fragment is null right now
        }
        else {
            mChildFragment.setTextInvisible(); //error! the textivew of the child fragment is null right now
        }
    }
}

ChildFragment.java

public class ChildFragment extends Fragment {
    private TextView mTextView;

    public static ChildFragment newInstance() {

        Bundle args = new Bundle();

        ChildFragment fragment = new ChildFragment();
        fragment.setArguments(args);
        return fragment;
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_child, container, false);
        mTextView = (TextView) view.findViewById(R.id.make_this_text_visible);
        return view;
    }

    public void setTextVisible() {
        mTextView.setVisibility(View.VISIBLE);
    }
    public void setTextInvisible() {
        mTextView.setVisibility(View.INVISIBLE);
    }
}

How can I ensure that the child fragment is formed by the time I call doStuff() in the ParentFragment?


Solution

  • What I would do is keep the state of the text view visibility so it can be updated properly once the view has been created if it hasn't been already. Instead of separate methods for setTextVisible() and setTextInvisible have a single method setTextVisibile(boolean isVisible) and implement it as follows:

    public class ChildFragment extends Fragment {
        private TextView mTextView;
        private boolean mIsTextVisible;
    
        public static ChildFragment newInstance() {
            Bundle args = new Bundle();
            ChildFragment fragment = new ChildFragment();
            fragment.setArguments(args);
            return fragment;
        }
    
        @Nullable
        @Override
        public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
            View view = inflater.inflate(R.layout.fragment_child, container, false);
            mTextView = (TextView) view.findViewById(R.id.make_this_text_visible);
            setTextVisible(mIsTextVisible);
    
            return view;
        }
    
        public void setTextVisible(boolean isVisible) {
            mIsTextVisible = isVisible;
            if(mTextView != null) {
                mTextView.setVisibility(isVisible ? View.VISIBLE : View.GONE);
            }
        }
    }
    

    and then in the parent fragment you can call doStuff() without worrying about the current state of the views in the ChildFragment since mIsTextVisible is properly set. If the mTextView is null at the time setTextVisible() is called the visibility will still be properly set in onCreateView().

    Just take care to save and restore the mIsTextVisible flag when the fragment is recreated such as when the device is rotated.


    Alternative Answer Using A Callback

    Using callbacks update the ParentFragment to implement ChildFragment.OnViewCreatedListener and its method.

    public class ParentFragment extends Fragment
        implements ChildFragment.OnViewCreatedListener {
    
        @Override
        public void onViewCreated() {
            doStuff();
        }
    }
    

    And then in ChildFragment

    public class ChildFragment extends Fragment {
        public interface OnViewCreatedListener {
            void onViewCreated();
        }
    
        public View onCreateView(LayoutInflater inflater,
            @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    
            View view = inflater.inflate(R.layout.fragment_child, container, false);
            mTextView = (TextView) view.findViewById(R.id.make_this_text_visible);
    
            if(getParentFragment() instanceof OnViewCreatedListener) {
                ((OnViewCreatedListener) getParentFragment()).onViewCreated();
            } else if (getActivity() instanceof OnViewCreatedListener) {
                ((OnViewCreatedListener) getActivity()).onViewCreated();
            }
    
            return view;
        }
    }