Programación declarativa de aplicaciones cliente-servidor en Android. Parte 2

El artículo anterior mostró brevemente las ventajas de la programación declarativa de aplicaciones cliente-servidor para Android en comparación con el imperativo.

Ahora escribiremos un proyecto pequeño pero suficiente para evaluar la efectividad de la biblioteca DePro. Es parte de uno de los ejemplos educativos de la biblioteca . El diseño de todas las pantallas que describimos se muestra en las siguientes figuras:

imagen imagen imagen
Pantalla DRAWER Pantalla CATALOGUE Pantalla PRODUCT_LIST

imagen imagen imagen
 CATALOG_           DESCRIPT          CHARACTERISTIC

imagen imagen imagen
 FITNESS           FITNESS_          


Desde estas pantallas, su funcionalidad se entiende generalmente. El menú lateral contiene dos elementos "Catálogo" y "Estado físico". Cuando selecciona un elemento, se muestra la pantalla correspondiente. La pantalla CATÁLOGO contiene una lista horizontal de "Noticias". Cuando hace clic en cualquier "producto nuevo", se muestra una pantalla con una descripción de este producto. Descripción consta de dos pestañas: "Descripción" y "Características". También en la pantalla CATÁLOGO hay una lista desplegable "Catálogo". Cuando hace clic en la flecha hacia abajo, el directorio se abre (se cierra). Cuando hace clic en la línea completa, se muestra la pantalla PRODUCT_LIST con una lista de productos para el artículo seleccionado en el catálogo.

Cuando hace clic en el elemento "Fitness", se muestra la pantalla APTITUD con una lista de servicios. La lista depende del club seleccionado en la ruleta. Cuando intenta salir de la aplicación, aparece un cuadro de diálogo de advertencia.

El servidor transfiere datos en formato json a la aplicación. La estructura de datos para cada pantalla se describe a continuación. En las aplicaciones escritas en DePro desde la API, solo se usa la URL, la estructura de datos solo se necesita para establecer correctamente la vista de nombre (id), porque el enlace se realiza por nombre. Vale la pena señalar que los datos son una porción incompleta de datos reales. Por lo tanto, puede que no haya conexiones en nombres, imágenes. En particular, las imágenes del producto suman un total de 20 piezas. Por lo tanto, la misma imagen puede estar en muchos productos.

API por ejemplo pantallas
CATALOG () URL depro/cron/news, GET. . .



    {
        "product_id":4610,
        "catalog_id":15984,
        "product_name":"  20  6  ( 1*20) APRO",
        "catalog_code":"ZRG-20kit",
        "picture":"depro/cronimg/picture_1.jpeg",
        "bar_code":"4824041010653",
        "oem":"",
        "price":175.98,
        "brand":"APRO",
        "product_code":"032578",
        "gift":0,
        "bonus":0,
        "new_product":1,
        "quantity":5
    },
    . . .

URL depro/cron/catalog.

URL depro/cron/catalog_ex catalog_id id , .

:

[
    {
        "catalog_id":15510,
        "parent_id":0,
        "catalog_name":""
    },
    {
        "catalog_id":15584,
        "parent_id":0,
        "catalog_name":""
    },
    . . .
]

PRODUCT_LIST URL depro/cron/product_list : expandedLevel catalog_id.
expandedLevel — (0, 1 2), catalog_id — id .

0 1 , , .

GET

()

URL depro/cron/product_barcode
barcode_scanner.

URL depro/cron/product_search product_name. LIKE. product_name .

.

DESCRIPT

: URL depro/cron/product_id product_id. GET.



{
    "product_id":2942,
    "catalog_id":15594,
    "product_name":"   2110, 2111, 2112,  Sens ''  ",
    "catalog_code":"23.3828"
    ,"picture":"depro/cronimg/picture_16.jpeg",
    "bar_code":"2000000148472",
    "oem":"2112-3851010",
    "price":103.02,
    "brand":", . , ",
    "product_code":"027729",
    "gift":1,
    "bonus":0,
    "new_product":0,
    "quantity":16
}

: URL depro/cron/product_analog product_id. GET.



[
    {
        "product_id":561,
        "catalog_id":15587,
        "product_name":"   2110, 2111, 2112 (  16 . ) AURORA",
        "catalog_code":"WP-LA2112",
        "picture":"depro/cronimg/picture_12.jpeg",
        "bar_code":"2900011680711",
        "oem":"2112-1307010",
        "price":188.16,
        "brand":"AURORA, Poland",
        "product_code":"016807",
        "gift":0,
        "bonus":1,
        "new_product":0,
        "quantity":15
    },
    . . .
]


CHARACTERISTIC URL depro/cron/product_charact product_id. GET.


[
    {
        "prop_id":2764,
        "product_id":2942,
        "name":"",
        "value":", . , "
    },
    {
        "prop_id":2765,
        "product_id":2942,
        "name":"   ",
        "value":","
    },
    . . .
]


Ahora considere nuestros pasos para escribir una solicitud.

1. En el estudio crearemos un nuevo proyecto. El nombre se puede establecer a su discreción.

2. Despliegue de recursos. Como se mencionó en el primer artículo, los archivos de recursos (XML) se usan de manera convencional. Por lo tanto, para no perder el tiempo en cosas conocidas, simplemente descargamos todos los recursos necesarios .

Descomprima el archivo res_example.zip resultante. En el estudio, elimine todo el contenido de la carpeta res.

Transfiera el contenido de la carpeta res descomprimida a la carpeta res del proyecto. Después de eso, teniendo en cuenta las características de Android Studio, es posible que deba borrar el proyecto y / o ejecutar el comando Invalid Caches / Restart.

3. Conexión de la biblioteca

En la sección de dependencias del archivo build.gradle del módulo, debe especificar:

    implementation 'github.com/deprosystem/depro:compon:3.0.1'

En la sección de Android del archivo build.gradle del módulo, debe especificar:

    packagingOptions {
        exclude 'META-INF/DEPENDENCIES'
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

Al elegir el atributo minSdkVersion, debe tener en cuenta que la biblioteca admite minSdkVersion = 17. Después de cambiar build.gradle, debe sincronizar el proyecto.

4. Creación de clases necesarias (archivos). Cuando se trabaja con la biblioteca, solo se utilizan 4 clases: MyDeclareScreens: se describen todas las pantallas; MyParams: establece los parámetros necesarios para la aplicación; MyApp: se inicia la biblioteca DePro; MainActivity - actividad de inicio. Puedes usar tus propios nombres de clase.

Cree la clase MyDeclareScreens.java. Sus contenidos son los siguientes:

public class MyDeclareScreens extends DeclareScreens {

    public final static String
            MAIN = "main", DRAWER = "DRAWER", CATALOG = "CATALOG",
            DESCRIPT = "DESCRIPT", CHARACTERISTIC = "CHARACTERISTIC",
            PRODUCT_LIST = "PRODUCT_LIST", PRODUCT_DESCRIPT = "PRODUCT_DESCRIPT",
            FITNESS = "FITNESS";

    @Override
    public void declare() {
        activity(MAIN, R.layout.activity_main)
                .navigator(finishDialog(R.string.attention, R.string.finishOk))
                .drawer(R.id.drawer, R.id.content_frame, R.id.left_drawer, null, DRAWER);

        fragment(DRAWER, R.layout.fragment_drawer)
                .menu(model(menu), view(R.id.recycler));

        fragment(CATALOG, R.layout.fragment_catalog)
                .navigator(handler(R.id.back, VH.OPEN_DRAWER))
                .component(TC.RECYCLER_HORIZONTAL, model(Api.NEWS_PROD).pagination().progress(R.id.progr),
                        view(R.id.recycler_news, R.layout.item_news_prod),
                        navigator(start(PRODUCT_DESCRIPT)))
                .component(TC.RECYCLER, model(Api.CATALOG),
                        view(R.id.recycler, "expandedLevel", new int[]{R.layout.item_catalog_type_1,
                                R.layout.item_catalog_type_2, R.layout.item_catalog_type_3})
                                .expanded(R.id.expand, R.id.expand, model(Api.CATALOG_EX, "catalog_id")),
                        navigator(start(PRODUCT_LIST)));

        activity(PRODUCT_LIST, R.layout.activity_product_list, "%1$s", "catalog_name").animate(AS.RL)
                .navigator(back(R.id.back))
                .component(TC.RECYCLER, model(Api.PRODUCT_LIST, "expandedLevel,catalog_id"),
                        view(R.id.recycler, R.layout.item_product_list)
                                .visibilityManager(visibility(R.id.bonus_i, "bonus"),
                                        visibility(R.id.gift_i, "gift"),
                                        visibility(R.id.newT, "new_product")),
                        navigator(start(PRODUCT_DESCRIPT)));

        activity(PRODUCT_DESCRIPT, R.layout.activity_product_descript, "%1$s", "catalog_name").animate(AS.RL)
                .navigator(back(R.id.back))
                .setValue(item(R.id.product_name, TS.PARAM, "product_name"))
                .component(TC.PAGER_F, view(R.id.pager, DESCRIPT, CHARACTERISTIC)
                        .setTab(R.id.tabs, R.array.descript_tab_name));

        fragment(DESCRIPT, R.layout.fragment_descript)
                .component(TC.PANEL, model(Api.PRODUCT_ID, "product_id"),
                        view(R.id.panel).visibilityManager(visibility(R.id.bonus, "bonus")))
                .component(TC.RECYCLER, model(Api.ANALOG_ID_PRODUCT,"product_id"),
                        view(R.id.recycler, R.layout.item_product_list).noDataView(R.id.not_analog)
                                .visibilityManager(visibility(R.id.bonus_i, "bonus"),
                                        visibility(R.id.gift_i, "gift"),
                                        visibility(R.id.newT, "new_product")),
                        navigator(start(0, PRODUCT_DESCRIPT, PS.RECORD),
                                handler(0, VH.BACK))) ;

        fragment(CHARACTERISTIC, R.layout.fragment_characteristic)
                .component(TC.RECYCLER, model(Api.CHARACT_ID_PRODUCT, "product_id"),
                        view(R.id.recycler, "2", new int[] {R.layout.item_property, R.layout.item_property_1}));

        fragment(FITNESS, R.layout.fragment_fitness)
                .navigator(handler(R.id.back, VH.OPEN_DRAWER))
                .component(TC.SPINNER, model(JSON, getString(R.string.clubs)),
                        view(R.id.spinner, R.layout.item_spin_drop, R.layout.item_spin_hider))
                .component(TC.RECYCLER, model(Api.FITNESS, "clubId"),
                        view(R.id.recycler, R.layout.item_fitness), null).eventFrom(R.id.spinner);
    }

    Menu menu = new Menu()
            .item(R.drawable.list, R.string.m_catalog, CATALOG, true)
            .divider()
            .item(R.drawable.ic_aura, R.string.fitness, FITNESS);
}

Más adelante describiremos todas las construcciones DePro utilizadas. Para conectar la importación, use las teclas alt + enter. El rojo también se resaltará en la clase Api en la que se establecen las direcciones para todas las solicitudes. Su contenido se dará más adelante.

Cree la clase MyParams.java. En la mayoría de los casos, la configuración predeterminada es suficiente. En nuestro caso, configuraremos solo la URL base.

public class MyParams extends AppParams {
    @Override
    public void setParams() {
        baseUrl = "https://deprosystem.com/";
    }
}

Cambie el contenido creado por el estudio de la clase MainActivity a lo siguiente:

public class MainActivity extends BaseActivity {
    @Override
    public String getNameScreen() {
        return MyDeclareScreens.MAIN;
    }
}

En el manifiesto de MainActivity, puede especificar la orientación vertical. En principio, la biblioteca admite la rotación de pantalla, pero en esta versión para pantallas del tipo de actividad (), además del inicio, también se prescribe orientación vertical.

Cree la clase MyApp:

public class MyApp extends Application {
    private static MyApp instance;
    private Context context;

    public static MyApp getInstance() {
        if (instance == null) {
            instance = new MyApp();
        }
        return instance;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        instance = this;
        context = getApplicationContext();

        DeclareParam.build(context)
                .setAppParams(new MyParams())
                .setDeclareScreens(new MyDeclareScreens());
    }
}

Este es un singleton regular en el método onCreate en el que se configuran MyParams y MyDeclareScreens.
Recuerde describir MyApp en el manifiesto.

Para finalizar, cree la clase Api:

public class Api {
    public static final String CATALOG = "depro/cron/catalog",
            NEWS_PROD = "depro/cron/news_prod",
            PRODUCT_LIST = "depro/cron/product_list",
            PRODUCT_ID = "depro/cron/product_id",
            ANALOG_ID_PRODUCT = "depro/cron/product_analog",
            CHARACT_ID_PRODUCT = "depro/cron/product_charact",
            FITNESS = "depro/cron/fitness",
            CATALOG_EX = "depro/cron/catalog_ex";
}

5. Ahora puede ejecutar la aplicación para su ejecución.

Si todo se ingresa correctamente, cuando se inicie la aplicación, se mostrarán todas las pantallas especificadas (y funcionarán).

Como vemos, solo tenemos cinco clases simples para toda la aplicación. Y cuatro de ellos son auxiliares. Sus contenidos son independientes del número de pantallas. La descripción, generalmente no trivial, de las pantallas también ocupa un poco de espacio (menos de 80 líneas). Ahora mostramos que la descripción no solo no es grande, sino que tampoco es complicada. Para hacer esto, describimos el funcionamiento de los componentes de la biblioteca usados.

Como se indica en el primer artículo, las pantallas en la biblioteca pueden ser actividad o fragmento, lo que establece el nombre de pantalla (línea) y el diseño. Los enlaces a las pantallas se hacen por sus nombres. La elección de qué tipo de pantalla utilizar se lleva a cabo de la manera habitual.

Pantalla PRINCIPAL

Entonces, desde el diseño se puede ver que la pantalla de inicio tiene un menú lateral y un contenedor para fragmentos. En el marcado de R.layout.activity_main, se establece un DrawerLayout estándar. Por lo tanto, en la descripción de la pantalla PRINCIPAL, especificamos el componente del cajón, al que pasamos la identificación del propio DrawerLayout y sus contenedores para la barra lateral y los fragmentos. También indicamos el nombre de la pantalla (CAJÓN), que se mostrará en la barra lateral. El navegador finishDialog indica que antes de salir de la aplicación, debe emitir un diálogo de confirmación.

Pantalla de CAJÓN

Contiene solo un componente de menú para el que se especifica un modelo de tipo Menú en el modelo, y en la vista de identificación de un elemento de marcado de tipo RecyclerView, que mostrará el menú. El menú en sí indica que el elemento de menú "CATÁLOGO" se mostrará en el contenedor de fragmentos cuando se inicie el menú.

Pantalla CATALOGO

Contiene una Nueva Lista horizontal. Cuando recibe datos del servidor, su modelo usa paginación y muestra el progreso en R.id.progr. Si su Internet es rápido, es posible que no note la aparición de un panel con progreso. Si desea verlo, puede cambiar a un Internet más lento o cambiar el color de fondo de R.id.progr. En este caso, al menos lo verá parpadear. Para la paginación, se utilizan los parámetros especificados en AppParams de manera predeterminada. La vista se da R.id.recycler_news con RecyclerView y diseño para elementos. Cuando hace clic en cualquier elemento de la lista, se inicia la pantalla PRODUCT_DESCRIPT.

La lista del directorio es un menú desplegable. El nivel de divulgación se define en el campo "nivel expandido". Si no se transmite con los datos de origen, la biblioteca misma se ocupará de esto. El mismo parámetro establece qué diseño de la lista usar en cada nivel de divulgación. El hecho de que la lista sea desplegable sirve para la funcionalidad expandida (...). El modelo se configurará en el proveedor para recibir datos del servidor para los siguientes niveles. El modelo especifica la dirección de la solicitud Api.CATALOG_EX y el nombre del parámetro de solicitud "catalog_id". En expandido, R.id.expand también se indica: un elemento de marcado en los elementos haciendo clic en el cual se expandirá la lista y la identificación del elemento que se rotará animadamente 180 grados al expandirse, cerrando la lista. Y finalmente, el navegador indicaque cuando hace clic en el elemento de la lista, se abrirá la pantalla PRODUCT_LIST.

El navegador, que se refiere a toda la pantalla, indica que cuando hace clic en el elemento R.id.back, se abrirá un panel lateral.

Pantalla PRODUCT_LIST

Al especificar una pantalla, se indica que el valor actual del parámetro nombre_categoría se mostrará en el encabezado y la animación de salida será de derecha a izquierda (AS.RL). La descripción de la lista ya nos es familiar. Lo único para la presentación es el VisorManager, que controla la visibilidad de los elementos de marcado según los valores de los datos correspondientes. El navegador relacionado con toda la pantalla indica que cuando hace clic en el elemento R.id.back, volverá a la pantalla anterior.

Pantalla PRODUCT_DESCRIPT

Nuevo para nosotros es un componente de tipo PAGER_F. Está asociado con un ViewPager normal. Se le muestra una lista de pantallas (fragmentos) para mostrar, y TabLayout (setTab) está conectado.

La pantalla PRODUCT_DESCRIPT aparece en la pestaña DESCRIPCIÓN.

Un componente del tipo PANEL muestra datos obtenidos por el modelo. La lista "Analógicos" muestra una lista de productos similares, si los hay, o usando la funcionalidad noDataView (), el mensaje "Faltan los analógicos".

Pantalla CARACTERÍSTICA

Muestra una lista de características del producto.

Pantalla FITNESS

Un componente de tipo SPINNER muestra una lista de clubes con la opción de seleccionar uno. La lista de palos se establece en el modelo como una cadena json.

La lista de categorías de clases es normal. La función eventFrom (...) indica que al cambiar el componente asociado con R.id.spinner (en nuestro caso, el spiner), es necesario actualizar los datos.
El código de la aplicación discutida en el artículo se puede ver en Github .

El artículo es solo orientativo. Más completamente con las capacidades de la biblioteca DePro está disponible en la documentación .

All Articles