Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android dynamic fragment loading progress bar visibility won't set

I'm using Android fragments to load the code in my application. To create a simple loader I have LoadingFragment extends Fragment and then my fragment classes extend that, for example: MyFragment extends LoadingFragment.

The LoadingFragment has hideLoader and showLoader which theoretically my subclass Fragment should be able to call in onCreateView and onPostExecute to show and hide a progress bar in between loads.

Layout wise I have a main_layout.xml which has a framelayout for the dynamic fragments and a static relativelayout that houses the progress bar.

At the moment my fragments load and replace from within one another onclick of elements but I have removed that code.

The Problem

The issue is the setVisibilty in LoadingFragment seems to have zero effect when calling it from the subclasses, for example MyFragment, why is this?

I have given LoadingFragment it's own View variable of viewMaster which I believe should reference main_layout but still the visibility changes seem to have no effect?

MainActivity

public class MainActivity extends AppCompatActivity {

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

        MyFragment myFragment = MyFragment.newInstance();

        getSupportFragmentManager().beginTransaction()
            .replace(R.id.fragment_holder, myFragment).commit();
    }
}

LoadingFragment

public class LoadingFragment extends Fragment {

    View viewMaster;

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

        viewMaster = inflater.inflate(R.layout.main_layout, container, false);

        return viewMaster;
    }

    public void showLoader() {

        viewMaster.findViewById(R.id.fragment_holder).setVisibility(View.INVISIBLE);
        viewMaster.findViewById(R.id.loading).setVisibility(View.VISIBLE);
    }

    public void hideLoader() {

        viewMaster.findViewById(R.id.fragment_holder).setVisibility(View.VISIBLE);
        viewMaster.findViewById(R.id.loading).setVisibility(View.INVISIBLE);
    }
}

MyFragment

public class MyFragment extends LoadingFragment {

    View view;

    public MyFragment() {}

    public static MyFragment newInstance() {
        MyFragment fragment = new MyFragment();
        return fragment;
    }

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

        super.onCreateView(inflater, container, savedInstanceState);
        super.showLoader();

        view = inflater.inflate(R.layout.fragment_lans, container, false);

        MyFragment.ApiCallJob apicalljob = new MyFragment.ApiCallJob();
        apicalljob.execute("a string");

        return view;
    }

    public class ApiCallJob extends AsyncTask<String, Void, String> {

        @Override
        protected String doInBackground(String[] params) {
            // do things
        }

        @Override
        protected void onPostExecute(String data) {
            // do things
            // tried both to be sure but they should do the same?  
            hideLoader();
            MyFragment.super.hideLoader();
        }
    }

}

main_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="app.stats.MainActivity"
    android:orientation="horizontal">

    <FrameLayout
        android:id="@+id/fragment_holder"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </FrameLayout>

    <RelativeLayout
        android:id="@+id/loading"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

        <ProgressBar
            style="?android:attr/progressBarStyleLarge"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/progress_bar"/>
    </RelativeLayout>

</RelativeLayout>
like image 785
Dan Avatar asked Dec 14 '16 13:12

Dan


1 Answers

The issue is the setVisibility in LoadingFragment seems to have zero effect when calling it from the subclasses, for example MyFragment, why is this?

Let's look at what happens when MyFragment onCreateView is called:

You call super onCreateView which is in LoadingFragment. This inflates main_layout and returns the inflated view. Note that this view is not referenced in MyFragment (i.e. View mainLayout = super.onCreateView(inflater, container, savedInstanceState);)

You just inflated the view that has loading view group, then threw it away. However, you did manage to save a reference to that view in the LoadingFragment superclass.

Next you inflate R.layout.fragment_lans and (after starting your load) you return that view as the view to be used in the fragment.

So the thing to observe here is that LoadingFragment now has a reference to a view that is nowhere in the fragment's active view hierarchy.

Given that, it's no wonder that setVisibility doesn't do anything, because the fragment isn't displaying that view.

I wouldn't use inheritance to do this, but if you must use it, here's how to fix your problem.

  1. Let's just use the ProgressBar without the view group wrapper. Since it's in a RelativeLayout, let's center it. Then make it android:visibility="gone" so it's effectively out of the fragment layout:

            <ProgressBar
                android:id="@+id/progress_bar"
                style="?android:attr/progressBarStyleLarge"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:visibility="gone"/>
    

    You're going to put this progress bar in the layout for every LoadingFragment subclass, including fragment_lans.xml. You can use the <include> tag to make life easier here.

  2. Change the LoadingFragment so that a) it doesn't interfere with the inflation of its subclass' layouts and b) it uses the progress bar in the subclass' active view hierarchy:

    public class LoadingFragment extends Fragment {
    
        public void showLoader() {
    
            if (getView() != null) {
               View progressBar = getView().findViewById(R.id.progress_bar);
               if (progressBar != null) {
                   progressBar.setVisibility(View.VISIBLE);
               }
            }
        }
    
        public void hideLoader() {
    
            if (getView() != null) {
               View progressBar = getView().findViewById(R.id.progress_bar);
               if (progressBar != null) {
                   progressBar.setVisibility(View.GONE);
               }
            }
        }
    }
    

Now you should see what you expect.


EDIT

If you really want to use the layout scheme you started with, here is my recommendation:

Your activity is inflating main_layout that has the progress bar, so I would give some of the responsibility to the activity.

Now, you could just write some code for the fragment that would trawl around the view hierarchy and look for the progress bar. That wouldn't be my preference.

Here's another approach:

  • Create an interface

    interface ProgressDisplay {
    
        public void showProgress();
    
        public void hideProgress();
    }
    
  • Make the activity implement it

        public class MainActivity extends AppCompatActivity implements ProgressDisplay {
    

    ...

        public void showProgress() {
            findViewById(R.id.loading).setVisibility(View.VISIBLE);
        }
    
        public void hideProgress() {
            findViewById(R.id.loading).setVisibility(View.GONE);
        }
    
  • Then add the methods to LoadingFragment that call the activity:

        protected void showProgress() {
            if (getActivity() instanceof ProgressDisplay) {
                ((ProgressDisplay) getActivity()).showProgress();
            }
        }
    
        protected void hideProgress() {
            if (getActivity() instanceof ProgressDisplay) {
                ((ProgressDisplay) getActivity()).hideProgress();
            }
        }
    

This way you can take any activity that inflates a layout with your progress view and have it implement ProgressDisplay so you can use the LoadingFragment class with it.

You could also make a LoadingActivity class and subclass that, too.

like image 93
kris larson Avatar answered Nov 09 '22 19:11

kris larson