146

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
    }
}
Sufian
  • 6,405
  • 16
  • 66
  • 120
thunderousNinja
  • 3,510
  • 9
  • 37
  • 49

14 Answers14

310

Faced with same issue. My solution -

mSwipeRefreshLayout.post(new Runnable() {
    @Override
    public void run() {
        mSwipeRefreshLayout.setRefreshing(true);
    }
});
Volodymyr Baydalka
  • 3,635
  • 1
  • 12
  • 13
  • 1
    Works well but I don't know why we need to do this instead of mSwipeRefreshLayout.setRefreshing(true); – Cocorico May 27 '15 at 07:50
  • http://www.google.com/design/spec/patterns/swipe-to-refresh.html#swipe-to-refresh-swipe-to-refresh – Anoop M Maddasseri Sep 23 '15 at 06:23
  • 1
    That's not a good solution / workaround.If the user is on airplane mode, your refreshLayout would start refreshing on its own thread after already starting the network request and receiving its response (failure in this case). On handling it to stop the refreshLayout, it wouldn't work since it didn't start yet! In other words the refreshLayout would start refreshing after you've received your response. Good luck stopping it – Samer Jan 24 '16 at 09:21
  • 1
    As I remember "posts" are sync and runs in the order of adding. So you can add another post to stop it. – Volodymyr Baydalka Jan 25 '16 at 10:03
  • 2
    It perhaps looks simplest in implementation but it is not nice. @niks.stack solution [below](http://stackoverflow.com/a/28692493/1235698) is better as does not require any changes in code using it, so when eventually this bug is fixed in support lib, you just switch back to support lib SwipeRefreshLayout – Marcin Orlowski Apr 29 '16 at 17:12
  • It doesn't work on Fragments, :( RecyclerView inside SwipeRefresh. – RoCkDevstack Jun 09 '16 at 11:41
  • In addition to this, after my refresh method had completed I had to use exactly this code snippet but with `setRefreshing(false)`. If I just called `setRefreshing(false)` without the Runnable stuff, the progress indicator would never go away. – Ellis Jun 13 '16 at 21:30
  • @savepopulation me to. Do you find any way? – naiyu Jun 16 '16 at 07:23
  • @naiyu no i did not. but i changed it to progressbar for first loading process. and later i show swipe refresh layout. but if i'm not wrong this issue is fixed in latest library. – savepopulation Jun 16 '16 at 07:27
  • @savepopulation I have tried the current version 23.4.0 , but seams not fixed – naiyu Jun 17 '16 at 02:07
102

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.

Ahmed Hegazy
  • 12,395
  • 5
  • 41
  • 64
  • Did you do much testing with the `delay time`? I am using `500` on my `Galaxy S4`. Not sure if this would be a problem on another device. – theblang Dec 02 '14 at 20:09
  • I didn't do much testing with the delay time, but I think `500` would do fine.I have tested it on the `emulator`.I just wanted to be `safe` with the `1000` milliseconds – Ahmed Hegazy Dec 03 '14 at 11:31
  • 3
    There's no need to post delayed. you can just post. posting without a delay just means "do this thing once you're done with what you're doing now". and what it's doing now is measuring and laying out your UI. – Oren Jun 20 '15 at 17:25
48

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;
        }
    }
}
Sufian
  • 6,405
  • 16
  • 66
  • 120
nikita.zhelonkin
  • 627
  • 1
  • 6
  • 13
  • 3
    The advantage of your solution is to keep the view offset untouched! This way we continue in conformity with design pattern specified here: http://www.google.com/design/spec/patterns/swipe-to-refresh.html#swipe-to-refresh-swipe-to-refresh. Thanks! – Igor de Lorenzi Mar 16 '15 at 18:38
  • Can you please explain how this works? It is working but I don't quite get the inner workings. Am I just missing something trivial? – Sree Apr 28 '15 at 03:00
  • setRefreshing works only after onMeasure call, so we store local refreshing flag, and on first onMeasure call apply it – nikita.zhelonkin Apr 28 '15 at 08:49
  • 5
    I love this solution because it means the code calling the SwipeRefreshLayout can be exactly as we desire it to be, without any complications. It basically fixed the bug in SwipeRefreshLayout. SwipeRefreshLayout really should just be implemented this way. – DataGraham Jul 08 '15 at 19:13
  • This answer may looks not that straight forward, but it fixes the issue nicely through many support library versions (in my case it's 23.1.1). According to the ticket this issue is not fixed @ 23.2. https://code.google.com/p/android/issues/detail?id=77712 – Robert Mar 03 '16 at 14:04
  • Great answer. Subclassing FTW! Needless to say, we already have to subclass to catch another annoying bug - http://stackoverflow.com/a/31452997/ – Sufian Aug 03 '16 at 10:34
20
mRefreshLayout.getViewTreeObserver()
                .addOnGlobalLayoutListener(
                        new ViewTreeObserver.OnGlobalLayoutListener() {
                            @Override
                            public void onGlobalLayout() {
                                mRefreshLayout
                                        .getViewTreeObserver()
                                        .removeGlobalOnLayoutListener(this);
                                mRefreshLayout.setRefreshing(true);
                            }
                        });
baoyz
  • 358
  • 1
  • 3
  • 8
  • 3
    This answer is the only one which is not a hack so it should be accepted – Jan Heinrich Reimer May 03 '15 at 14:28
  • 1
    Just a small thing: .removeGlobalOnLayoutListener should be .removeOnGlobalLayoutListener – mkuech Jun 08 '15 at 20:06
  • 1
    @mkeuch depends on which API your targeting. If your targeting under API16, you have to do API version check, and use both. – Marko Aug 11 '15 at 12:48
  • I like this one a bit more than the most upvoted answer because it's more clear why it is used. – Marcel Bro Feb 18 '16 at 19:02
  • I must correct myself, this solution does NOT always work for me. In some cases calling `setRefreshing(false)` wrapped in `onGlobalLayoutListener()` does not dismiss the loading indicator. – Marcel Bro Feb 18 '16 at 19:52
14

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

Andrei Buneyeu
  • 6,662
  • 6
  • 35
  • 38
4

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.

Community
  • 1
  • 1
Gil Vegliach
  • 3,542
  • 2
  • 25
  • 37
2

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.

1

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>
Pang
  • 9,564
  • 146
  • 81
  • 122
Jesús Castro
  • 2,061
  • 1
  • 22
  • 26
1

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'

Junaid
  • 4,822
  • 2
  • 21
  • 27
1

@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;
    }
}
mco
  • 1,809
  • 15
  • 31
  • I was looking for a good callback after onMeasure! Thnx. Was annoying the shit out of me that on the initial run the offset was off... – xdbas Mar 10 '16 at 10:13
0

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);
Artrmz
  • 340
  • 2
  • 14
0

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.

Chinmay
  • 1
  • 1
0

Try this

mSwipeRefreshLayout.setNestedScrollingEnabled(true);

Ashwin H
  • 695
  • 7
  • 24
-1

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;
            }
        }
    }
}
alchy
  • 9
  • 1