Статьи

Приложение Android Weather с использованием провайдера погоды Yahoo и AutoCompleteTextView (часть 1)

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

В этом первом посте мы хотим проанализировать, как мы можем получить информацию о городе, используя Yahoo API. Мы предполагаем, что у вас уже есть аккаунт разработчика Yahoo, если вы не можете создать его по этой ссылке . Важно, чтобы у вас был appid, он абсолютно бесплатный, но необходимо использовать Yahoo API. Пока мы будем изучать API Java, у нас есть возможность описать некоторые интересные компоненты пользовательского интерфейса Android, такие как AutoCompleteTextView и синтаксический анализ XML в Android. Наша цель — создать приложение для Android, которое покажет список городов, которые соответствуют частичному имени, введенному пользователем, как показано ниже:

android_yahoo_weather_autocomplete [4]

Yahoo Woeid

Первый шаг к получению информации о погоде — получение информации о горе . Это пространственный идентификатор, который Yahoo использует для определения города / региона. Затем мы должны найти способ получить это горе из названия города, введенного пользователем.

С точки зрения пользовательского интерфейса, мы хотим, чтобы пользователь вставлял только название города или его частичное имя, и мы должны найти способ получить список городов, который соответствует данным, введенным пользователем, с соответствующим woeid : мы можем использовать приведенный ниже API, чтобы получить список городов, соответствующих введенному нами шаблону:

1
<a href="http://where.yahooapis.com/v1/places.q(city_name_pattern);count=MAX_RESULT_SIZE?appid=your_app_id">http://where.yahooapis.com/v1/places.q(city_name_pattern);count=MAX_RESULT_SIZE?appid=your_app_id</a>

Если вы используете этот API в своем браузере, вы получите XML-файл со списком информации о городах, которые соответствуют city_name_pattern .

Android Yahoo XML анализатор данных

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
public class CityResult {
 
    private String woeid;
    private String cityName;
    private String country;
 
    public CityResult() {}
 
    public CityResult(String woeid, String cityName, String country) {
        this.woeid = woeid;
        this.cityName = cityName;
        this.country = country;
    }
 
     // get and set methods
 
    @Override
    public String toString() {
        return cityName + "," + country;
    }
}

Теперь мы создаем класс парсера, который мы называем YahooClient . Этот класс отвечает за извлечение данных XML и их анализ. Он имеет простой статический метод, который принимает шаблон, который мы должны использовать, чтобы получить список городов. Сначала мы открываем соединение HTTP и передаем поток в анализатор XML:

1
2
3
4
yahooHttpConn= (HttpURLConnection) (new URL(query)).openConnection();
yahooHttpConn.connect();
XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
parser.setInput(new InputStreamReader(yahooHttpConn.getInputStream()));

затем мы начинаем анализ данных, ища некоторые интересующие нас теги. К настоящему времени, учитывая и нашу модель данных, нас интересует только горе , название города и страны . В XML есть другая информация, но мы пока не хотим ее извлекать.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
int event = parser.getEventType();
 
CityResult cty = null;
String tagName = null;
String currentTag = null;
 
// We start parsing the XML
while (event != XmlPullParser.END_DOCUMENT) {
    tagName = parser.getName();
 
    if (event == XmlPullParser.START_TAG) {
       if (tagName.equals("place")) {
          // place Tag Found so we create a new CityResult
          cty = new CityResult();
           Log.d("Swa", "New City found");
       }
        currentTag = tagName;
        Log.d("Swa", "Tag ["+tagName+"]");
    }
    else if (event == XmlPullParser.TEXT) {
        // We found some text. let's see the tagName to know the tag related to the text
        if ("woeid".equals(currentTag))
            cty.setWoeid(parser.getText());
        else if ("name".equals(currentTag))
            cty.setCityName(parser.getText());
        else if ("country".equals(currentTag))
            cty.setCountry(parser.getText());
 
        // We don't want to analyze other tag at the moment
    }
    else if (event == XmlPullParser.END_TAG) {
        if ("place".equals(tagName))
            result.add(cty);
    }
 
    event = parser.next();
}

Приведенный ниже код очень прост: в строке 1 мы просто получаем первое событие XML и начинаем обходить документ XML, пока не достигнем конца. В конце метода у нас есть список городов с нужной информацией, которую мы искали.

AutoCompleteTextView и ArrayAdapter с фильтром

Как только мы узнаем, как получить данные из XML, мы должны показать пользователю список элементов, которые мы получаем с помощью API Yahoo. Есть несколько способов достичь этой цели, мы будем использовать AutoCompleteTextView. Этот компонент определен в Android doc как « Редактируемое текстовое представление, которое автоматически отображает предложения по завершению, пока пользователь печатает. Список предложений отображается в раскрывающемся меню, из которого пользователь может выбрать элемент для замены содержимого поля редактирования . ». Это удовлетворяет наши потребности! Использование этого компонента тривиально, но немного сложнее использовать адаптер массива и то, как мы фильтруем результат. Обычно этот компонент используется со статическим списком элементов, в нашем случае мы должны получить его с удаленного сервера. Сначала мы реализуем пользовательский адаптер, расширяющий ArrayAdapter, и это очень просто:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
private class CityAdapter extends ArrayAdapter<CityResult>  {
 
private Context ctx;
private List<CityResult> cityList = new ArrayList<CityResult>();
 
public CityAdapter(Context ctx, List<CityResult> cityList) {
    super(ctx, R.layout.cityresult_layout, cityList);
    this.cityList = cityList;
    this.ctx = ctx;
}
 
@Override
public CityResult getItem(int position) {
    if (cityList != null)
        return cityList.get(position);
 
    return null;
}
 
@Override
public int getCount() {
    if (cityList != null)
        return cityList.size();
 
    return 0;
}
 
@Override
public View getView(int position, View convertView, ViewGroup parent) {
    View result = convertView;
 
    if (result == null) {
        LayoutInflater inf = (LayoutInflater) ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        result = inf.inflate(R.layout.cityresult_layout, parent, false);
 
    }
 
    TextView tv = (TextView) result.findViewById(R.id.txtCityName);
    tv.setText(cityList.get(position).getCityName() + "," + cityList.get(position).getCountry());
 
    return result;
}
 
@Override
public long getItemId(int position) {
    if (cityList != null)
        return cityList.get(position).hashCode();
 
    return 0;
}
...
}

Гораздо интереснее, как мы можем получить данные с удаленного сервера. Если мы реализуем интерфейс Filterable в нашем настраиваемом адаптере, мы можем получить искомый результат. Итак, мы имеем:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private class CityAdapter extends ArrayAdapter<CityResult> implements Filterable {
....
        @Override
        public Filter getFilter() {
            Filter cityFilter = new Filter() {
 
                @Override
                protected FilterResults performFiltering(CharSequence constraint) {
                    FilterResults results = new FilterResults();
                    if (constraint == null || constraint.length() < 2)
                        return results;
 
                    List<CityResult> cityResultList = YahooClient.getCityList(constraint.toString());
                    results.values = cityResultList;
                    results.count = cityResultList.size();
                    return results;
                }
 
                @Override
                protected void publishResults(CharSequence constraint, FilterResults results) {
                    cityList = (List) results.values;
                    notifyDataSetChanged();
                }
            };
 
            return cityFilter;
        }
..
}

В строке 4 мы реализуем наш Фильтр, который имеет два других метода, которые мы должны реализовать. В методе performFiltering мы выполняем HTTP-вызов и извлекаем данные (строка 12). Очевидно, у нас может быть проблема с ANR, и мы очень хорошо знаем, что не следует делать HTTP-вызов в главном потоке. Но если вы прочитаете документацию о executeFiltering, вы обнаружите, что этот метод работает в отдельном потоке, поэтому у нас не возникнет никаких проблем.

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

1
2
3
4
5
6
7
8
9
AutoCompleteTextView edt = (AutoCompleteTextView) rootView.findViewById(R.id.edtCity);
CityAdapter adpt = new CityAdapter(this.getActivity(), null);
edt.setAdapter(adpt);
edt.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
       // We handle the onclick event and select the city chosen by the user
   }
});

В следующем посте мы будем получать информацию о погоде, используя woied, так что следите за обновлениями!

  • Исходный код скоро появится