SwipeRefreshLayout with ListView done right

Setting up a SwipeRefreshLayout is pretty easy. There are great tutorials out there. Even more if you already played around with other compat components like the DrawerLayout.

You just wrap your ScrollView or ListView, a few wiring here and there, and it's done. Works flawlessly, with just a few lines of code.

But what if you want to use a ListView, but you need something else besides a ListView as SwipeRefreshLayout's only child?

Maybe you want to use an empty view for all those times the list comes up empty. Or you want to put any extra view, like a SmoothProgressBar anywhere. Or a loading spinner at the center of the screen while it's loading.

Image a layout like this one.

<android.support.v4.widget.SwipeRefreshLayout       xmlns:android="http://schemas.android.com/apk/res/android"     
 android:id="@+id/swipe_container"
 android:layout_width="match_parent"     
 android:layout_height="match_parent">      
    <LinearLayout         
                  android:layout_width="match_parent"
                  android:layout_height="match_parent"
                  android:orientation="vertical">          
        <TextView             
                  android:layout_width="match_parent"
                  android:layout_height="match_parent"
                  android:text="@string/guides_no_results"
                  android:id="@+id/empty_view"
                  android:gravity="center"             
                  android:padding="16dp"             
                  android:fontFamily="sans-serif-light"             
                  android:textSize="20sp"             
                  android:visibility="gone"/>           
        <ListView             
                  android:layout_width="match_parent"             
                  android:layout_height="match_parent"             
                  android:drawSelectorOnTop="true"             
                  android:divider="@android:color/transparent"             
                  android:dividerHeight="0dp"             
                  android:id="@+id/guides_list" />      
    </LinearLayout>  
</android.support.v4.widget.SwipeRefreshLayout> 

If you use this as-is, everytime you scroll up in that view, the SwipeRefreshLayout fires and updates, making your app unable to scroll up in a list.

guidesList.setOnScrollListener(new AbsListView.OnScrollListener() {     
  @Override   
  public void onScrollStateChanged(AbsListView view, int scrollState) {
  }    
  @Override   
  public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {     
  int topRowVerticalPosition =        
  (guidesList == null || guidesList.getChildCount() == 0) 
  ? 0 
  : guidesList.getChildAt(0).getTop();     
  swipeContainer.setEnabled(firstVisibleItem == 0 && topRowVerticalPosition >= 0);   
  } 
}); 

The trick here is to wire the OnScrollListener from the ListView manually. You just check if the first row being shown matches the first top-most position, and then enable the SwipeRefreshLayout. Otherwise, disable it.

With this little snippet now it works perfectly.

Happy coding!