Проект погода (продолжение): SplashScreen (заставка). Выбор города. Выбор и отображение массива значений (почасовая, ежедневная). Разбор XML. Содержание Wear OS
# Проект "база". Авторизация на сервере (Basic auth, token). POST-запросы. API. **Содержание** * [API](#API) * [Первичная настройка приложения](#Первичная-настройка-приложения) * [Добавление альбомной ориентации](#Добавление-альбомной-ориентации) * [~~Модальный диалог авторизации~~](#Модальный-диалог-авторизации) * [Регулярные выражения](#Регулярные-выражения) * [HTTP-запросы, методы, форматы, заголовки.](#HTTP-запросы-методы-форматы-заголовки) * [Сохранение данных при работе приложения](#сохранение-данных-при-работе-приложения) * [Фильтрация данных](#фильтрация-данных) * [Spinner (выпадающий список)](#spinner-выпадающий-список) ## API Приведу пример описания API, которое было на моём демо-экзамене: >API доступно по адресу http://car.areas.su/ >Для работы с API метод принимает в теле application/json >Например: >Метод входа. Ссылка http://cars.areas.su/login > >Request Body(application/json) >```json >{ > "username":"serk", > "password":"123" >} >``` > >Response > >```json >{ > "notice": > { > "token":111520 > } >} >``` > >1 **/login** – метод для получения ключа для входа > - Используется метод POST > - Формат запроса JSON > - Принимает два ключа username и password > - ответ в формате JSON > >2 **/logout** – метод обнуления действия token > - Используется метод POST > - Формат запроса JSON > - Принимает ключ username > >3 **/signup** – метод регистрации нового пользователя > - Используется метод POST > - Формат запроса JSON > - Принимает ключ username, email, password > >4 **/cars** – метод для получения свободных автомобилей > - Используется метод GET > - Формат ответа JSON > >5 **/history** – метод для получения истории вождения > - Используется метод POST > - Формат запроса JSON > - **Принимает ключ token** > - Формат ответа JSON Несколько косноязычно и не расписаны "подводные камни". Логика авторизации такая, что если токен уже получен, то новый не выдается - нужно сначала разлогиниться. Методы есть как публичные (**/cars**), так и требующие авторизации (**/history** - это видно по тому, что в параметрах требуется токен). Я на примере этого разработал АПИ для ваших баз: 1. Создаем файл `api.http`: >Примеры запросов реализованы в формате плагина **REST Client** для Visual Studio Code Прописываем в нем URL сервера: ``` @url=http://s4a.kolei.ru ``` 2. Проверить работоспособность можно послав запрос **/about**: ``` GET {{url}}/about ``` 3. Для авторизации нужно послать **POST** запрос **/login** в формате `application/json` с параметрами *username* и *password* (соответственно логин и пароль к ВАШЕЙ базе MySQL) ``` ### логин # @name login POST {{url}}/login Content-Type: application/json { "username": "esmirnov", "password": "111103" } ### @token={{login.response.body.$.notice.token}} ``` Тут встречаются средства автоматизации **REST Client**-а: * `# @name login` этой командой мы присваиваем имя текущему запросу * `@token={{login.response.body.$.notice.token}}` а здесь, используя ответ команды достаём токен. Таким образом для последующих команд не надо выписывать на бумажке токен и править запросы. При успешном ответе придет токен авторизации: ```json { "notice": { "token": 2200743 } } ``` При ошибке текст ошибки в поле *answer*: ```json { "notice": { "answer": "Пользователь уже авторизован, используйте токен или перелогиньтесь" } } ``` То есть при получении ответа вы должны разобрать полученный JSON, если есть токен, то можно продолжать работать. Если ошибка, то показать **Alert** с ошибкой и остаться на экране авторизации. >Маловероятно, но вдруг попадётся задача сделать "базовую авторизацию" (Basic Auth) >При таком методе авторизации в запрос нужно добавить заголовок `Authorization: Basic <логин:пароль в кодировке base64>` > >```kt >Base64.encodeToString( > "$login:$password".toByteArray(), > Base64.NO_WRAP) >``` > >Базовая авторизация позволяет использовать GET-запрос, т.к. в теле запроса ничего не предается 4. Для выхода нужно послать **POST** запрос **/logout** c параметом *username*: ``` POST {{url}}/logout Content-Type: application/json { "username": "esmirnov" } ``` 5. Для запроса данных из базы нужно послать **GET** запрос с названием таблицы. В заголовке запроса передать токен полученный при авторизации. ``` GET {{url}}/Product Content-Type: application/json Token: {{token}} ``` В ответ должны получить содержимое таблицы: ```json { "notice": { "data": [ { "ID": 1, "Title": "Колесо R18 Кованый", "ProductTypeID": 128, "ArticleNumber": "241659", "Description": null, "Image": "\\products\\tire_15.jpg", "ProductionPersonCount": 4, "ProductionWorkshopNumber": 10, "MinCostForAgent": 11509 }, ... ] } } ``` 6. Для получения изображений нужно выделить его название из поля *Image* и сформировать **GET** запрос: ``` GET {{url}}/img/tire_0.jpg ``` Обратите внимание, для загрузки картинок я использую не всю строку, которая у вас в базе (`\products\tire_0.jpg`), а только название файла. Чтобы вытащить название из строки можно использовать метод *split* - он делит исходную строку на список подстрок по указанному разделителю: ```kt val fileName = imageNameFromDB.split("\\").lastOrNull() if(fileName != null){ ... } ``` Если условие поиска в строке сложнее, то можно применить регулярные выражения: ```kt // то что мы хотим найти заключаем в круглые скобки (группы) val re = Regex("""\\products\\(.*)""") val res = re.find("""\products\tire_0.jpg""") // если регулярное выражение ничего не найдет, то вернёт null if(res != null){ // искомый текст в ПЕРВОЙ группе // (в 0 группе находится вся строка совпавшая с регулярным выражением) Log.d("KEILOG", res.groupValues[1]) } ``` ## Первичная настройка приложения 1. Создаем новый проект и сразу пытаемся его запустить. Если при сборке проекта выходит подобная ошибка, то нужно "понизить" версию зависимости, на которую ругается сборщик. Вообще эта ошибка означает, что какой-то пакет (в нашем случае **androidx.appcompat:appcompat:1.4.0**) требует более новой SDK, чем установлена. Но в **AVD** пока нет версий новее 30. ``` The minCompileSdk (31) specified in a dependency's AAR metadata (META-INF/com/android/build/gradle/aar-metadata.properties) is greater than this module's compileSdkVersion (android-30). Dependency: androidx.appcompat:appcompat:1.4.0. ``` Открываем `build.graddle (Module...)`, находим нужный пакет в зависимостях (секция **dependencies**) и уменьшаем минорную версию пакета. Например, если была версия **1.4.0**, то правим на **1.3.0**. 2. Устанавливаем иконку и название проекта. * Установка иконки: В контекстном меню папки ресурсов (**res**) выбираем `New -> Image asset` ![](../img/04033.png) В появившемся окне в поле путь (**Path**) выбираем картинку (в нашем случае произвольную, а на демо-экзамене она должна быть в предоставленных ресурсах). Можно заодно задать имя ресурса в поле **Name**. ![](../img/04034.png) Открываем манифест (`manifest/AndroidManifest.xml`) и в теге **application** правим атрибут `android:icon`: ``` android:icon="@mipmap/ico" ``` * установка названия приложения В принципе достаточно поменять в манифесте атрибут ``` android:label="Название вашего приложения" ``` Но на всякий случай можно завернуть его в ресурсы (вдруг на экзамене будет под это отдельный критерий): В файле строковых ресурсов (`res/values/strings.xml`) добавить (исправить если уже есть) значение ```xml Восьмёрка ``` И в манифесте вставить указатель на строковый ресурс ``` android:label="@string/app_name" ``` Раньше как-то не пришло в голову, но часто графические ресурсы таскают сразу в приложении. По крайней мере в одном из заданий про банк иконки валют и стран прилагались к заданию. Это значит, что при получении, например, информации о валюте мы должны иконку тащить не из интернета, а из ресурсов. Простая загрузка ресурса с ИЗВЕСТНЫМ id не сложная: ```kt findViewById(R.id.ico) .setImageResource( R.drawable.ic_launcher_background ) ``` Но при получении данных из интернета мы имеем НАЗВАНИЕ ресурса (файла), а не его id в приложении. Для поиска id по имени есть отдельный метод: ```kt val icoId = resources .getIdentifier( "ic_launcher_background", // название ресурса "drawable", // раздел, в котором находится ресурс this.packageName // пакет ) // дальше как обычно findViewById(R.id.ico).setImageResource(icoId) ``` ## Добавление альбомной ориентации В окне разметки (acticity_main.xml) перейдите в режим "design" и кликните кнопку "Orientation..." выбираем "Create Landscape Variation" ![](/img/as023.png) Система автоматически создаст Layout с альбомной ориентацией. ![](/img/as024.png) >Учитывайте, что конструктор общий для всех ориентаций - при обращении к несуществующему объекту произойдет исключение. Чтобы для разных ориентаций не рисовать одинаковую разметку (допустим список валют выводится в обеих ориентациях) можно вынести повторяющиеся куски разметки в отдельные файлы разметки (layout), а в нужные места вставить ссылку на них с помощью тега **include** ```xml ``` В паре с **include** используется тег **merge**. Если выделяемый кусок разметки содержит несколько отдельных элементов, то по стандартам XML мы должны завернуть их в один родительский. Как раз тег **merge** и можно использовать в таком случае. Он игнорируется при разборе разметки и ни как на неё не влияет. ```xml