Deklarative Programmierung von Client-Server-Anwendungen auf Android. Teil 2

Der vorherige Artikel zeigte kurz die Vorteile der deklarativen Programmierung von Client-Server-Anwendungen für Android im Vergleich zu Imperativ.

Jetzt werden wir ein kleines, aber ausreichendes Projekt schreiben, um die Wirksamkeit der DePro-Bibliothek zu bewerten. Es ist Teil eines der pädagogischen Beispiele der Bibliothek . Das Design aller von uns beschriebenen Bildschirme ist in den folgenden Abbildungen dargestellt:

Bild Bild Bild
DRAWER-Bildschirm CATALOG-Bildschirm PRODUCT_LIST-Bildschirm

Bild Bild Bild
 CATALOG_           DESCRIPT          CHARACTERISTIC

Bild Bild Bild
 FITNESS           FITNESS_          


Aus diesen Bildschirmen wird ihre Funktionalität allgemein verstanden. Das Seitenmenü enthält zwei Elemente: "Verzeichnis" und "Fitness". Wenn Sie ein Element auswählen, wird der entsprechende Bildschirm angezeigt. Der KATALOG-Bildschirm enthält eine horizontale "Nachrichten" -Liste. Wenn Sie auf ein „neues Produkt“ klicken, wird ein Bildschirm mit einer Beschreibung dieses Produkts angezeigt. Die Beschreibung besteht aus zwei Registerkarten: "Beschreibung" und "Merkmale". Ebenfalls auf dem KATALOG-Bildschirm befindet sich eine Dropdown-Liste "Katalog". Wenn Sie auf den Abwärtspfeil klicken, wird das Verzeichnis geöffnet (geschlossen). Wenn Sie auf die gesamte Zeile klicken, wird der Bildschirm PRODUCT_LIST mit einer Liste der Produkte für den ausgewählten Artikel im Katalog angezeigt.

Wenn Sie auf den Punkt „Fitness“ klicken, wird der Bildschirm FITNESS mit einer Liste der Dienste angezeigt. Die Liste hängt von dem im Spinner ausgewählten Verein ab. Wenn Sie versuchen, die Anwendung zu beenden, wird ein Warndialogfeld angezeigt.

Der Server überträgt Daten im JSON-Format an die Anwendung. Die Datenstruktur für jeden Bildschirm wird unten beschrieben. In Anwendungen, die in DePro über die API geschrieben wurden, wird nur die URL verwendet. Die Datenstruktur wird nur benötigt, um die Ansicht mit dem Namen (ID) korrekt festzulegen, da die Bindung nach Namen ausgeführt wird. Es ist erwähnenswert, dass Daten ein unvollständiger Teil der realen Daten sind. Daher gibt es möglicherweise keine Verbindungen zu Namen und Bildern. Insbesondere Produktbilder insgesamt 20 Stück. Daher kann das gleiche Bild in vielen Produkten sein.

API zum Beispiel Bildschirme
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":","
    },
    . . .
]


Betrachten Sie nun unsere Schritte zum Schreiben einer Bewerbung.

1. Im Studio erstellen wir ein neues Projekt. Der Name kann nach eigenem Ermessen festgelegt werden.

2. Bereitstellung von Ressourcen. Wie im ersten Artikel erwähnt, werden Ressourcendateien (XML) herkömmlich verwendet. Um keine Zeit mit bekannten Dingen zu verschwenden, laden wir einfach alle erforderlichen Ressourcen herunter .

Entpacken Sie die resultierende Datei res_example.zip. Löschen Sie im Studio den gesamten Inhalt des res-Ordners.

Übertragen Sie den Inhalt des entpackten res-Ordners in den res-Ordner des Projekts. Danach müssen Sie unter Berücksichtigung der Funktionen von Android Studio möglicherweise das Projekt löschen und / oder den Befehl Ungültige Caches / Neustart ausführen.

3. Verbindung der Bibliothek

Im Abschnitt Abhängigkeiten der Datei build.gradle des Moduls müssen Sie Folgendes angeben:

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

Im Android-Bereich der build.gradle-Datei des Moduls müssen Sie Folgendes angeben:

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

Bei der Auswahl des Attributs minSdkVersion müssen Sie berücksichtigen, dass die Bibliothek minSdkVersion = 17 unterstützt. Nach dem Ändern von build.gradle müssen Sie das Projekt synchronisieren.

4. Erstellung der notwendigen Klassen (Dateien). Bei der Arbeit mit der Bibliothek werden nur 4 Klassen verwendet: MyDeclareScreens - alle Bildschirme werden beschrieben; MyParams - Legen Sie die erforderlichen Parameter für die Anwendung fest. MyApp - Die DePro-Bibliothek wird initiiert. MainActivity - Startaktivität. Sie können Ihre eigenen Klassennamen verwenden.

Erstellen Sie die Klasse MyDeclareScreens.java. Sein Inhalt ist wie folgt:

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

Wir werden später alle verwendeten DePro-Konstruktionen beschreiben. Verwenden Sie zum Verbinden des Imports die Tastenkombination Alt + Eingabetaste. Rot wird auch die API-Klasse hervorgehoben, in der die Adressen für alle Anforderungen festgelegt sind. Der Inhalt wird später bekannt gegeben.

Erstellen Sie die Klasse MyParams.java. In den meisten Fällen sind die Standardeinstellungen ausreichend. In unserem Fall legen wir nur die Basis-URL fest.

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

Ändern Sie den vom Studio der MainActivity-Klasse erstellten Inhalt wie folgt:

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

Im Manifest für MainActivity können Sie die Hochformatausrichtung festlegen. Grundsätzlich unterstützt die Bibliothek die Bildschirmrotation, aber in dieser Version ist für Bildschirme vom Typ activity () neben dem Start auch die Hochformatausrichtung vorgeschrieben.

Erstellen Sie die MyApp-Klasse:

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

Dies ist ein regulärer Singleton in der onCreate-Methode, für die MyParams und MyDeclareScreens festgelegt sind.
Denken Sie daran, MyApp im Manifest zu beschreiben.

Erstellen Sie zum Abschluss die Api-Klasse:

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. Jetzt können Sie die Anwendung zur Ausführung ausführen.

Wenn alles korrekt eingegeben wurde, werden beim Start der Anwendung alle angegebenen Bildschirme angezeigt (und funktionieren).

Wie wir sehen, haben wir nur fünf einfache Klassen für die gesamte Anwendung. Und vier von ihnen sind Hilfskräfte. Ihr Inhalt ist unabhängig von der Anzahl der Bildschirme. Die im Allgemeinen nicht triviale Beschreibung der Bildschirme nimmt ebenfalls etwas Platz ein (weniger als 80 Zeilen). Nun zeigen wir, dass die Beschreibung nicht nur nicht groß, sondern auch nicht kompliziert ist. Dazu beschreiben wir den Betrieb der verwendeten Bibliothekskomponenten.

Wie im ersten Artikel angegeben, können Bildschirme in der Bibliothek entweder Aktivität oder Fragment sein, wodurch der Bildschirmname (Zeile) und das Layout festgelegt werden. Links zu Bildschirmen werden anhand ihrer Namen erstellt. Die Auswahl des zu verwendenden Bildschirmtyps erfolgt auf übliche Weise.

Hauptbildschirm

Aus dem Design geht hervor, dass der Startbildschirm ein Seitenmenü und einen Container für Fragmente enthält. Im Markup von R.layout.activity_main wird ein Standard-DrawerLayout festgelegt. Daher geben wir in der Beschreibung des MAIN-Bildschirms die Schubladenkomponente an, an die wir die ID des DrawerLayout selbst und seiner Container für die Seitenleiste und die Fragmente übergeben. Wir geben auch den Namen des Bildschirms (SCHUBLADE) an, der in der Seitenleiste angezeigt wird. Der finishDialog-Navigator gibt an, dass Sie vor dem Beenden der Anwendung einen Bestätigungsdialog ausgeben müssen.

DRAWER-Bildschirm

Enthält nur eine Menükomponente mit einer im Modell angegebenen Menütypvariablen und einem Markup-Element vom Typ RecyclerView in der ID-Ansicht, das das Menü anzeigt. Das Menü selbst zeigt an, dass der Menüpunkt „KATALOG“ beim Starten des Menüs im Fragmentcontainer angezeigt wird.

KATALOG-Bildschirm

Enthält eine horizontale neue Liste. Beim Empfang von Daten vom Server verwendet das Modell die Paginierung und zeigt den Fortschritt in R.id.progr an. Wenn Ihr Internet schnell ist, bemerken Sie möglicherweise nicht, dass ein Panel mit Fortschritt angezeigt wird. Wenn Sie es sehen möchten, können Sie zu einem langsameren Internet wechseln oder die Hintergrundfarbe von R.id.progr ändern. In diesem Fall wird es zumindest blinken. Für die Paginierung werden standardmäßig die in AppParams angegebenen Parameter verwendet. Die Ansicht erhält R.id.recycler_news mit einer RecyclerView und einem Layout für Elemente. Wenn Sie auf ein Element in der Liste klicken, wird der Bildschirm PRODUCT_DESCRIPT gestartet.

Die Verzeichnisliste ist eine Dropdown-Liste. Die Offenlegungsstufe wird im Feld "expandLevel" definiert. Wenn es nicht mit den Quelldaten übertragen wird, wird die Bibliothek selbst damit umgehen. Der gleiche Parameter legt fest, welches Layout aus der Liste auf jeder Offenlegungsebene verwendet werden soll. Die Tatsache, dass die Liste ein Dropdown-Menü ist, dient der erweiterten (...) Funktionalität. Das Modell wird im Cator so eingestellt, dass Daten vom Server für die nächsten Ebenen empfangen werden. Das Modell gibt die Adresse der Api.CATALOG_EX-Anforderung und den Namen des Anforderungsparameters "catalog_id" an. In erweitert wird auch R.id.expand angezeigt - ein Markup-Element in Elementen, indem Sie darauf klicken, auf das die Liste erweitert wird, und die ID des Elements, das beim Erweitern animiert um 180 Grad gedreht wird - Schließen der Liste. Und schließlich zeigt der Navigator anWenn Sie auf das Listenelement klicken, wird der Bildschirm PRODUCT_LIST aufgerufen.

Der Navigator, der sich auf den gesamten Bildschirm bezieht, zeigt an, dass beim Klicken auf das Element R.id.back ein Seitenbereich geöffnet wird.

Bildschirm PRODUCT_LIST

Wenn Sie einen Bildschirm angeben, wird angezeigt, dass der aktuelle Wert des Parameters catalog_name in der Kopfzeile angezeigt wird und die Ausgabeanimation von rechts nach links (AS.RL) erfolgt. Die Listenbeschreibung ist uns bereits bekannt. Das einzige, was für die Präsentation vorgesehen ist, ist der Sichtbarkeitsmanager, der die Sichtbarkeit der Markup-Elemente in Abhängigkeit von den Werten der entsprechenden Daten steuert. Der Navigator für den gesamten Bildschirm zeigt an, dass Sie beim Klicken auf das Element R.id.back zum vorherigen Bildschirm zurückkehren.

Bildschirm PRODUCT_DESCRIPT

Neu für uns ist eine Komponente vom Typ PAGER_F. Es ist einem regulären ViewPager zugeordnet. Ihm wird eine Liste der anzuzeigenden Bildschirme (Fragmente) angezeigt, und TabLayout (setTab) ist verbunden.

Der Bildschirm PRODUCT_DESCRIPT wird auf der Registerkarte BESCHREIBUNG angezeigt .

Eine Komponente vom Typ PANEL zeigt die vom Modell erhaltenen Daten an. In der Liste „Analoga“ wird eine Liste ähnlicher Produkte angezeigt, sofern vorhanden. Wenn Sie die Funktion noDataView () verwenden, wird die Meldung „Analoga fehlen“ angezeigt.

Bildschirm CHARACTERISTIC

Zeigt eine Liste der Produktfunktionen an.

Bildschirm

FITNESS Eine Komponente vom Typ SPINNER zeigt eine Liste der Clubs mit der Option zur Auswahl eines Clubs an. Die Liste der Clubs wird im Modell als JSON-Zeichenfolge festgelegt.

Die Liste der Klassenkategorien ist normal. Die Funktion eventFrom (...) gibt an, dass beim Ändern der mit R.id.spinner verknüpften Komponente (in unserem Fall des Spiners) die Daten aktualisiert werden müssen.
Der im Artikel beschriebene Code der Anwendung kann auf Github angezeigt werden .

Der Artikel dient nur zur Orientierung. Weitere Informationen zu den Funktionen der DePro-Bibliothek finden Sie in der Dokumentation .

All Articles