domingo, 17 de junio de 2012

Programación Android: Como cargar más datos en un ListView al llegar al final

Hola de nuevo. Ahora quiero hablaros de un pequeño truco que he descubierto. La aplicación Android de MasQueCursos.org utiliza varios web services para leer los datos de Internet: lista de cursos, datos de un curso, lista de centros, datos de un centro, lista de categorías, etc. Para no sobrecargar las comunicaciones, en lugar de cargar todos los resultados a mostrar de golpe (por ejemplo cursos), lo hago en bloques de 25. Al principio puse botones para paginar igual que hay en la web: siguiente página, anterior página, primera página y última página, pero eso no quedaba demasiado bien en el móvil: los botones comen espacio para los resultados y la pantalla se veía demasiado llena. Así que decidí que lo ideal era cargar los 25 primeros resultados y cuando el usuario moviera la lista para ver el último, se cargaran automáticamente los siguientes 25 resultados.

Estuve investigando diferentes soluciones en Internet, pero ninguna me acababa de gustar. La mayoría usaban un evento mediante la función setOnScrollListener(), pero me dio algunos problemas y busqué otras soluciones. Al final resulta que ha sido relativamente fácil.

La solución consiste en añadir un elemento vacío al final de la lista de datos de forma que cuando Android llama a la función getView() del adaptador del ListView, si la llamada se hace para la última posición, cargamos los nuevos resultados.

A continuación pongo el código de la clase ListViewActivity, que es la que se encarga de gestionar la lista de resultados. Atención seguir este código requiere un mínimo de conocimientos de programación Android.

public class ListViewActivity
    extends Activity
    implements OnItemClickListener {

    // Miembros de la clase
    private ListViewData[] mListViewData;

    // Controles del layout
    private ListView mListView;

    // Parámetros de llamada
    private int mStart;
    private int mLimit;

    // Tamaño de la vista
    private int mCount;
    private int mActual;

    @Override
    public void onCreate(Bundle savedInstanceState) {

        // Llamada a la clase padre
        super.onCreate(savedInstanceState);

        // Inicialización de la vista
        setContentView(R.layout.listviews);

        // Obtención de referencias permanentes a los controles
        mListView = (ListView) findViewById(R.id.listview);

        // Tratamiento de eventos del ListView
        mListView.setOnItemClickListener(this);

        // Inicializaciones
        mListViewData = null;
        mActual = 0;
        mCount = 0;

        // Se Obtención y carga de datos en el ListView
        mStart = 0;
        mLimit = 25;
        _loadDataCount();
        _loadDataList();

    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

        // Validaciones iniciales (necesario al haber un item de más)
        if(mListViewData == null || mListViewData[position] == null)
            return;

        // Tratamiento de la pulsación sobre el item mListViewData[position]
        // ...
    }

    private void _loadDataCount() {

        // Aquí cargamos en mCount el tamaño total de los datos, ya sea mediante
        // una query a una base de datos interna o extarna o mediante una llamada
        // REST o lo que sea.
        // ...
    }

    private void _loadDataList() {

        // Aquí accedermos a la fuente de datos (base de datos, web service u
        // otro) para obtener los datos entre mStart y mLimit y los cargamos
        // por ejemplo en un array llamado data.
        // ...

        // Se cargan los datos en el ListView
        _fillListView(data);
    }

    private void _fillListView(ListViewData[] data) {

        // Se cuenta el número de items en el array de datos
        int dataCount = data.length();

        // Establecimiento de los textos de la cabecera
        mActual += dataCount;

        // Si no hay datos es un error error
        if(dataCount < 1) {

            // Hacer el tratamiento del error
            // ...

            // Fin por error
            return;
        }

        // Nos guardamos el array de datos anterior
        ListViewData[] oldData = mListViewData;

        // Se crea un array de n + 1 items para el ListView excepto si ya se han
        // cargado todos los datos. Si hay un item de más, éste quedará vacío
        int count = mActual;
        if(mActual < mCount)
            ++count;

        mListViewData = new ListViewData[count];

        // Si había un array anterior, se traspasa la información al nuevo
        int position = 0;
        if(oldData != null) {
            for(int i = 0; i < oldData.length; ++i) {
                if(oldData[i] != null) {
                    mListViewData[position++] = oldData[i];
                }
            }
        }

        // Traspaso de la información leída de la fuente de datos
        for (int i = 0; i < dataCount; i++) {
            mListViewData[position++] = new ListViewData(data[i]);
        }

        // Si hay más resultados, el último item irá vacío
        if(mActual < mCount)
            mListViewData[mActual] = null;

        // Se asigna el nuevo adaptador al ListView
        DataAdapter adapter = new DataAdapter();
        mListView.setAdapter(adapter);

        // Scroll del ListView al último item leído
        mListView.setSelection((mStart > 0) ? mStart - 1 : 0);
    }

    public class ListViewData {

        // En esta clase se definen los datos y métodos para acceder a ellos.
        // ...
    }

    public class DataAdapter extends ArrayAdapter<ListViewData> {

        public DataAdapter() {

            // Llamada a la clase padre
            super(ListViewActivity.this, R.layout.listitem, mListViewData);
        }

        public View getView(int position, View convertView, ViewGroup parent) {

            // Obtención del objeto que se usa para cargar los items
            LayoutInflater inflater = ListViewActivity.this.getLayoutInflater();

            View item;
            if(position == mActual) {

                // Inicializaciones
                item = inflater.inflate(R.layout.listitem, null);

                // Traspaso de información al item recién creado
                // ...

                // Se carga una nueva lista de cursos
                mStart = mActual;
                mLimit = 25;
                _loadDataList();
            }
            else {

                // Inicializaciones
                item = inflater.inflate(R.layout.listitem, null);


                // Traspaso de información al item recién creado
                // ...
            }

            // Finalización
            return(item);
        }
    }
}

PD: ¿Alguien sabe como hacer que el código fuente salga coloreado en el blog? Con las palabras clave resaltadas y eso.

Curso de programación Android

Hola a todos. Llevo algunos meses haciendo un curso de programación Android, por aquello de ampliar horizontes y estar al día de la tecnología más actual. Se trata de un curso a distancia, asistido por un profesor que resuelve tus dudas.

El curso regular lo he acabado y ahora tengo que hacer un proyecto, pero en lugar de hacer algo sólo para demostrar que ya se programar Android, he decidido hacer algo funcional: La aplicación para móviles de una de nuestras webs: MasQueCursos.org. La intención, aparte de acabar el curso, es aprender haciendo una aplicación de verdad para en el futuro poder realizar aplicaciones para terceros. Además cuando esté acabada subiré la aplicación a Google Play, poniéndola gratuita.

Iré informando aquí de los progresos. Además como me estoy encontrando problemas reales, publicaré algunos trucos de programación Android que voy encontrando.

lunes, 16 de abril de 2012

Tabla de países y actualización de códigos postales

En la entrada anterior habíamos ofrecido forma libre y gratuita un archivo que generaba una base de datos de provincias, municipios y códigos postales (MySQL). Ese archivo tenía algunos pequeños errores, por lo que ahora ofrecemos la versión 1.1 del mismo. Se permite explícitamente usar y alterar los datos contenidos en ese archivo para cualesquier fin, aunque no garantizamos que los datos sean correctos o estén totalmente actualizados.

Descargar codigos-postales-v1.1.zip (393 KB).

Además hemos necesitado para otro cliente una lista actualizada de países que se seleccionarán de una lista desplegable en un formulario HTML. En otro proyecto anterior ya habíamos necesitado esa lista de países, así que en este caso ha sido fácil. Aprovechamos para poner a disposición de quien lo necesite, nuevamente de forma libre y gratuita, un archivo que genera una tabla de países en formato MySQL. Se permite explícitamente usar y alterar los datos contenidos en ese archivo para cualesquier fin, aunque no garantizamos que los datos sean correctos o estén totalmente actualizados.

Descargar paises-v1.0.zip (2,67 KB).

El formato de las tablas es el siguiente:

CREATE TABLE `provincias`
( `cod_prov` VARCHAR(2) NOT NULL
, `provincia` VARCHAR(48) NOT NULL
, PRIMARY KEY ( `cod_prov`, `provincia` )
);

CREATE TABLE `poblaciones`
( `cod_postal` VARCHAR(5) NOT NULL
, `poblacion` VARCHAR(64) NOT NULL
, `cod_prov` VARCHAR(2) NOT NULL
, PRIMARY KEY ( `cod_postal`, `poblacion`, `cod_prov` )
);

CREATE TABLE `paises`
( `cod_pais` VARCHAR(2) NOT NULL
, `pais` VARCHAR(64) NOT NULL
, PRIMARY KEY ( `cod_pais` )
);

lunes, 12 de marzo de 2012

Provincias, municipios y códigos postales

Quería empezar este blog oficial de iFraktal Internet Marketing, S.L. con un caso práctico.

Estábamos diseñando la página web para un cliente consistente en una mini-tienda on-line, desde la cual se aceptan pedidos de un producto y pensamos que sería buena idea añadir una validación de códigos postales, municipios y provincias. Al tratarse de un nuevo negocio no había datos previos que importar, con lo partíamos de una base de datos en blanco y de esta forma podíamos evitar que la misma se llenase de información duplicada e inútil que nos hemos encontrado otras veces con nombres de municipio mal escritos. Un caso típico sería encontrarse municipios como "HOSPITALET", "L'HOSPITALET", "HOSPITALET DE LLOBREGAT", "L'HOSPITALET DE LLOBREGAT",... cuando la denominación oficial según el INE es "HOSPITALET DE LLOBREGAT, L'".

Así que nos pusimos a buscar en Internet una lista actualizada de poblaciones y códigos postales españoles. Debería ser fácil, ¿no? PUES NO.

Lo primero que encontramos fue una relación de provincias con sus códigos y la lista oficial de municipios actualizada a 1 de enero de 2012, ambos proporcionados por el INE (Instituto Nacional de Estadística). Hasta aquí fue fácil, pero había un problema: la lista de municipios incluía el código de la provincia, el código de municipio y hasta un dígito de control, pero no incluía el código postal del mismo.

Así que nos pusimos a buscar una relación de códigos postales y municipios. Siguiendo con el INE hay una sección de descargas del callejero, que prometía. Sin embargo una vez descargada la información consistía de 4 archivos de texto en formato ASCII con campos de ancho fijo conteniendo vías, pseudovías, tramos de vías y unidades poblacionales. Existe un archivo en formato Word que explica como se relaciona cada archivo. Tras revisar la documentación y echar un vistazo a los archivos decidimos seguir buscando. Procesar esos archivos iba a requerir demasiado esfuerzo.

Seguimos buscando y lo siguiente que encontramos es que no somos los primeros que nos ha ocurrido esto. En el blog de Carme Pla la autora explica que se encontró con la misma problemática que nosotros. Allí nos enteramos que Correos proporciona unas bases de datos de códigos postales a un precio totalmente desorbitado. El mismo blog nos dirige a una página llamada "Códigos Postales de España: Base de datos", que contiene datos de provincias, municipios poblaciones y calles con sus correspondientes códigos postales en hasta 3 formatos diferentes (CSV, XML y MySQL). Descargamos y probamos.

Los fichero tienen algún fallo y probablemente no estén complemente actualizados pero algo es algo. Tras cargar el archivo CSV en una tabla MySQL y procesar el contenido, hemos conseguido una tabla conteniendo la lista de municipios poblaciones y códigos postales, que era lo que queríamos. La información de las calles se ha eliminado pues en nuestro caso no tenía utilidad.

Y para que este trabajo pueda servir a otros, hemos decidido ofrecer de forma libre y gratuita un archivo de texto conteniendo las sentencias MySQL para crear la base de datos de provincias, municipios poblaciones y códigos postales. Se permite explícitamente usar y alterar los datos contenidos en ese archivo para cualesquier fin. No garantizamos que los datos sean correctos o estén actualizados.

Descargar codigos-postales-v1.0.zip (391 KB).


ACTUALIZACIÓN:

Se ha actualizado el archivo de códigos postales a la versión 1.1 debido a algunos pequeños fallos: Descargar codigos-postales-v1.1.zip.

También puede interesarte: