Programmation déclarative des applications client-serveur sur Android. Partie 2

L' article précédent a brièvement montré les avantages de la programmation déclarative des applications client-serveur pour Android par rapport à l'impératif.

Nous allons maintenant écrire un petit projet, mais suffisant, pour évaluer l'efficacité de la bibliothèque DePro. Il fait partie d'un des exemples pédagogiques de la bibliothèque . La conception de tous les écrans que nous décrivons est illustrée dans les figures suivantes:

image image image
Écran TIROIR Écran CATALOGUE Écran PRODUCT_LIST

image image image
 CATALOG_           DESCRIPT          CHARACTERISTIC

image image image
 FITNESS           FITNESS_          


A partir de ces écrans, leur fonctionnalité est généralement comprise. Le menu latéral contient deux éléments «Catalogue» et «Fitness». Lorsque vous sélectionnez un élément, l'écran correspondant s'affiche. L'écran CATALOGUE contient une liste horizontale "Actualités". Lorsque vous cliquez sur un «nouveau produit», un écran avec une description de ce produit s'affiche. La description se compose de deux onglets: «Description» et «Caractéristiques». Également sur l'écran CATALOGUE, il y a une liste déroulante "Catalogue". Lorsque vous cliquez sur la flèche vers le bas, le répertoire s'ouvre (se ferme). Lorsque vous cliquez sur toute la ligne, l'écran PRODUCT_LIST s'affiche avec une liste de produits pour l'article sélectionné dans le catalogue.

Lorsque vous cliquez sur l'élément «Fitness», l'écran FITNESS avec une liste de services s'affiche. La liste dépend du club sélectionné dans le spinner. Lorsque vous essayez de quitter l'application, une boîte de dialogue d'avertissement s'affiche.

Le serveur transfère les données au format json vers l'application. La structure des données pour chaque écran est décrite ci-dessous. Dans les applications écrites dans DePro à partir de l'API, seule l'URL est utilisée, la structure de données n'est nécessaire que pour définir correctement la vue nom (id), car la liaison est effectuée par nom. Il convient de noter que les données sont une tranche incomplète de données réelles. Par conséquent, il peut ne pas y avoir de connexion sur les noms, les images. En particulier, les images de produits totalisent 20 pièces. Par conséquent, la même image peut être présente dans de nombreux produits.

API pour des exemples d'écrans
Écran CATALOGUE pour la liste horizontale (Nouveau) URL depro / cron / news, méthode GET. Lors de la lecture des données sur les nouveaux produits, la pagination est utilisée. Les paramètres de pagination sont passés dans l'en-tête.

Réponse

    {
        "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":","
    },
    . . .
]


Considérez maintenant nos étapes pour rédiger une demande.

1. Dans le studio, nous allons créer un nouveau projet. Le nom peut être défini à votre discrétion.

2. Déploiement des ressources. Comme mentionné dans le premier article, les fichiers de ressources (XML) sont utilisés de manière conventionnelle. Par conséquent, afin de ne pas perdre de temps sur des choses connues, nous téléchargeons simplement toutes les ressources nécessaires .

Décompressez le fichier res_example.zip résultant. Dans le studio, supprimez tout le contenu du dossier res.

Transférez le contenu du dossier res décompressé dans le dossier res du projet. Après cela, en tenant compte des fonctionnalités d'Android Studio, vous devrez peut-être effacer le projet et / ou exécuter la commande Caches / Redémarrage non valide.

3. Connexion de la bibliothèque

Dans la section des dépendances du fichier build.gradle du module, vous devez spécifier:

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

Dans la section android du fichier build.gradle du module, vous devez spécifier:

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

Lorsque vous choisissez l'attribut minSdkVersion, vous devez considérer que la bibliothèque prend en charge minSdkVersion = 17. Après avoir modifié build.gradle, vous devez synchroniser le projet.

4. Création des classes nécessaires (fichiers). Lorsque vous travaillez avec la bibliothèque, seules 4 classes sont utilisées: MyDeclareScreens - tous les écrans sont décrits; MyParams - définissez les paramètres nécessaires pour l'application; MyApp - la bibliothèque DePro est lancée; MainActivity - activité de démarrage. Vous pouvez utiliser vos propres noms de classe.

Créez la classe MyDeclareScreens.java. Son contenu est le suivant:

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);
}

Nous décrirons plus loin toutes les constructions DePro utilisées. Pour connecter l'importation, utilisez les touches alt + entrée. Le rouge sera également mis en surbrillance Classe Api dans laquelle les adresses pour toutes les demandes sont définies. Son contenu sera communiqué ultérieurement.

Créez la classe MyParams.java. Dans la plupart des cas, les paramètres par défaut sont suffisants. Dans notre cas, nous ne définirons que l'URL de base.

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

Modifiez le contenu créé par le studio de la classe MainActivity comme suit:

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

Dans le manifeste de MainActivity, vous pouvez spécifier l'orientation portrait. En principe, la bibliothèque prend en charge la rotation d'écran, mais dans cette version pour les écrans de type activité (), outre le début, l'orientation portrait est également prescrite.

Créez la classe 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());
    }
}

Il s'agit d'un singleton régulier dans la méthode onCreate dont MyParams et MyDeclareScreens sont définis.
N'oubliez pas de décrire MyApp dans le manifeste.

Pour terminer, créez la classe 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. Vous pouvez maintenant exécuter l'application pour exécution.

Si tout est entré correctement, alors au démarrage de l'application, tous les écrans spécifiés seront affichés (et fonctionneront).

Comme nous le voyons, nous n'avons que cinq classes simples pour l'ensemble de l'application. Et quatre d'entre eux sont auxiliaires. Leur contenu est indépendant du nombre d'écrans. La description, généralement non banale, des écrans prend également un peu de place (moins de 80 lignes). Maintenant, nous montrons que la description est non seulement pas grande, mais aussi pas compliquée. Pour ce faire, nous décrivons le fonctionnement des composants de bibliothèque utilisés.

Comme indiqué dans le premier article, les écrans de la bibliothèque peuvent être soit des activités, soit des fragments, qui définissent le nom d'écran (ligne) et la disposition. Les liens vers les écrans se font par leur nom. Le choix du type d'écran à utiliser s'effectue de la manière habituelle.

Écran MAIN

Ainsi, d'après la conception, on peut voir que l'écran de démarrage a un menu latéral et un conteneur pour les fragments. Dans le balisage de R.layout.activity_main, un DrawerLayout standard est défini. Par conséquent, dans la description de l'écran MAIN, nous spécifions le composant de tiroir, auquel nous transmettons l'id du DrawerLayout lui-même et ses conteneurs pour la barre latérale et les fragments. Nous indiquons également le nom de l'écran (TIROIR), qui sera affiché dans la barre latérale. Le navigateur finishDialog indique qu'avant de quitter l'application, vous devez ouvrir une boîte de dialogue de confirmation.

Écran TIROIR

Contient un seul composant de menu pour lequel un modèle de type Menu est spécifié dans le modèle et dans la vue id d'un élément de balisage de type RecyclerView, qui affichera le menu. Le menu lui-même indique que l'élément de menu «CATALOGUE» sera affiché dans le conteneur de fragments au démarrage du menu.

Écran CATALOG

Contient une nouvelle liste horizontale. Lors de la réception de données du serveur, son modèle utilise la pagination et affiche la progression dans R.id.progr. Si votre Internet est rapide, vous ne remarquerez peut-être pas l'apparition d'un panneau en cours de progression. Si vous voulez le regarder, vous pouvez passer à un Internet plus lent ou changer la couleur d'arrière-plan de R.id.progr. Dans ce cas, vous le verrez au moins clignoter. Pour la pagination, les paramètres spécifiés dans AppParams par défaut sont utilisés. La vue est donnée à R.id.recycler_news avec un RecyclerView et une disposition pour les articles. Lorsque vous cliquez sur un élément de la liste, l'écran PRODUCT_DESCRIPT démarre.

La liste des répertoires est une liste déroulante. Le niveau de divulgation est défini dans le champ "extendedLevel". S'il n'est pas transmis avec les données source, la bibliothèque elle-même s'en occupera. Le même paramètre définit la disposition de la liste à utiliser à chaque niveau de divulgation. Le fait que la liste soit déroulante sert à la fonctionnalité étendue (...). Le modèle sera défini dans le cator pour recevoir les données du serveur pour les niveaux suivants. Le modèle spécifie l'adresse de la requête Api.CATALOG_EX et le nom du paramètre de requête «catalog_id». Dans développé, R.id.expand est également indiqué - un élément de balisage dans les éléments en cliquant sur lequel la liste sera développée et l'ID de l'élément qui sera animé d'une rotation de 180 degrés lors du développement - fermant la liste. Et enfin, le navigateur indiqueque lorsque vous cliquez sur l'élément de liste, l'écran PRODUCT_LIST sera appelé.

Le navigateur, qui fait référence à tout l'écran, indique que lorsque vous cliquez sur l'élément R.id.back, un panneau latéral s'ouvre.

Écran PRODUCT_LIST

Lorsque vous spécifiez un écran, il est indiqué que la valeur actuelle du paramètre catalog_name sera affichée dans l'en-tête et l'animation de sortie sera de droite à gauche (AS.RL). La description de la liste nous est déjà familière. La seule chose pour la présentation est le visibilitéManager, qui contrôle la visibilité des éléments de balisage en fonction des valeurs des données correspondantes. Le navigateur lié à l'ensemble de l'écran indique que lorsque vous cliquez sur l'élément R.id.back, vous revenez à l'écran précédent.

Écran PRODUCT_DESCRIPT

Nouveau pour nous est un composant de type PAGER_F. Il est associé à un ViewPager standard. On lui montre une liste d'écrans (fragments) à afficher, et TabLayout (setTab) est connecté.

L'écran PRODUCT_DESCRIPT apparaît dans l'onglet DESCRIPTION.

Un composant de type PANEL affiche les données obtenues par le modèle. La liste «Analogues» affiche une liste de produits similaires, le cas échéant, ou en utilisant la fonctionnalité noDataView (), le message «Analogues manquants».

Écran CARACTÉRISTIQUE

Affiche une liste des fonctionnalités du produit.

Écran FITNESS

Un composant de type SPINNER affiche une liste de clubs avec l'option d'en sélectionner un. La liste des clubs est définie dans le modèle sous la forme d'une chaîne json.

La liste des catégories de classes est normale. La fonction eventFrom (...) indique que lors du changement du composant associé à R.id.spinner (dans notre cas, le spiner), il est nécessaire de mettre à jour les données.
Le code de l'application discuté dans l'article peut être consulté sur Github .

L'article est uniquement à titre indicatif. Plus complètement avec les capacités de la bibliothèque DePro est disponible dans la documentation .

All Articles