Explorar el Código

алерты, XML, таймер

Евгений Колесников hace 4 años
padre
commit
91a3712187
Se han modificado 5 ficheros con 267 adiciones y 5 borrados
  1. 1 1
      api.http
  2. 193 3
      articles/weather2.md
  3. 13 1
      readme.md
  4. 6 0
      shpora/AlertDialog.kt
  5. 54 0
      shpora/XML.kt

+ 1 - 1
api.http

@@ -8,5 +8,5 @@ GET https://api.openweathermap.org/data/2.5/weather?lat={{lat}}&lon={{lon}}&appi
 GET https://api.openweathermap.org/data/2.5/forecast?q=Йошкар-Ола&appid={{token}}&lang=ru&units=metric
 
 ### Запрос погоды за 5 дней https://openweathermap.org/forecast5
-GET https://api.openweathermap.org/data/2.5/forecast?lat={{lat}}&lon={{lon}}&appid={{token}}&lang=ru&units=metric
+GET https://api.openweathermap.org/data/2.5/forecast?lat={{lat}}&lon={{lon}}&appid={{token}}&lang=ru&units=metric&mode=xml
 

+ 193 - 3
articles/weather2.md

@@ -6,7 +6,7 @@
 <a href="../articles/weather2.md">Проект погода (продолжение): SplashScreen (заставка). Выбор города. Выбор и отображение массива значений (почасовая, ежедневная)
 </a></td><tr></table>
 
-# Проект погода (продолжение): SplashScreen (заставка). Выбор города. Выбор и отображение массива значений (почасовая, ежедневная)
+# Проект погода (продолжение): SplashScreen (заставка). Выбор города. Выбор и отображение массива значений (почасовая, ежедневная). Разбор XML.
 
 ## Содержание
 
@@ -14,6 +14,9 @@
 * [Выбор города](#выбор-города)
 * [Веделение лямбда-выражения в отдельную переменную](#выделение-лямбда-выражения-в-отдельную-переменную)
 * [Получение и разбор массива данных. Вывод списка на экран.](#получение-и-разбор-массива-данных-Вывод-списка-на-экран)
+* [Доработка SplashScrin-а](#Доработка-SplashScrin-а)
+* [Вывод сообщений](#Вывод-сообщений)
+* [Разбор XML](#Разбор-XML)
 
 ## SplashScreen
 
@@ -363,11 +366,198 @@ dailyInfoRecyclerView.adapter = weatherAdapter
 </shape>
 ```
 
+## Доработка SplashScrin-а
+
+По разным причинам данные мы можем и не получить. Чтобы наша заставка вечно не висела на экране приделаем таймер.
+
+* В конструктор добавляем таймер с обратным отсчётом, в параметрах которого указываем **макcимальное время ожидания** и **интервал** между тиками. На каждый тик срабатвает метод *onTick*, в котором мы можем принудительно закрыть таймер. Если за **макcимальное время ожидания** таймер не закроют в *onTick*, то сработает метод *onFinish* и таймер завершит свою работу.
+
+
+    >На уровне класса нужно объявить переменные `counter=0` и `ready=false`
+
+    ```kt
+    object : CountDownTimer(5000,1000){
+        override fun onTick(millisUntilFinished: Long) {
+            // заставляем пялиться на нашу заставку как минимум 3 секунды
+            counter++
+            if(counter>3 && ready){
+                // данные получены - скрываем заставку
+                splash.elevation = 0F
+                this.cancel()
+            }
+        }
+
+        override fun onFinish(){
+            splash.elevation = 0F
+        }
+    }.start()
+    ```
+
+    Не забудьте при получении данных установить `ready=true`. Хотя ничего страшного не произойдёт, если забудете - просто будут смотреть на заставку чуть подольше.
+
+## Вывод сообщений
+
+>В шпоры положил
+
+В принципе тут простой телескопический конструктор
+
+```kt
+AlertDialog.Builder(this)
+    .setTitle("Заголовок")
+    .setMessage("Текст сообщения")
+    .setPositiveButton("OK", null)
+    .create()
+    .show()
+```
+
+## Разбор XML
+
+Маловероятно, мо может встретиться XML-формат в данных. Разберёмся с ним на примере погоды. В URL добавьте параметр `&mode=xml`.
+
+Придёт примерно такой ответ:
+
+```xml
+<?xml version="1.0" encoding="UTF-8"?>
+<weatherdata>
+  <location>
+    <name>Йошкар-Ола</name>
+    <type></type>
+    <country>RU</country>
+    <timezone>10800</timezone>
+    <location altitude="0" latitude="56.6384" longitude="47.893" geobase="geonames" geobaseid="466806"></location>
+  </location>
+  <credit></credit>
+  <meta>
+    <lastupdate></lastupdate>
+    <calctime>0</calctime>
+    <nextupdate></nextupdate>
+  </meta>
+  <sun rise="2021-11-16T04:30:05" set="2021-11-16T12:36:33"></sun>
+  <forecast>
+    <time from="2021-11-16T15:00:00" to="2021-11-16T18:00:00">
+      <symbol number="600" name="небольшой снег" var="13n"></symbol>
+      <precipitation probability="0.69" unit="3h" value="0.28" type="snow"></precipitation>
+      <windDirection deg="275" code="W" name="West"></windDirection>
+      <windSpeed mps="5.56" unit="m/s" name="Moderate breeze"></windSpeed>
+      <windGust gust="13.37" unit="m/s"></windGust>
+      <temperature unit="celsius" value="-1.58" min="-2.05" max="-1.58"></temperature>
+      <feels_like value="-7.22" unit="celsius"></feels_like>
+      <pressure unit="hPa" value="1015"></pressure>
+      <humidity value="94" unit="%"></humidity>
+      <clouds value="пасмурно" all="99" unit="%"></clouds>
+      <visibility value="651"></visibility>
+    </time>
+    ...
+  </forecast>
+</weatherdata>
+```
+
+Для работы с XML в андроиде есть класс **XmlPullParser**. Разберём с его помощью ответ сервера.
+
+**Во-первых**, создадим сам объект парсера:
+
+```kt
+HTTP.requestGET(url) {result, error ->
+    if(result != null) {
+        val factory = XmlPullParserFactory.newInstance()
+        factory.isNamespaceAware = true
+        val parser = factory.newPullParser()
+        parser.setInput(StringReader(result))
+        ...
+```
+
+А дальше идёт тупой перебор тегов в цикле:
+
+```kt
+while (parser.eventType != XmlPullParser.END_DOCUMENT) {
+    when (parser.eventType) {
+        XmlPullParser.START_TAG -> ...
+        XmlPullParser.END_TAG -> ...
+    }
+    parser.next()
+}
+```
+
+Т.е. пока не достигнем конца документа смотрим что у нас в текущем элементе:
+
+* START_DOCUMENT – начало документа
+* START_TAG – начало тега
+* TEXT – содержимое элемента
+* END_TAG – конец тега
+* END_DOCUMENT – конец документа
+
+Мы из всего этого многообразия будем использовать только начало и конец тега.
+
+По началу тега мы смотрим в каком теге находимся и, при необходимости, считываем текст или атрибут в локальную переменную. А по концу тега создаём объект **Weather** из накопленных данных и записываем его в массив.
+
+Пример (как обычно не полный, но достаточный для понимания):
+
+>В переменных *topTag* и *subTag* я запоминаю текущую позицию в иерархии XML. В этом документе они в принципе не нужны, но для примера проверок сделал.
+
+```kt
+var cityName = ""
+var topTag = ""
+var subTag = ""
+var dt_txt = ""
+var description: String = ""
+var icon: String = ""
+while (parser.eventType != XmlPullParser.END_DOCUMENT) {
+    when (parser.eventType) {
+        XmlPullParser.START_TAG -> {
+            when (parser.name) {
+                // в качестве начальных тегов нам интересны "location" и "forecast"
+                "location", "forecast" -> topTag = parser.name
+                "name" -> {
+                    // внутри "location" в теге "name" читаем название города
+                    if(topTag=="location") cityName = parser.nextText()
+                }
+                "time" -> {
+                    if (topTag=="forecast") {
+                        // внутри "forecast" нам интересно содержимое "time"
+                        subTag = parser.name
+                        // и сразу считываем СТРОКОВУЮ дату
+                        dt_txt = parser.getAttributeValue(null, "from").toString()
+                    }
+                }
+                "symbol" -> {
+                    if(subTag=="time"){
+                        description = parser.getAttributeValue(null, "name").toString()
+                        icon = parser.getAttributeValue(null, "var").toString()
+                    }
+                }
+                // тут мне как обычно лень стало расписывать остальные теги
+            }
+        }
+        XmlPullParser.END_TAG -> {
+            when (parser.name) {
+                "time" -> {
+                    // по закрытию тега "time" пишем погоду в массив
+                    weatherList.add(
+                        Weather(
+                            0,
+                            0.0,
+                            0,
+                            icon,
+                            description,
+                            0.0,
+                            0,
+                            dt_txt
+                        )
+                    )
+                }
+            }
+        }
+    }
+    parser.next()
+}
+```
+
 # Задание
 
-* вывести в элементы списка остальную информацию о погоде
+* вывести в элементы списка остальную информацию о погоде (формат XML)
 * при обновлени списка и при клике на элемент списка выводить в верхнюю часть детальную информацию о погоде
-
+* вставить алерты на все нештатные ситуации (не получены координаты, нет ответа от сервера...)
+* завернуть всю работу с интернетом в исключительные ситуации (try..catch..) с выводом алертов
 
 # Это интересно
 

+ 13 - 1
readme.md

@@ -138,7 +138,19 @@ http://sergeyteplyakov.blogspot.com/2014/01/microsoft-fakes-state-verification.h
 
 4. [Проект погода (начало): геолокация, http(s)-запросы, разбор json, ImageView.](./articles/weather.md)
 
-5. [Проект погода (продолжение): SplashScreen (заставка). Выбор города. Выбор и отображение массива значений (почасовая, ежедневная)](./articles/weather2.md)
+5. [Проект погода (продолжение): SplashScreen (заставка). Выбор города. Выбор и отображение массива значений (почасовая, ежедневная). Разбор XML.](./articles/weather2.md)
+
+
+<!-- 
+ориентация
+регулярка для проверки email
+xml
+alert с полями ввода (логин, пароль, кнопка регистрация)
+post-запросы с авторизацией
+чат
+tablayout
+часы
+-->
 
 <!--
 

+ 6 - 0
shpora/AlertDialog.kt

@@ -0,0 +1,6 @@
+AlertDialog.Builder(this)
+    .setTitle("Заголовок")
+    .setMessage("Текст сообщения")
+    .setPositiveButton("OK", null)
+    .create()
+    .show()

+ 54 - 0
shpora/XML.kt

@@ -0,0 +1,54 @@
+val factory = XmlPullParserFactory.newInstance()
+factory.isNamespaceAware = true
+val parser = factory.newPullParser()
+parser.setInput(StringReader(result))
+
+var cityName = ""
+var topTag = ""
+var subTag = ""
+var dt_txt = ""
+var description: String = ""
+var icon: String = ""
+while (parser.eventType != XmlPullParser.END_DOCUMENT) {
+    when (parser.eventType) {
+        XmlPullParser.START_TAG -> {
+            when (parser.name) {
+                "location", "forecast" -> topTag = parser.name
+                "name" -> {
+                    if(topTag=="location") cityName = parser.nextText()
+                }
+                "time" -> {
+                    if (topTag=="forecast") {
+                        subTag = parser.name
+                        dt_txt = parser.getAttributeValue(null, "from").toString()
+                    }
+                }
+                "symbol" -> {
+                    if(subTag=="time"){
+                        description = parser.getAttributeValue(null, "name").toString()
+                        icon = parser.getAttributeValue(null, "var").toString()
+                    }
+                }
+            }
+        }
+        XmlPullParser.END_TAG -> {
+            when (parser.name) {
+                "time" -> {
+                    weatherList.add(
+                        Weather(
+                            0,
+                            0.0,
+                            0,
+                            icon,
+                            description,
+                            0.0,
+                            0,
+                            dt_txt
+                        )
+                    )
+                }
+            }
+        }
+    }
+    parser.next()
+}