Введение
Общее описание
Структура приложения
Уроки
Урок 1
Создание приложения, экраны, меню, список, локализация
Урок 2
Просмотр видео (youtube), панель ввода
Урок 3
Карты, PlusMinus, пользовательская обработка экранов
Урок 4
Завершение разработки, закрепление материала
Урок 5
Интро, авторизация, фото
Урок 6
Боковое меню, горизонтальный список, пагинация, раскрывающийся список, баркод-сканер, распознавание речи, поиск, листание страниц
Урок 7
Работа с базами данных SQLite, свайп, componentDateDiapason
Урок 8
Спиннер, календарь, пиккер и события
Урок 9
Работа с пуш сообщениями, BadgeTextView
Урок 10
Анимация, кастомные компоненты
Описание библиотеки
Приложения

Урок 1

На протяжении четырех уроков мы напишем приложение для склада (магазина) с использование библиотеки декларативного программирования.

Как указывалось выше файлы ресурсов не отличаются от обычных. Поэтому в рамках уроков мы не будем отвлекаться на их разработку, а скачаем с сервера.

Можно скачать сразу все ресурсы для всех уроков по приложению, либо скачивать файлы ресурсов для каждого урока отдельно.

Скачать все ресурсы

Рекомендуется скачать сразу все ресурсы, чтобы на каждом уроке не отвлекаться на действия по их развертыванию.

На этом уроке мы научимся: создавать приложения, описывать экраны, работать с такими компонентами как нижнее меню, прокручиваемый список, панель, контейнер фрагментов. А также способы локализации приложений.

Постановка задачи (задание урока)

В приложении имеется 4 группы экранов:

- продукция,

- сервисы по ремонту,

- о нас,

- новости.

На данном этапе приложение должно быть локализовано для трех языков: украинского, русского, английского. Предполагается в дальнейшем расширить количество языков.

Дизайн начальных экранов этих групп приведен на рисунках ниже:

Рис. 1 экран HOME
Рис. 1 экран HOME
Рис. 2 экран REPAIRS_MAIN
Рис. 2 экран REPAIRS_MAIN
Рис. 3 экран ABOUT
Рис. 3 экран ABOUT
Рис. 4 экран NEWS
Рис. 4 экран NEWS

Во время загрузки картинок с сервера и в случае их отсутствия показывать иконку sms.png.

При клике на текст “полное описание” (рис. 3) показывается дополнительное описание, а текст меняется на “свернуть описание”. При повторном клике на этот текст (“свернуть описание”) возвращаемся в исходное состояние.

На рисунках ниже приведен дизайн: заставки при отсутствии новостей; панели выбора языка; панели с сообщениями об ошибках сети и сервера; сплэш экрана, который показывается во время загрузки приложения

Рис. 5 экран NEWS
Рис. 5 экран NEWS
Рис. 6 экран HOME
Рис. 6 экран HOME
Рис. 7 экран HOME
Рис. 7 экран HOME
Рис. 8 заставка SPLASH
Рис. 8 заставка SPLASH

Для всех запросов будет использоваться базовый URL: "https://deprosystem.com/"

Описание API

Экран HOME URL "depro/products/categories", ответ
[
    {
        "categoryId":49,
        "title":"Линии переработки ТБО",
        "imagePath":"images/icon-conveyor_nut.png",
        "count":12,
        "order":0
    }, 
    {
        "categoryId":53,
        "title":"Линии переработки фруктов/овощей/ягод",
        "imagePath":"images/icon-conveyor_berry.png",
        "count":21,
        "order":1
    },
    ....
]

Экран ABOUT URL depro/org/about, ответ
{
    "orgId":1,
    "mainImagePath":"images/about_us.png",
    "text1":"ООО «Системы модернизации складов»\\n«Системы модернизации складов» – украинская компания-производитель.\\nГлавная Миссия компании: способствовать ....",
    "text2":"Комплексные решения компании «СМС» – это разработка, проектирование....",
    "videoLink":"C6d_iP_RZrc",
    "phone":"067 890 98 76"
}

Экран NEWS URL "depro/news/list",ответ
[
    {
        "newsId":1539,
        "title":"Официально открыта фирма в Грузии — Motion Systems LLC",
        "date":"2019-04-21T17:04:38-0",
        "mainImagePath":"images/IMG-dd209ba862386381e870c31b9f48cd37-V.jpg"
    },
    {
        "newsId":1551,
        "title":"ПУЭТ День карьер",
        "date":"2019-04-24T07:04:33-0",
        "mainImagePath":"images/PUET-foto2.jpg"},
    ....
]

Общие решения по разработке приложения.

Из анализа первых 4-х экранов видно, что должна быть активность с нижним меню и 4-е фрагмента, которые будут загружаться при клике на соответствующие кнопки меню. Тулбар можно разместить в активити, а можно и во фрагментах. В данном приложении размещаем его во фрагментах. Хотя в библиотеке есть компонент ToolBar, но здесь мы будем использовать обычный RelativeLayout.

Наши действия по разработке приложения

Проект должен иметь следующие файлы (классы): MyDeclareScreens в котором описываются все экраны; MyAppParams в которм задаются значения нужных параметров проекта, например, базовый URL; MyApp в нем подключаются классы MyDeclareScreens и MyAppParams; MainActivity стартовая активити. Также необходимо настроить файл манифеста (задать MainActivity и необходимые разрешения). В целом написание этих небольших класов несложно и выполняется один раз при создании приложения. Далее только описываются экраны в классе MyDeclareScreens. Естественно названия классов можно выбирать по своему усмотрению.

1. В студии создадим новый проект. Имя можно задать на свое усмотрение. На уроках, для определенности, создадим проект с именем Stock.

2. Развертывание ресурсов. Скачаем все ресурсы приложения. Разархивируем полученный файл res.zip. В студии удалим всё содержимое папки res. Перенесем содержимое разархивированной папки res в папку res проекта. После этого, с учетом особенностей работы Android Studio, возможно нужно будет очистить проект и\или выполнить команду Invalid Caches/Restart.

3. Подключение библиотеки декларативного программирования

В секции dependencies файла build.gradle модуля нужно задать:

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

Следует учитывать, что библиотека DePro использует androidx.

В секции android файла build.gradle модуля нужно задать:

    packagingOptions {
        exclude 'META-INF/DEPENDENCIES'
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

При выборе атрибута minSdkVersion нужно учитывать, что библиотека поддерживает minSdkVersion = 17.

После изменения build.gradle нужно синхронизировать проект.

4. Создание необходимых классов (файлов). Названия классов можно использовать собственные.

Файл MyDeclareScreens.java

public class MyDeclareScreens extends DeclareScreens {
    public final static String MAIN = "main", HOME = "home", 
        REPAIRS_MAIN = "repairs_main", ABOUT = "ABOUT", NEWS = "news";

    @Override
    public void declare() {
        activity(MAIN, R.layout.activity_main)
            .fragmentsContainer(R.id.content_frame)
            .navigator(handler(R.id.apply, VH.SET_LOCALE))
            .menuBottom(R.id.nav, HOME, REPAIRS_MAIN, ABOUT, NEWS)
            .component(TC.RECYCLER,            // меню вибору мови
                model(JSON, getString(R.string.jsonListLang)),
                view(R.id.recycler, new int[] {R.layout.item_lang, R.layout.item_lang_sel}).selected("id_language"));
    }
}

Не знакомые классы студия отметит красным цветом. Если кликнуть на них клавишами Alt+Enter будет добавлен нужный импорт. В этом файле будут описываться все экраны приложения. Сейчас описан только главный экран. В нем описаны строковые константы MAIN, HOME, REPAIRS_MAIN, ABOUT, NEWS, которые служат названием используемых в приложении экранов. Названия экранов можете выбирать на свой вкус.

В методе declare() в строке activity(MAIN, R.layout.activity_main) указывается, что у нас есть экран с именем MAIN и id лайоута activity_main (файл разметки).

Можете посмотреть его содержимого в ресурсах. Там имеются стандартные элементы FrameLayout, который используется в качестве контейнера для фрагментов и RadioGroup с кнопками меню. Также там имеются элементы библиотеки SheetBottom - всплывающая панель для которой можно задать атрибутом viewId - View которая будет отображаться при раскрытии элемента и, при необходимости атрибутом negativeViewId можно указать элемент при клике на который панель закроется. В данном уроке используются панели для выбора локали (дизайн на рис. 6) и для сообщения об ошибках (рис. 7).

В строке .fragmentsContainer(R.id.content_frame) указывается контейнер для фрагментов.

В строке .navigator(...) задается навигатор, который содержит перечень необходимых действий. Более побробно навигатор описан в разделе "Навигация в DePro"

Строка .menuBottom(R.id.nav, HOME, REPAIRS_MAIN, ABOUT, NEWS) указывает, что на экране будет нижнее меню с id элемента RadioGroup разметки. Также указывается перечень экранов, которые будут вызываться при клике на ту или иную кнопку. Порядок в перечне соответствует порядку кнопок в RadioGroup. Стартовым будет экран для которого в соответствующей кнопке указано android:checked="true". В нашем случае это экран HOME.

Строки начиная с .component(TC.RECYCLER, .... Описывают список (на это указывает тип компонента TC.RECYCLER) языков, которые будут показываться в панели с id="@+id/lang" (находится в подключаемом лайоуте view_lang).

В строках view(R.id.recycler, new int[] {R.layout.item_lang, R.layout.item_lang_sel}) указывается, что для отображения списка будет использован элемент разметки R.id.recycler. А вид каждого айтема задается R.layout.item_lang и R.layout.item_lang_sel. Строка .selected("id_language") указывает, что в списке будет выделяться некоторые строки по полю в данных с именем id_language.

Данные для списка задаются в строке model(JSON, getString(R.string.jsonListLang)). В ней указывается что в R.string.jsonListLang будут данные в формате JSON.

На языковой панели также имеется кнопка (TextView) с id="@+id/apply". В строке .navigator(handler(R.id.apply, VH.SET_LOCALE)) указывается, что при клике на эту кнопку нужно установить выбранную локаль.

Более подробно создание экранов и компонентов описано в разделе "Компоненты декларативного программирования".

Файл MyAppParams.java

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

        progressLayoutId = R.layout.dialog_progress;
        errorDialogViewId = R.id.error_dialog;

        idStringDefaultErrorTitle = R.string.er_title_def;
        idStringERRORINMESSAGE = R.string.er_message;
        idStringNOCONNECTION_TITLE = R.string.er_connect_title;
        idStringNOCONNECTIONERROR = R.string.er_connect;
        idStringTIMEOUT = R.string.er_timeout;
        idStringSERVERERROR = R.string.er_server_error;
        idStringJSONSYNTAXERROR = R.string.er_json_syntax;

        nameLanguageInHeader = "Language";
        nameLanguageInParam = "id_language";
        initialLanguage = "uk";
    }
}

В этом файле указываются параметры общие для всего приложения. В нашем случае это следующие параметры.

baseUrl - указывает адрес сайта.

В библиотеке предусмотрено показывать прогресс при выполнении операций ввода-вывода данных. В параметре progressLayoutId указывается id лайоута с прогрессом.

Аналогично отображаются сообщения об ошибках ввода-вывода: либо DialogFragment с лайоутом заданным в параметре errorDialogLayoutId, либо id View на экране разметки. Мы используем второй вариант и задаем параметр errorDialogViewId. Его значение R.id.error_dialog буде для всех экранов, где предусматривается вывод сообщения об ошибках. Фрагменты используют R.id.error_dialog своей activity.

После указания этих параметров вывод прогресса и ошибок осуществляется автоматически.

Сообщение об ошибке приходит с сервера в формате: {“title”:”текст заголовка”,”message”:”текст сообщения”}. В R.id.error_dialog присутствуют элементы с соответствующими id (R.id.title и R.id.message). В принципе, можно передавать и другие данные и использовать другие названия данных. Лишь бы они совпадали с названиями полей в R.id.error_dialog. При этом нужно учитывать следующее.

Кроме ошибок, которые приходят с сервера, существуют ошибки, которые обнаруживаются на смартфоне, например, time out, отсутствие интернета и др. В этом случае библиотека формирует свои сообщения об ошибках в указанном выше формате. Поэтому, если с сервера будут приходить другие названия, то в разметке нужно предусмотреть R.id.title и R.id.message, либо эти названия указать в алиасах.

С учетом этого понятно как заполнять параметры которые начинаются с “idString...”.

И последняя группа параметров связана с локализацией: nameLanguageInHeader - задает название локали в заголовках запросов, initialLanguage - стартовую локаль. Локаль может передаваться и в параметрах. Для этого служит nameLanguageInParam.

Более подробно парметры класса AppParams описаны в приложении.

Файл MyApp.java

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 MyAppParams())
                .setDeclareScreens(new MyDeclareScreens());
    }
}

Здесь в конструкторе инициируется библиотека (DeclareParam.build(context)) и ей передаются классы MyAppParams и MyDeclareScreens.

Не забудьте описать MyApp в манифесте

Файл MainActivity.java

Изменяем файл MainActivity следующим образом:

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

Здесь в методе getNameScreen указывается название стартового экрана (MAIN)

Файл API.java

public class API {
    public static String CATEGORIES = "depro/products/categories";
    public static String NEWS = "depro/news/list";
    public static String ABOUT = "depro/org/about";
}

В нем указываются адреса на сервере для данных.

Файл AndroidManifest.xml

Наконец внесем исправления в манифест:

    <application
        android:allowBackup="true"
        android:icon="@drawable/icon"
        android:label="@string/app_name"
        android:name=".MyApp"
        android:supportsRtl="true"
        android:usesCleartextTraffic="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity"
            android:screenOrientation="portrait"
            android:theme="@style/SplashTheme"
            android:windowSoftInputMode="adjustPan"
            android:launchMode="singleTask">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
    <uses-permission android:name="android.permission.INTERNET"/>

Здесь использованы все стандартные атрибуты

5. Теперь можно запустить приложение на выполнение

Если все было сделано правильно, то вы получите следующий экран:

При тапе на элементы нижнего меню в логах будете получать сообщения типа:

SMPL_APP: 0003 No screen with name home

Это библиотека сообщает об отсутствии описания соответствующих экранов.Так как мы не меняли названия тегов (параметр NAME_LOG_APP в файле MyAppParams), то сообщения выдаются с тегами по умолчанию.

6. Опишем остальные экраны

Так как эти экраны будут ссылаться на другие экраны, то нужно дополнить список экранов. Чтобы постоянно не корректировать этот список введем сразу названия всех экранов. Для этого в MyDeclareScreens.java добавим нужные строковые константы:

SERVICE = "service", CATEGORY = "category", PRODUCT = "product", ITEM_FORM_REPAIR = "item_form_repair", ITEM_FORM = "item_form", 
REPAIRS_CALC = "repairs_calc", ITEM_FORM_SERVICE = "ITEM_FORM_SERVICE", 
COUNTRY_CODE_PH = "COUNTRY_CODE_PH", COMMENT = "COMMENT", THANKS = "THANKS", NEWS_DETAIL = "NEWS_DETAIL", WRITE_US = "WRITE_US", 
BACK_THANKS = "BACK_THANKS", YOUTUBE = "YOUTUBE"

Добавим в метод declare() класса MyDeclareScreens.java описание экрана HOME:

    fragment(HOME, R.layout.fragment_home)
        .setValue(item(R.id.lang_txt, TS.LOCALE))
        .navigator(show(R.id.sel_lang, R.id.lang, true))
            .component(TC.RECYCLER,
                model(API.CATEGORIES, 1).sort("order"),
                view(R.id.recycler, R.layout.item_home),
                navigator(start(CATEGORY, PS.RECORD)));

Перед дальнейшим изучением материала нужно ознакомиться с файлом разметки экрана fragment_home и разметкой элементов списка R.layout.item_home.

В описании экрана многие элементы нам знакомы, поэтому опишем только новые.

В некоторых случаях необходимо установить элементам разметки значения, которые не приходят с данными (модели). Для этого используется setValue. Им можно присвоить значения сразу нескольким элементам. Каждое присвоение описывается конструкцией item(id элемента, тип присвоения [, значение]). В нашем случае элементу с id lang_txt присваивается значение текущей локали.

Обработчик show указывает, что при клике на R.id.sel_lang нужно показать элемент R.id.lang (панель выбора языка), который находится в activity (MAIN).

Данные для списка задаются model(API.CATEGORIES, 1). Здесь в API.CATEGORIES находится адрес (URL) на сервере.

В библиотеке декларативного программирования имеется две модели кэширования. Обе они задаются конструкцией model(url, duration). Первая модель кеширования работает если duration > 1. В этом случае при запросе на получение данных проверяется выполнялся ли предыдущий запрос меньше чем duration миллисекунд тому. Если да то данные берутся из кэша, если больше то данные читаются с сервера (и заносятся в кэш.). Вторая модель используется если duration = 1. В этом случае при запросе на получение данных компоненту сразу передаются данные из кэша (при их наличии) потом читаются данные с сервера, обновляется кэш и полученные данные передаются в компонент.

В соответствии с постановкой задачи использован второй способ кэширования данных, а именно: model(API.CATEGORIES, 1).

Обработчик навигатора start(CATEGORY, PS.RECORD)) указывает, что при клике на какой нибудь элемент списка будет вызван экран CATEGORY и ему будет передана запись, соответствующая элементу на который кликнули.

Элементы списка отображаются в соответствии с R.layout.item_home. В этом лайоуте имеется элемент библиотеки ComponImageView. Ему можно указать кастомные атрибуты: placeholder, blur и oval. В остальном его поведение наследуется от AppCompatImageView. В нашем случае задан атрибут app:placeholder="@drawable/sms" для выполнения условий постановки задачи (Во время загрузки картинок с сервера и в случае их отсутствия показывать иконку sms.png).


После подключения данного экрана (его описания в MyDeclareScreens) и каждого последующего можно запустить приложение на выполнение, а можно описать все предусмотренные уроком экраны и потом запустить приложение.


Экран REPAIRS_MAIN (рис. 2) вообще простой. Он описывается следующим образом:

    fragment(REPAIRS_MAIN, R.layout.fragment_repairs_main)
        .setValue(item(R.id.lang_txt, TS.LOCALE))
        .navigator(show(R.id.sel_lang, R.id.lang, true),
            start(R.id.apply, SERVICE, true));

Здесь все нам знакомое. Требует пояснения обработчик навигатора start(R.id.apply, SERVICE, true). В ряде случаев при клике на элемент разметки действие выполняется достаточно долго. Чтобы исключить повторное нажатие на этот элемент блокируют (делают недоступной для кликов). Третий параметр (true) как раз и указывает на необходимость блокировать R.id.apply после клика.

Также добавим описание экрана ABOUT

    fragment(ABOUT, R.layout.fragment_about)
        .setValue(item(R.id.lang_txt, TS.LOCALE))
        .navigator(show(R.id.sel_lang, R.id.lang, true), start(R.id.apply, WRITE_US))
        .component(TC.PANEL,
            model(API.ABOUT),
            view(R.id.panel),
            navigator(start(R.id.video, YOUTUBE),
                handler(R.id.phone, VH.DIAL_UP),
                showHide(R.id.full_desc, R.id.text2, R.string.hide, R.string.full_desc)));

Здесь у нас появляется компонент нового типа - PANEL. Под панелью понимается View (LinearLayout, RelativeLayout, ...). Данные полученные по модели заносятся в соответствующие элементы панели по совпадению имен.

Обработчик handler(R.id.phone, VH.DIAL_UP) обеспечивает вызов стандартной звонилки при клике на R.id.phone (TextView или его наследник). При этом в качестве номера выступает содержимое R.id.phone. Обработчик showHide(R.id.full_desc, R.id.text2, R.string.hide, R.string.full_desc) обеспечивает показ\скрытие элемента R.id.text2 при клике на R.id.full_desc (TextView или его наследник). При этом текст R.id.full_desc будет устанавливаться R.string.hide или R.string.full_desc.

И наконец опишем экран NEWS

    fragment(NEWS, R.layout.fragment_news)
        .setValue(item(R.id.lang_txt, TS.LOCALE))
        .navigator(show(R.id.sel_lang, R.id.lang, true))
        .component(TC.RECYCLER,
            model(API.NEWS).errorShowView(R.id.error_view),
            view(R.id.recycler,  R.layout.item_news).noDataView(R.id.no_news),
            navigator(start(0, NEWS_DETAIL)));

Здесь нужно пояснить конструкции errorShowView(R.id.error_view) и noDataView(R.id.no_news. noDataView указывает элемент который будет показан в случае отсутствия данных для списка. errorShowView указывает элемент в который будет выводиться сообщение об ошибке ввода-вывода. Если указан этот функционал для модели, то сообщения об ошибке будут выводиться не в соответствии с заданными в классе MyAppParams.java параметрами, а в элемент который указан errorShowView. По непонятным мне причинам дизайнеры решили для двух экранов сообщение об ошибках показывать не так как на всех экранах. Поэтому здесь и применен функционал errorShowView.

7. Запуск приложения

Все готово для запуска приложения. Если все было сделано правильно у вас будут отображаться все экраны указанные в задании к уроку. Также уже можно менять язык интерфейса и данных

В логах можно посмотреть сообщения приложения. Если Вы не задавали параметры NAME_LOG_NET и NAME_LOG_APP (в классе MyAppParams.java), то сообщения будут с тегами по умолчанию: SMPL_NET и SMPL_APP.



Лучше пакета DePro может быть только искусственный интеллект
Задать вопрос
Отправить вопрос