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

Урок 5

На протяжении следующих уроков мы расширим и углубим наши знания по декларативному программированию. Материал будем изучать на учебном проекте. Это будет не целостный проект как на предыдущих уроках. В рамках учебного проекта будут собраны части из разных реальных проектов. Выбор екранов для проекта осуществлялся из желания описать как можно больше различных компонентов.

Таким образом учебный проект в некотором роде является "винегретом" частей от разных реальных проектов, что нашло отображение и в его названии (vinaigrette). Поэтому в проекте ресурсы использованы от разных проектов. В связи с этим у разных екранов проекта будут различные дизайнерские решения, отличающиеся между собой стилем, цветовой схемой, пропорциями и т.д.

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

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

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

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

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

На этом уроке мы научимся: работать с компонентом Tutorial (Intro), авторизацией, а также организацией последвательного вызова туториала, авторизации, основной активити

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

Во многих приложениях используется следующий шаблон. При первом входе в приложение на нескольких показывается общая информация о приложении в виде рисунков и текста. С помощью этих экранов пользователя знакомят с основными свойствами приложения. Иногда здесь приводится краткая инструкция по работе с приложением. Поэтому эта группа экранов часто называется INTRO или TUTORIAL. После них выполняется группа экранов авторизации. А после авторизации осуществляется переход на основные экраны. Первые две группы экранов выполняютяс один раз при входе в приложение. Причем если выйти из приложения не закончив действия по некоторой группе, то при повторном входе в приложение будут показаны экраны этой группы.

В данном тестовом проекте у нас есть три группы соответствующих экранов: INTRO, AUTH (LOGIN и REGISTRATION) и MAIN

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

Дизайн экранв урока приведен на следующих рисунках:

Рис. 1 экран INTRO
Рис. 1 экран INTRO
Рис. 2 экран INTRO
Рис. 2 экран INTRO
Рис. 3 экран INTRO
Рис. 3 экран INTRO
Рис. 4 экран INTRO
Рис. 4 экран INTRO
Рис. 5 экран LOGIN
Рис. 5 экран LOGIN
Рис. 6 экран LOGIN
Рис. 6 экран LOGIN
Рис. 7 экран LOGIN
Рис. 7 экран LOGIN
Рис. 8 экран LOGIN
Рис. 8 экран LOGIN
Рис. 9 экран LOGIN
Рис. 9 экран LOGIN
Рис. 10 экран REGISTRATION
Рис. 10 экран REGISTRATION
Рис. 11 экран REGISTRATION
Рис. 11 экран REGISTRATION
Рис. 12 экран REGISTRATION
Рис. 12 экран REGISTRATION
Рис. 13 экран REGISTRATION
Рис. 13 экран REGISTRATION
Рис. 14 экран MAIN
Рис. 14 экран MAIN

На рисунках 1 - 4 изображены разные виды одного и того же экрана (INTRO). На рисунках 5 - 9 также изображен один экран (LOGIN) в разных состояниях. Аналогично и экраны на рисунках 10 - 13 (REGISTRATION). На рисунке 14 изображен пустой екран (MAIN). Он в данном уроке используется просто для целостного восприятия темы урока.

Описание API

Для авторизованых пользователей на сервер нужно передавать токен в заголовке с именем "Auth-token"

Экран INTRO метод JSON строка (json):
[
    {
        "text":"Талоны дешевле на 12%, чем на АЗС",
        "img":"tutorial_1"
    },
    {
        "text":"Электронные талоны всегда с собой",
        "img":"tutorial_2"
    },
    {
        "text":"Становитесь агентом и зарабатывайте",
        "img":"tutorial_3"
    },
    {
        "text":"Навигатор укажет путь к ближайшей заправке",
        "img":"tutorial_4"
    }
]

Экран LOGIN URL depro/auth/login, метод POST отправка данных
    {"login":"ddddddd","password":"ddddddd"}
Ответ 
{
    "token":"YKqJ7TdmYLGZEk0CwuA0kHyvGTKy4K",
    "profile":
        {
            "userId":69,
            "login":"ddddddd",
            "surname":"Іванченко",
            "name":"Петро",
            "second_name":"Петрович",
            "phone":"+380777777777",
            "photo":"depro/usersphoto/1566625644241.jpg",
            "email":"d@x.com"
        }
}

Экран REGISTRATION URL depro/auth/register, метод POST тип данных - multipart, отправка данных
    Передается файл фото (если есть) и данные в виде json строки:
    {"login":"ssssss","password":"ssssss","surname":"Иванов","name":"Иван",
        "second_name":"Иванович","phone":"+380555555555","email":"x@c.n"}
Ответ
    Как для экрана LOGIN

В принципе данные для экрана INTRO можно получать с сервера. В этом случае необходимо в модели указать соответствующий URL, а на сервере предусмотреть его обработку.

При клике на экране INTRO на текст "Пропустить знакомство" или "Приступить" осуществляется переход на экран LOGIN (рис. 5). При клике на текст "Продолжить" осуществляется переход к следующему фрагменту экрана INTRO. Свайпом влево - вправо можно листать фрагменты экрана INTRO.

Экран LOGIN позволяет:

- Ввести логин и пароль, в этом случае (если они валидны) становится доступной кнопка "Войти". При клике на которую осуществляется переход на экран MAIN. Для валидности (в данном случае) нужно чтобы логин был не менее 3 символов, а пароль не менее 6 символов.

- По клику на кнопку "Зарегистрироваться" перейти на экран REGISTRATION.

- По клику на кнопку "Войти без авторизации" перейти на экран MAIN. В этом случае будет доступен не весь функционал приложения.

Экран REGISTRATION позволяет:

Ввести данные регистрации и по клику на кнопку "Зарегистрироваться" передать их на сервер и после положительного ответа перейти на экран MAIN. Валидными должны быть только поля логин и пароль.

Экран MAIN на данном уроке пустой и нужен чтобы были переходы на него, а не сообщения в логах о его отсутствии.

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

Многое нам знакомо по предыдущим урокам. Поэтому будем акцентировать внимание только на новых элементах.

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

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

3. Подключение библиотеки

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

    implementation 'github.com/deprosystem/idedepro:idedepro:3.0.2'

Следует учитывать, что библиотека 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
            SEQUENCE = "sequence", INTRO = "INTRO", AUTH = "auth", MAIN = "main",
            LOGIN = "LOGIN", REGISTRATION = "REGISTRATION", DRAWER = "DRAWER", CATALOG = "CATALOG",
            PRODUCT_LIST = "PRODUCT_LIST", BARCODE = "BARCODE",
            PRODUCT_DESCRIPT = "PRODUCT_DESCRIPT", ADD_PRODUCT = "ADD_PRODUCT",
            DESCRIPT = "DESCRIPT", CHARACTERISTIC = "CHARACTERISTIC", ORDER_LIST = "ORDER_LIST",
            ORDER_PRODUCT = "ORDER_PRODUCT", PROFILE = "PROFILE", FITNESS = "FITNESS",
            PICK_TIME = "pick time", NEWS_EVENTS = "NEWS_EVENTS", NEWS = "NEWS", EVENT = "event",
            NEWS_DETAIL = "NEWS_DETAIL", ANIMATION = "ANIMATION";

    public String PUSH_NEWS = "news", PUSH_EVENTS = "events";

    @Override
    public void declare() {
        activity(SEQUENCE, R.layout.activity_sequence)
            .componentSequence(INTRO, AUTH, MAIN);

        activity(INTRO, R.layout.activity_tutorial)
            .componentIntro(model(JSON, getString(R.string.json_tutorial)),
                R.id.pager, R.layout.item_tutorial, R.id.indicator, R.id.skip, R.id.contin, R.id.proceed);

        activity(AUTH, R.layout.activity_auth).animate(AS.RL)
            .fragmentsContainer(R.id.content_frame, LOGIN);

        fragment(LOGIN, R.layout.fragment_login)
            .component(TC.PANEL_ENTER, null,
                view(R.id.panel),
                navigator(handler(R.id.done, VH.CLICK_SEND, model(POST, Api.LOGIN, "login,password"),
                    after(setToken(), setProfile("profile"), handler(0, VH.NEXT_SCREEN_SEQUENCE)), true, R.id.login, R.id.password),
                    start(R.id.register, REGISTRATION),
                    handler(R.id.enter_skip, VH.NEXT_SCREEN_SEQUENCE)));

        fragment(REGISTRATION, R.layout.fragment_registration)
            .navigator(back(R.id.back))
            .componentPhoto(R.id.cli, new int[] {R.id.blur, R.id.photo}, R.string.source_photo)
            .component(TC.PANEL_ENTER, null,
                view(R.id.panel),
                navigator(handler(R.id.done, VH.CLICK_SEND, model(POST, Api.REGISTER,
                    "login,password,surname,name,second_name,phone,photo,email"),
                    after(setToken(), setProfile("profile"), handler(0, VH.NEXT_SCREEN_SEQUENCE)),
                    true, R.id.login, R.id.password))) ;

        activity(MAIN, R.layout.activity_main);
    }
}

Здесь строковыми константамтами заданы все экраны, которые будут описаны в приложении и названия пушей (детально о них в уроке 9). Также описаны экраны данного урока.

Экран SEQUENCE имеет только один компонент - componentSequence, которому передаются названия экранов порядок выполнения которых он котролирует. Правила его работы уже были описаны выше. Так как компонент Sequence не имеет соответствующего элемента разметки, то и в лайоуте activity_sequence не имеется ничего.

Экран INTRO описывает также только один компонент - componentIntro. В модели указывается, что данные берутся из json строки. Хотя компоненту передается много вьюшек, их предназначение крайне простое:

- R.id.pager задает элемент ViewPager в котором будут отображаться картики и текст, полученные по модели.
- R.layout.item_tutorial задает лайоут в котором содержится разметка для item ViewPager-а.
- R.id.indicator задает индикатор.
- R.id.skip задает элемент в activity_tutorial, который соответствует кнопке "Пропустить знакомство".
- R.id.contin задает элемент в activity_tutorial, который соответствует кнопке "Продолжить".
- R.id.proceed задает элемент в activity_tutorial, который соответствует кнопке "Приступить".

В качестве R.id.skip, R.id.contin, R.id.proceed могут выступать не обязательно элементы TextView, а любые элементы, которые будут ассоцировать с указанными действиям, например ImageView. В случае, если дизайном не будут предусмотрены какие нибудь из указанных действий, то для них нужно указать 0.

Экран AUTH содержит контейнер фрагментов в который сразу загружается екран LOGIN

Экран LOGIN содержит компонент типа PANEL_ENTER. Так как начальные данные не нужны, то модель равна null. Представление указывает только id панели. Навигатор содержит три действия:

- По кнопке "Войти" (done) обработчиком CLICK_SEND будут отправлены данные (login и password) по урл Api.LOGIN на сервер для авторизации. Также указывается что активность кнопки done буде зависеть от валидности элементов разметки R.id.login и R.id.password. При получении положительного ответа в блоке after запоминается токен и профиль. Они запоминаются в соответствующие системные глобальные переменные. Если для setToken не указан параметр, то считается что в полученных от сервера данных токен находится в поле с именем "token". Если для setProfile не указан параметр, то в профиль запоминается вся полученная от сервера запись. Если параметр задан (как в нашем случае "profile"), то в глобальную переменную профиля будут занесены данные поля с таким именем. После запоминания по обработчику NEXT_SCREEN_SEQUENCE будет осуществлен выход из текущей активности и переход к следующей.

- По кнопке "Зарегистрироваться" (register) осуществляется переход нп экран регистрации.

- По кнопке "Войти без авторизации" (enter_skip) осуществляется переход нп следующий экран (MAIN).

Экран REGISTRATION содержит компонент типа PANEL_ENTER. Его описание во многом похоже на описание экрана LOGIN. Новым для нас компонентом является componentPhoto, который служит для получения фото. В качестве параметров ему передаются id элемента при клике на который начнет работать коспонент (cli). Список id элементов в которые будет заноситься фото. И строка которая будет отображаться в диалоге выбора источника фото (камера, галерея, ..). В разметке этому компоненту ничего не соответствует. Полуенные изображения заносятся в R.id.blur и R.id.photo. Если необходимо, что бы эти изображения передавались на сервер, то нужно использовать элемент ComponImageView.

Для ввода данных на экранах LOGIN и REGISTRATION используется элемент ComponEditText, который наследуется от AppCompatEditText и имеет дополнительные атрибуты для проверки валидности данных. Для ввода номера телефона используется елемент EditTextMask

Файл MyParams.java

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

        progressLayoutId = R.layout.dialog_progress;
        errorDialogLayoutId = R.layout.dialog_error;
        errorDialogNegativeId = R.id.cancel;
        errorDialogPositiveId = R.id.exit;

        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;
        idStringNO_AUTH = R.string.er_no_auth;
    }
}

В этом файле указываются параметры общие для всего приложения. Практически все они нам знакомы по первому уроку за исключением следующих:
- nameTokenInHeader задает имя токена в заголовке запросов;
- errorDialogLayoutId указывает лайоут который будет использован системным диалогом;
- errorDialogNegativeId и errorDialogPositiveId указывают id соответствующих кнопок в разметке окна диалога.

Файл 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 MyParams())
                .setDeclareScreens(new MyDeclareScreens());
    }
}

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

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

Файл StartActivity.java

Удаляем файл MainActivity и вместо него создаем класс StartActivity:

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

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

Файл Api.java

public class Api {
    public static final String LOGIN = "depro/auth/login",
        REGISTER = "depro/auth/register",
        CATALOG = "depro/cron/catalog",
        NEWS_PROD = "depro/cron/news_prod",
        CATALOG_EX = "depro/cron/catalog_ex",
        PRODUCT_BARCODE = "depro/cron/product_barcode",
        PRODUCT_LIST = "depro/cron/product_list",
        PRODUCT_SEARCH = "depro/cron/product_search",
        PRODUCT_ID = "depro/cron/product_id",
        ANALOG_ID_PRODUCT = "depro/cron/product_analog",
        CHARACT_ID_PRODUCT = "depro/cron/product_charact",
        PROFILE = "depro/crontoken/profile",
        EDIT_PROF = "depro/auth/profile_edit",
        FITNESS = "depro/cron/fitness",
        SEND_ORDER = "depro/crontoken/send_order",
        FREEE_TIME = "depro/cron/freetime",
        NEWS = "depro/cron/news",
        NEWS_DETAIL = "depro/cron/news_detail",
        NEWS_SUBSCRIBE = "api/push/subscribe_news",
        NEWS_UNSUBSCRIBE = "api/push/unsubscribe_news",
        TOPIC_SUBSCRIBE = "api/push/subscribe_events",
        TOPIC_UNSUBSCRIBE = "api/push/unsubscribe_events",
        SEND_EVENTS_PUSH = "api/push/send_events",
        SEND_NEWS_PUSH = "api/push/send_news",
        EVENT = "depro/cron/events",
        SEND_FIT_TIME  = "depro/cron/fit_time";
}

В нем указываются адреса на сервере для всех экранов приложения.

Файл 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=".StartActivity"
            android:screenOrientation="portrait"
            android:theme="@style/SplashTheme"
            android:windowSoftInputMode="adjustPan">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <provider
            android:name=".MyFileProvider"
            android:authorities="${applicationId}.provider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths"/>
        </provider>
    </application>
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-feature android:name="android.hardware.camera" />
    <uses-permission android:name="android.permission.FLASHLIGHT"/>

    <uses-feature android:name="android.hardware.camera.flash"
        android:required="true" />

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES"/>

Здесь использованы все стандартные атрибуты. Так как у нас используется камера, то для нее указаны необходимые разрешения включая память и provider. В ресурсах у нас уже есть файл file_paths нужный для провайдера.

Также создадим класс MyFileProvider:

Файл MyFileProvider.java

public class MyFileProvider extends FileProvider {
}

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

Если все введено правильно, то при старте приложения будут отображаться (и работать) все указанные экраны.



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