Usando Web Service JSON con Android (Infinite Scroll, NetworkImage, RecyclerView, CardView...)
Un servicio web o web service es un conjunto de datos publicados a través de una URL y que pueden ser utilizados por otras aplicaciones, comúnmente el formato de salida es XML o JSON, de esta manera las aplicaciones consumen información de un servidor.
Este ejemplo en Android utiliza el siguiente web service itcha.edu.sv/android-json/feed.php?page=1 que genera los datos en formato JSON (JavaScript Object Notation) un formato que empareja los valores en una especie de nodos con un patrón de clave => valor.
El parámetro page=1 puede incrementarse para mostrar más información, incrementando este valor automáticamente se logra un efecto de Infinite Scroll cada vez que llega al limite inferior el contador aumenta en uno (page=2...), trayendo nuevos registros en la consulta, el proyecto usa controles como RecyclerView en lugar de ListView, Android Volley y NetworkImage para aumentar la velocidad de descarga de la información.
Result
MainActivity.java
package com.android_json;
import com.android.volley.Cache;
import com.android.volley.NetworkResponse;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonArrayRequest;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import android.os.Bundle;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
//Creating a List of notices class
private List<Notice> listNotices;
//Creating Views
private RecyclerView mLista;
private RecyclerView.Adapter mAdapter;
StaggeredGridLayoutManager mStaggeredGridLayoutManager;
int mCount = 0;
SwipeRefreshLayout mSwipeRefreshLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mSwipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh_layout);
mLista = (RecyclerView) findViewById(R.id.listaNoticias);
mStaggeredGridLayoutManager = new StaggeredGridLayoutManager(
1, //number of grid columns
GridLayoutManager.VERTICAL);
//Sets the gap handling strategy for StaggeredGridLayoutManager
mStaggeredGridLayoutManager.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_NONE);
mLista.setLayoutManager(mStaggeredGridLayoutManager);
//Initializing our notices list
listNotices = new ArrayList<>();
mLista.setOnScrollListener(new EndlessRecyclerOnScrollListener(mStaggeredGridLayoutManager) {
@Override
public void onLoadMore(int current_page) {
// do something...
getData(current_page);
}
});
mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
mSwipeRefreshLayout.setRefreshing(true);
listNotices = new ArrayList<>();
mCount = 0;
getData(1);
mAdapter = new NoticesAdapter(listNotices, getApplicationContext());
mLista.setAdapter(mAdapter);
mAdapter.notifyDataSetChanged();
mStaggeredGridLayoutManager = new StaggeredGridLayoutManager(
1, //number of grid columns
GridLayoutManager.VERTICAL);
//Sets the gap handling strategy for StaggeredGridLayoutManager
mStaggeredGridLayoutManager.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_NONE);
mLista.setLayoutManager(mStaggeredGridLayoutManager);
}
});
mSwipeRefreshLayout.post(new Runnable() {
@Override
public void run() {
mSwipeRefreshLayout.setRefreshing(true);
getData(1);
mAdapter = new NoticesAdapter(listNotices, getApplicationContext());
mLista.setAdapter(mAdapter);
}
}
);
}
private JsonArrayRequest getDataFromServer(final int requestCount) {
JsonArrayRequest jsonArrayRequest = new JsonArrayRequest(Config.DATA_URL + String.valueOf(requestCount),
new Response.Listener<JSONArray>() {
@Override
public void onResponse(JSONArray response) {
if (requestCount == mCount + 1) {
mCount++;
parseData(response);
}
mSwipeRefreshLayout.setRefreshing(false);
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
mSwipeRefreshLayout.setRefreshing(false);
}
}) {
@Override
protected Response<JSONArray> parseNetworkResponse(NetworkResponse response) {
Response<JSONArray> resp = super.parseNetworkResponse(response);
if (!resp.isSuccess()) {
return resp;
}
long now = System.currentTimeMillis();
Cache.Entry entry = resp.cacheEntry;
if (entry == null) {
entry = new Cache.Entry();
entry.data = response.data;
entry.responseHeaders = response.headers;
entry.ttl = now + 24 * 60 * 60 * 1000; //keeps cache for 1 day
}
entry.softTtl = 0; // refresh in 0 minute
return Response.success(resp.result, entry);
}
};
return jsonArrayRequest;
}
//This method will get data from the web api on scroll increment the page in 1 value
private void getData(int requestCount) {
if (requestCount == mCount + 1) {
MySocialMediaSingleton.getInstance(getApplicationContext()).addToRequestQueue(getDataFromServer(requestCount));
} else {
MySocialMediaSingleton.getInstance(getApplicationContext()).addToRequestQueue(getDataFromServer(mCount + 1));
}
}
//This method will parse json data
private void parseData(JSONArray array) {
for (int i = 0; i < array.length(); i++) {
//Creating the notice object
Notice notice = new Notice();
JSONObject json = null;
try {
//Getting json
json = array.getJSONObject(i);
//Adding data to the notice object
notice.setId(json.getInt(Config.TAG_ID));
notice.setImagen(json.getString(Config.TAG_IMAGE));
notice.setTitulo(json.getString(Config.TAG_TITULO));
notice.setDescripcion(json.getString(Config.TAG_DESCRIPCION));
notice.setFecha(json.getString(Config.TAG_FECHA));
notice.setNombrecorto(json.getString(Config.TAG_NOMBRECORTO));
notice.setFechaPub(json.getString(Config.TAG_FECHAPUB));
} catch (JSONException e) {
e.printStackTrace();
}
//Adding the notice object to the list
listNotices.add(notice);
}
mAdapter.notifyDataSetChanged();
}
}
Config.java
package com.android_json;
/**
* Created by Vladimir Salguero on 16/08/2016.
*/
public class Config {
//Data URL the parameter page is received from GetData method in Main Activity
public static final String DATA_URL = "https://www.itcha.edu.sv/android-json/feed.php?page=";
//JSON TAGS
public static final String TAG_ID = "id";
public static final String TAG_IMAGE = "IMG";
public static final String TAG_TITULO = "titulo";
public static final String TAG_DESCRIPCION = "descripcion";
public static final String TAG_NOMBRECORTO = "nombreCorto";
public static final String TAG_FECHA = "fecha";
public static final String TAG_FECHAPUB = "fechaPub";
}
EndlessRecyclerOnScrollListener.java
package com.android_json;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
public abstract class EndlessRecyclerOnScrollListener extends RecyclerView.OnScrollListener {
public static String TAG = EndlessRecyclerOnScrollListener.class.getSimpleName();
private int scrolledDistance = 0;
private boolean controlsVisible = false;
private int previousTotal = 0; // The total number of items in the dataset after the last load
private boolean loading = true; // True if we are still waiting for the last set of data to load.
private int visibleThreshold = 5; // The minimum amount of items to have below your current scroll position before loading more.
//int firstVisibleItem, visibleItemCount, totalItemCount;
private int pastVisibleItems, visibleItemCount, totalItemCount;
private int current_page = 1;
private LinearLayoutManager mLinearLayoutManager;
private GridLayoutManager mGridLayoutManager;
private StaggeredGridLayoutManager mStaggeredGridLayoutManager;
public EndlessRecyclerOnScrollListener(StaggeredGridLayoutManager staggeredGridLayoutManager) {
this.mStaggeredGridLayoutManager = staggeredGridLayoutManager;
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
visibleItemCount = recyclerView.getChildCount();
totalItemCount = mStaggeredGridLayoutManager.getItemCount();
//firstVisibleItem = mStaggeredGridLayoutManager.findFirstVisibleItemPosition();
int[] firstVisibleItems = null;
firstVisibleItems = mStaggeredGridLayoutManager.findFirstVisibleItemPositions(firstVisibleItems);
if (firstVisibleItems != null && firstVisibleItems.length > 0) {
pastVisibleItems = firstVisibleItems[0];
}
if (loading) {
if ((visibleItemCount + pastVisibleItems) >= totalItemCount) {
loading = false;
previousTotal = totalItemCount;
}
}
if (!loading && (totalItemCount - visibleItemCount)
<= (pastVisibleItems + visibleThreshold)) {
// End has been reached
// Do something
current_page++;
onLoadMore(current_page);
loading = true;
}
if (scrolledDistance > 1 && controlsVisible) {
controlsVisible = false;
scrolledDistance = 0;
} else if (scrolledDistance < -1 && !controlsVisible) {
controlsVisible = true;
scrolledDistance = 0;
}
if ((controlsVisible && dy > 0) || (!controlsVisible && dy < 0)) {
scrolledDistance += dy;
}
}
public abstract void onLoadMore(int current_page);
}
MySocialMediaSingleton.java
package com.android_json;
/**
* Created by Vladimir Salguero on 11/08/2016.
*/
import android.content.Context;
import android.graphics.Bitmap;
import android.util.LruCache;
import com.android.volley.Cache;
import com.android.volley.Network;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.BasicNetwork;
import com.android.volley.toolbox.DiskBasedCache;
import com.android.volley.toolbox.HurlStack;
import com.android.volley.toolbox.ImageLoader;
public final class MySocialMediaSingleton {
// Atributos
private static MySocialMediaSingleton singleton;
private RequestQueue requestQueue;
private static Context context;
private ImageLoader imageLoader;
private MySocialMediaSingleton(Context context) {
MySocialMediaSingleton.context = context;
requestQueue = getRequestQueue();
imageLoader = new ImageLoader(requestQueue,
new ImageLoader.ImageCache() {
private final LruCache<String, Bitmap>
cache = new LruCache<String, Bitmap>(20);
@Override
public Bitmap getBitmap(String url) {
return cache.get(url);
}
@Override
public void putBitmap(String url, Bitmap bitmap) {
cache.put(url, bitmap);
}
});
}
public static synchronized MySocialMediaSingleton getInstance(Context context) {
if (singleton == null) {
singleton = new MySocialMediaSingleton(context);
}
return singleton;
}
public ImageLoader getImageLoader() {
return imageLoader;
}
public void addToRequestQueue(Request req) {
getRequestQueue().add(req);
}
public RequestQueue getRequestQueue() {
if (requestQueue == null) {
Cache cache = new DiskBasedCache(context.getCacheDir(), 10 * 1024 * 1024);
Network network = new BasicNetwork(new HurlStack());
requestQueue = new RequestQueue(cache, network);
requestQueue.start();
}
return requestQueue;
}
}
Notice.java
package com.android_json;
public class Notice {
private String titulo;
private String descripcion;
private String img;
private String fecha;
private String fechaPub;
private String nombrecorto;
private int id;
public Notice() {
}
public Notice(String titulo, String descripcion, String img, String fechaPub, String fecha, String nombrecorto, int id) {
this.titulo = titulo;
this.descripcion = descripcion;
this.img = img;
this.fechaPub = fechaPub;
this.fecha = fecha;
this.nombrecorto = nombrecorto;
this.id = id;
}
public String getTitulo() {
return titulo;
}
public void setTitulo(String titulo) {
this.titulo = titulo;
}
public String getDescripcion() {
return descripcion;
}
public void setDescripcion(String descripcion) {
this.descripcion = descripcion;
}
public String getImagen() {
return img;
}
public void setImagen(String imagen) {
this.img = imagen;
}
public String getFechaPub() {
return fechaPub;
}
public void setFechaPub(String fechaPub) {
this.fechaPub = fechaPub;
}
public String getFecha() {
return fecha;
}
public void setFecha(String fecha) {
this.fecha = fecha;
}
public String getNombrecorto() {
return nombrecorto;
}
public void setNombrecorto(String nombrecorto) {
this.nombrecorto = nombrecorto;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
NoticesAdapter.java
package com.android_json;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.NetworkImageView;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.List;
import androidx.recyclerview.widget.RecyclerView;
/**
* Creado por Vladimir Salguero.
*/
public class NoticesAdapter extends RecyclerView.Adapter<NoticesAdapter.ViewHolder> {
private Context context;
//root url for images
private static final String URL = "https://www.itcha.edu.sv/publicaciones/";
//List to store all notices
List<Notice> notices;
//Constructor of this class
public NoticesAdapter(List<Notice> notices, Context context) {
super();
//Getting all notices
this.notices = notices;
this.context = context;
notifyDataSetChanged();
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_notice, parent, false);
ViewHolder viewHolder = new ViewHolder(v);
return viewHolder;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
//Getting the particular item from the list
Notice notice = notices.get(position);
//Loading image from url
String url_new = URL + notice.getNombrecorto() + "/" + notice.getId() + "-" + notice.getFecha() + "/thumbnail/" + notice.getImagen();
// Petición el image loader
ImageLoader imageLoader = MySocialMediaSingleton.getInstance(context).getImageLoader();
// Request
//Showing data on the views
holder.imageView.setImageUrl(url_new, imageLoader);
holder.titulo.setText(notice.getTitulo());
holder.descripcion.setText(notice.getDescripcion() + "...");
holder.fechaPub.setText(notice.getFechaPub());
}
@Override
public int getItemCount() {
return notices.size();
}
class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
//Views
public NetworkImageView imageView;
public TextView titulo;
public TextView descripcion;
public TextView fechaPub;
//Initializing Views
public ViewHolder(final View itemView) {
super(itemView);
imageView = (NetworkImageView) itemView.findViewById(R.id.img);
titulo = (TextView) itemView.findViewById(R.id.titulo);
descripcion = (TextView) itemView.findViewById(R.id.descripcion);
fechaPub = (TextView) itemView.findViewById(R.id.fechaPub);
imageView.setOnClickListener(this);
}
@Override
public void onClick(View view) {
}
}
}
Layout
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:id="@+id/notices_fragment"
android:layout_centerHorizontal="true"
android:paddingBottom="0dp"
android:layout_height="match_parent"
>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/listaNoticias"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</LinearLayout>
item_notice.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:padding="0dp"
android:layout_margin="0dp"
android:layout_height="wrap_content">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:background="#ffffff"
android:orientation="vertical"
android:padding="0dp"
android:layout_margin="0dp"
android:layout_height="match_parent"
>
<TextView
android:id="@+id/titulo"
android:textStyle="bold"
android:layout_margin="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
android:textSize="26sp"/>
<RelativeLayout android:layout_width="match_parent"
android:background="#f2f2f2"
android:layout_height="wrap_content">
<ProgressBar
android:id="@+id/loadingImg"
style="@android:style/Widget.Holo.ProgressBar"
android:layout_width="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:layout_centerInParent="true"
android:layout_height="wrap_content"
android:indeterminate="true"/>
<com.android.volley.toolbox.NetworkImageView
android:id="@+id/img"
android:layout_width="match_parent"
android:layout_height="232dp"
android:clickable="true"
android:focusable="true"
style="?android:borderlessButtonStyle"
android:scaleType="centerCrop"/>
</RelativeLayout>
<TextView
android:id="@+id/descripcion"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="0dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/img"
android:background="?android:selectableItemBackground"
/>
<LinearLayout android:layout_width="match_parent"
android:layout_marginTop="4dp"
android:layout_marginLeft="12dp"
android:orientation="horizontal"
android:layout_height="wrap_content">
<ImageView android:layout_width="wrap_content"
android:layout_gravity="right"
android:contentDescription="ic_calendar_clock"
android:layout_marginRight="4dp"
android:layout_height="wrap_content"
android:src="@drawable/ic_calendar_clock"
/>
<TextView
android:id="@+id/fechaPub"
android:layout_width="wrap_content"
android:textSize="12dp"
android:textStyle="italic"
android:layout_gravity="right"
android:layout_marginRight="12dp"
android:layout_height="wrap_content"
android:layout_below="@id/descripcion"
android:background="?android:selectableItemBackground"
android:clickable="true"
android:focusable="true"/>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
Descarga el Proyecto completo en GitHub
Visita esta entrada en mi blog