SwipeRefreshLayout setRefreshing() not showing indicator initially

AndroidSwiperefreshlayout

Android Problem Overview


I have a very simple layout but when I call setRefreshing(true) in onActivityCreated() of my fragment, it does not show initially.

It only shows when I do a pull to refresh. Any ideas why it isn't showing up initially?

Fragment xml:

<android.support.v4.widget.SwipeRefreshLayout
    android:id="@+id/swipe_container"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

        </RelativeLayout>


    </ScrollView>
</android.support.v4.widget.SwipeRefreshLayout>

Fragment code:

public static class LinkDetailsFragment extends BaseFragment implements SwipeRefreshLayout.OnRefreshListener {

    @InjectView(R.id.swipe_container)
    SwipeRefreshLayout mSwipeContainer;

    public static LinkDetailsFragment newInstance(String subreddit, String linkId) {
        Bundle args = new Bundle();
        args.putString(EXTRA_SUBREDDIT, subreddit);
        args.putString(EXTRA_LINK_ID, linkId);

        LinkDetailsFragment fragment = new LinkDetailsFragment();
        fragment.setArguments(args);

        return fragment;
    }

    public LinkDetailsFragment() {
    }

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

        mSwipeContainer.setOnRefreshListener(this);
        mSwipeContainer.setColorScheme(android.R.color.holo_blue_bright,
                android.R.color.holo_green_light,
                android.R.color.holo_orange_light,
                android.R.color.holo_red_light);
        mSwipeContainer.setRefreshing(true);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        final View rootView = inflater.inflate(R.layout.fragment_link_details, container, false);
        ButterKnife.inject(this, rootView);
        return rootView;
    }

    @Override
    public void onRefresh() {
        // refresh
    }
}

Android Solutions


Solution 1 - Android

Faced with same issue. My solution -

mSwipeRefreshLayout.post(new Runnable() {
    @Override
    public void run() {
        mSwipeRefreshLayout.setRefreshing(true);
    }
});

Solution 2 - Android

See Volodymyr Baydalka's answer instead.

These are the old workarounds.

That used to work on earlier version of the android.support.v4, but from version 21.0.0 ongoing it doesn't work and still exists with android.support.v4:21.0.3 released at 10-12 December, 2014 and this is the reason.

SwipeRefreshLayout indicator does not appear when the setRefreshing(true) is called before the SwipeRefreshLayout.onMeasure()

Workaround:

calling setProgressViewOffset() on the SwipeRefreshLayout that invalidtes the circle view of the layout causing SwipeRefreshLayout.onMeasure() to be called immediately.

mSwipeRefreshLayout.setProgressViewOffset(false, 0,
                (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 24, getResources().getDisplayMetrics()));
mSwipeRefreshLayout.setRefreshing(true);

UPDATE Better Workaround

Because the actionbar could got thinner when orientation changes or you have set actionbar size manually. We set the offset in pixels from the top of this view at which the progress spinner should come to reset after a successful swipe gesture to the current actionbar size.

TypedValue typed_value = new TypedValue();
getActivity().getTheme().resolveAttribute(android.support.v7.appcompat.R.attr.actionBarSize, typed_value, true);
mSwipeRefreshLayout.setProgressViewOffset(false, 0, getResources().getDimensionPixelSize(typed_value.resourceId));

UPDATE Nov 20 2014

If it's not very crucial to your app to show the SwipeRefreshLayout once the view is started. You can just post it to a time in the future by using handlers or any thing you want.

as an example.

handler.postDelayed(new Runnable() {
			
	@Override
	public void run() {
		mSwipeRefreshLayout.setRefreshing(true);
	}
}, 1000);

or as Volodymyr Baydalka's answer mentioned.

Here is the issue in the android issue tracker. Please upvote it to show them that we need it to be fixed.

Solution 3 - Android

My solution is to override SwipeRefreshLayout:

public class MySwipeRefreshLayout extends SwipeRefreshLayout {

    private boolean mMeasured = false;
    private boolean mPreMeasureRefreshing = false;

    public MySwipeRefreshLayout(final Context context) {
        super(context);
    }

    public MySwipeRefreshLayout(final Context context, final AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (!mMeasured) {
            mMeasured = true;
            setRefreshing(mPreMeasureRefreshing);
        }
    }

    @Override
    public void setRefreshing(final boolean refreshing) {
        if (mMeasured) {
            super.setRefreshing(refreshing);
        } else {
            mPreMeasureRefreshing = refreshing;
        }
    }
}

Solution 4 - Android

mRefreshLayout.getViewTreeObserver()
                .addOnGlobalLayoutListener(
                        new ViewTreeObserver.OnGlobalLayoutListener() {
                            @Override
                            public void onGlobalLayout() {
                                mRefreshLayout
                                        .getViewTreeObserver()
                                        .removeGlobalOnLayoutListener(this);
                                mRefreshLayout.setRefreshing(true);
                            }
                        });

Solution 5 - Android

With Android support library 24.2.0 the issue should have been solved! https://developer.android.com/topic/libraries/support-library/revisions.html#24-2-0-bugfixes

Solution 6 - Android

Based on Volodymyr Baydalka's answer, this is just a small idea that will help you to keep code clean. It deserves a post I believe: you will be able to revert the behavior easily from posting to direct method call, once the bug is fixed.

Write a utility class like:

public class Utils
{
	private Utils()
	{
	}

    public static void setRefreshing(final SwipeRefreshLayout swipeRefreshLayout, final boolean isRefreshing)
	{
        // From Guava, or write your own checking code
		checkNonNullArg(swipeRefreshLayout);
    	swipeRefreshLayout.post(new Runnable()
		{
    		@Override
    		public void run()
	    	{
	    		swipeRefreshLayout.setRefreshing(isRefreshing);
	    	}
    	});
	}
}

In your code substitute mSwipeContainer.setRefreshing(isRefreshing) by Utils.setRefreshing(mSwipeContainer, isRefreshing): now only one point in the code needs to be changed once the bug is fixed, the Utils class. The method can also be inlined then (and removed from Utils).

There will be usually no noticeable visual difference. Keep in mind though that the pending refresh could keep alive your old Activity instances, holding on the SwipeRefreshLayout in their view hierarchies. If that is a concern, tweak the method to use WeakReferences, but normally you don't block the UI thread anyway, and thus delay gc by a couple of milliseconds only.

Solution 7 - Android

Also you can call this method before setRefreshing..

    swipeRefreshLayout.measure(View.MEASURED_SIZE_MASK,View.MEASURED_HEIGHT_STATE_SHIFT);
swipeRefreshLayout.setRefreshing(true);

It workef for me.

Solution 8 - Android

I used AppCompat Library com.android.support:appcompat-v7:21.0.3, using your same approach and it worked. So, you update the version of that library.

Advice: RelativeLayout doesn't support orientation, it's an attribute for LinearLayout.

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, 
												 Bundle savedInstanceState) {		
  ViewGroup view = (ViewGroup) inflater.inflate(R.layout.license_fragment, 
           container, false);ButterKnife.inject(this, view);
	// Setting up Pull to Refresh
	swipeToRefreshLayout.setOnRefreshListener(this);
	// Indicator colors for refresh
	swipeToRefreshLayout.setColorSchemeResources(R.color.green, 
				R.color.light_green);
}

Layout XML:

<android.support.v4.widget.SwipeRefreshLayout>

<ScrollView
					android:layout_width="match_parent"
					android:layout_height="match_parent"
					android:paddingBottom="@dimen/activity_margin_vertical"
					android:paddingTop="@dimen/activity_margin_vertical">

    <!-- Content -->
</ScrollView>

</android.support.v4.widget.SwipeRefreshLayout>

Solution 9 - Android

In addition to Volodymyr Baydalka use the following code as well:

swipeContainer.post(new Runnable() {
        @Override
        public void run() {
            swipeContainer.setRefreshing(false);
        }
    });

Explanation I implemented the solution given by Volodymyr Baydalka (using fragments) but after the swipeReferesh started it never went away even on calling swipeContainer.setRefreshing(false); so had to implement the code given above which solved my problem. any more ideas why this happens are most welcome.

Regards,

'com.android.support:appcompat-v7:22.2.1'

Solution 10 - Android

@niks.stack In response to his answer, I would show the progress wheel after onLayout() has been done. When I used it directly after onMeasure(), it wouldn't honor some of the offsets, but using it after onLayout() did.

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);
    if (!mLaidOut) {
        mLaidOut = true;
        setRefreshing(mPreLayoutRefreshing);
    }
}

@Override
public void setRefreshing(boolean refreshing) {
    if (mLaidOut) {
        super.setRefreshing(refreshing);
    } else {
        mPreLayoutRefreshing = refreshing;
    }
}

Solution 11 - Android

My solution (without support v7) -

TypedValue typed_value = new TypedValue();
getTheme().resolveAttribute(android.R.attr.actionBarSize, typed_value, true);
swipeLayout.setProgressViewOffset(false, 0, getResources().getDimensionPixelSize(typed_value.resourceId));

if(!swipeLayout.isEnabled())
     swipeLayout.setEnabled(true);
swipeLayout.setRefreshing(true);

Solution 12 - Android

Im using 'com.android.support:appcompat-v7:23.1.1'

swipeRefreshLayout.post(new Runnable() {
        @Override
        public void run() {
            swipeRefreshLayout.setRefreshing(true);
            getData();
        }
    });

previously I was using swipeRefreshLayout.setRefreshing(true); inside getData() method,so it wasn't working. I don't know why its not working inside method.

Although I used swipeRefreshLayout.setRefreshing(true); only once in my Fragment.

Solution 13 - Android

Try this

mSwipeRefreshLayout.setNestedScrollingEnabled(true);

Solution 14 - Android

Another workaround is to create a new control and deriver from SwipeRefreshLayout. Override the OnMeasure function and toggle the refresh again, if refresh is activated. I use this solution in a xamarin project and it works well. Here some sample C#-Code:

class MySwipeRefreshLayout : SwipeRefreshLayout
{
    /// <summary>
    /// used to indentify, if measure was called for the first time
    /// </summary>
    private bool m_MeasureCalled;

    public MvxSwipeRefreshLayout(Context context, IAttributeSet attrs)
        : base(context, attrs)
    {
    }

    public MvxSwipeRefreshLayout(Context context)
        : base(context)
    {
    }
    
    public override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        base.OnMeasure(widthMeasureSpec, heightMeasureSpec);

        if (!m_MeasureCalled)
        {
            //change refreshing only one time
            m_MeasureCalled = true;

            if (Refreshing)
            {
                Refreshing = false;
                Refreshing = true;
            }
        }
    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionthunderousNinjaView Question on Stackoverflow
Solution 1 - AndroidVolodymyr BaydalkaView Answer on Stackoverflow
Solution 2 - AndroidAhmed HegazyView Answer on Stackoverflow
Solution 3 - Androidnikita.zhelonkinView Answer on Stackoverflow
Solution 4 - AndroidbaoyzView Answer on Stackoverflow
Solution 5 - AndroidAndrei BuneyeuView Answer on Stackoverflow
Solution 6 - AndroidGil VegliachView Answer on Stackoverflow
Solution 7 - AndroidLeonardo RoeseView Answer on Stackoverflow
Solution 8 - AndroidJesús CastroView Answer on Stackoverflow
Solution 9 - AndroidJunaidView Answer on Stackoverflow
Solution 10 - AndroidmcoView Answer on Stackoverflow
Solution 11 - AndroidArtrmzView Answer on Stackoverflow
Solution 12 - AndroidChinmayView Answer on Stackoverflow
Solution 13 - AndroidAshwin HView Answer on Stackoverflow
Solution 14 - AndroidalchyView Answer on Stackoverflow