Programação declarativa de aplicativos cliente-servidor no android. Parte 2

O artigo anterior mostrou brevemente as vantagens da programação declarativa de aplicativos cliente-servidor para android em comparação com o imperativo.

Agora, escreveremos um projeto pequeno, mas suficiente, para avaliar a eficácia da biblioteca DePro. Faz parte de um dos exemplos educacionais da biblioteca . O design de todas as telas que descrevemos é mostrado nas seguintes figuras:

imagem imagem imagem
Tela DRAWER Tela CATALOG Tela PRODUCT_LIST

imagem imagem imagem
 CATALOG_           DESCRIPT          CHARACTERISTIC

imagem imagem imagem
 FITNESS           FITNESS_          


Nessas telas, sua funcionalidade é geralmente entendida. O menu lateral contém dois itens: "Catálogo" e "Fitness". Quando você seleciona um item, a tela correspondente é exibida. A tela CATALOG contém uma lista horizontal "Notícias". Quando você clica em qualquer "novo produto", uma tela com uma descrição desse produto é exibida. Descrição consiste em duas guias: "Descrição" e "Características". Também na tela CATÁLOGO, há uma lista suspensa "Catálogo". Quando você clica na seta para baixo, o diretório é aberto (fecha). Quando você clica na linha inteira, a tela PRODUCT_LIST é exibida com uma lista de produtos para o item selecionado no catálogo.

Quando você clica no item "Fitness", a tela FITNESS com uma lista de serviços é exibida. A lista depende do clube selecionado no botão giratório. Quando você tenta sair do aplicativo, uma caixa de diálogo de aviso é exibida.

O servidor transfere dados no formato json para o aplicativo. A estrutura de dados para cada tela é descrita abaixo. Nos aplicativos gravados no DePro a partir da API, apenas a URL é usada, a estrutura de dados é necessária apenas para definir corretamente a exibição de nome (id), porque a ligação é realizada por nome. Vale a pena notar que os dados são uma fatia incompleta de dados reais. Portanto, pode não haver conexões em nomes, imagens. Em particular, as imagens do produto totalizam 20 peças. Portanto, a mesma imagem pode estar em muitos produtos.

API por exemplo telas
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":","
    },
    . . .
]


Agora considere nossas etapas para escrever um aplicativo.

1. No estúdio, criaremos um novo projeto. O nome pode ser definido a seu critério.

2. Implantação de recursos. Conforme mencionado no primeiro artigo, os arquivos de recurso (XML) são usados ​​convencionalmente. Portanto, para não perder tempo com coisas conhecidas, basta baixar todos os recursos necessários .

Descompacte o arquivo res_example.zip resultante. No estúdio, exclua todo o conteúdo da pasta res.

Transfira o conteúdo da pasta res descompactada para a pasta res do projeto. Depois disso, levando em conta os recursos do Android Studio, talvez seja necessário limpar o projeto e / ou executar o comando Caches / Reiniciar Inválidos.

3. Conexão da biblioteca

Na seção dependências do arquivo build.gradle do módulo, você precisa especificar:

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

Na seção android do arquivo build.gradle do módulo, você precisa especificar:

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

Ao escolher o atributo minSdkVersion, é necessário considerar que a biblioteca suporta minSdkVersion = 17. Após alterar o build.gradle, você precisa sincronizar o projeto.

4. Criação de classes necessárias (arquivos). Ao trabalhar com a biblioteca, apenas 4 classes são usadas: MyDeclareScreens - todas as telas são descritas; MyParams - defina os parâmetros necessários para a aplicação; MyApp - a biblioteca DePro é iniciada; MainActivity - atividade inicial. Você pode usar seus próprios nomes de classe.

Crie a classe MyDeclareScreens.java. Seu conteúdo é o seguinte:

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

Mais adiante, descreveremos todas as construções de DePro usadas. Para conectar a importação, use as teclas alt + enter. O vermelho também será destacado na classe Api na qual os endereços para todas as solicitações estão definidos. Seu conteúdo será dado posteriormente.

Crie a classe MyParams.java. Na maioria dos casos, as configurações padrão são suficientes. No nosso caso, definiremos apenas o URL base.

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

Altere o conteúdo criado pelo estúdio da classe MainActivity para o seguinte:

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

No manifesto de MainActivity, você pode especificar a orientação de retrato. Em princípio, a biblioteca suporta a rotação da tela, mas nesta versão para telas do tipo activity (), além do início, a orientação de retrato também é prescrita.

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

Este é um singleton regular no método onCreate no qual MyParams e MyDeclareScreens estão definidos.
Lembre-se de descrever o MyApp no ​​manifesto.

Para finalizar, crie a 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. Agora você pode executar o aplicativo para execução.

Se tudo for inserido corretamente, quando o aplicativo iniciar, todas as telas especificadas serão exibidas (e funcionarão).

Como vemos, temos apenas cinco classes simples para todo o aplicativo. E quatro deles são auxiliares. Seu conteúdo é independente do número de telas. A descrição, geralmente não trivial, das telas também ocupa um pouco de espaço (menos de 80 linhas). Agora mostramos que a descrição não é apenas não grande, mas também não é complicada. Para fazer isso, descrevemos a operação dos componentes de biblioteca usados.

Conforme indicado no primeiro artigo, as telas da biblioteca podem ser atividade ou fragmento, o que define o nome da tela (linha) e o layout. Links para telas são criados por seus nomes. A escolha de qual tipo de tela usar é realizada da maneira usual.

Tela PRINCIPAL

Assim, a partir do design, pode-se ver que a tela inicial possui um menu lateral e um contêiner para fragmentos. Na marcação de R.layout.activity_main, um DrawerLayout padrão é definido. Portanto, na descrição da tela PRINCIPAL, especificamos o componente da gaveta, para o qual passamos o ID do próprio DrawerLayout e seus contêineres para a barra lateral e os fragmentos. Também indicamos o nome da tela (DRAWER), que será exibida na barra lateral. O navegador finishDialog indica que, antes de sair do aplicativo, é necessário emitir um diálogo de confirmação.

Tela DRAWER

Contém apenas um componente de menu para o qual um modelo do tipo Menu é especificado no modelo e na visualização id de um elemento de marcação do tipo RecyclerView, que exibirá o menu. O próprio menu indica que o item de menu “CATALOG” será exibido no contêiner de fragmentos quando o menu for iniciado.

Tela CATALOG

Contém uma nova lista horizontal. Ao receber dados do servidor, seu modelo usa paginação e exibe o progresso em R.id.progr. Se sua Internet for rápida, você poderá não notar a aparência de um painel com progresso. Se você quiser assistir, poderá mudar para uma Internet mais lenta ou alterar a cor de fundo de R.id.progr. Nesse caso, você verá pelo menos piscando. Para paginação, os parâmetros especificados no AppParams por padrão são usados. A visualização recebe R.id.recycler_news com um RecyclerView e layout para itens. Quando você clica em qualquer item da lista, a tela PRODUCT_DESCRIPT é iniciada.

A lista de diretórios é uma lista suspensa. O nível de divulgação é definido no campo "extendedLevel". Se não for transmitido com os dados de origem, a própria biblioteca lidará com isso. O mesmo parâmetro define qual layout da lista usar em cada nível de divulgação. O fato de a lista estar suspensa serve à funcionalidade expandida (...) O modelo será definido no cator para receber dados do servidor para os próximos níveis. O modelo especifica o endereço da solicitação Api.CATALOG_EX e o nome do parâmetro de solicitação "catalog_id". Em expandido, R.id.expand também é indicado - um elemento de marcação nos itens clicando no qual a lista será expandida e o ID do elemento que será rotativamente animado em 180 graus ao expandir - fechando a lista. E, finalmente, o navegador indicaquando você clicar no item da lista, a tela do PRODUCT_LIST será chamada.

O navegador, que se refere à tela inteira, indica que quando você clica no elemento R.id.back, um painel lateral é aberto.

Tela PRODUCT_LIST

Ao especificar uma tela, é indicado que o valor atual do parâmetro catalog_name será mostrado no cabeçalho e a animação de saída será da direita para a esquerda (AS.RL). A descrição da lista já é familiar para nós. A única coisa para a apresentação é o visibleManager, que controla a visibilidade dos elementos de marcação, dependendo dos valores dos dados correspondentes. O navegador relacionado à tela inteira indica que, quando você clica no elemento R.id.back, você volta à tela anterior.

Tela PRODUCT_DESCRIPT

Novo para nós é um componente do tipo PAGER_F. Está associado a um ViewPager comum. Ele mostra uma lista de telas (fragmentos) a serem exibidas e o TabLayout (setTab) está conectado.

A tela PRODUCT_DESCRIPT aparece na guia DESCRIPTION.

Um componente do tipo PANEL exibe dados obtidos pelo modelo. A lista "Analógicos" exibe uma lista de produtos similares, se houver, ou usando a funcionalidade noDataView (), a mensagem "Faltam analógicos".

Tela CARACTERÍSTICA

Exibe uma lista de recursos do produto.

Tela FITNESS

Um componente do tipo SPINNER exibe uma lista de clubes com a opção de selecionar um. A lista de clubes é definida no modelo como uma string json.

A lista de categorias de classes é normal. A funcionalidade eventFrom (...) indica que, ao alterar o componente associado ao R.id.spinner (no nosso caso, o spiner), é necessário atualizar os dados.
O código do aplicativo discutido no artigo pode ser visualizado no Github .

O artigo é apenas para orientação. Mais detalhadamente com os recursos da biblioteca DePro está disponível na documentação .

All Articles