Android app development:weather app with forecast
This tutorial will describe how to develop an android application. In this tutorial we will explore what we need to create an android application and what are the bricks we can use to create our app. As example we will create a weather app. We talked about it some months ago, we will develop the weather app with weather current condition and weather forecast. To develop an android application we need at least:
- An activity
- a Layout
These are the basic component we can use. Of course we want to create some more complex because we have to retrieve information from a remote server (in our case the openweathermap), and we have to parse the result data. So we need to add to the basic components:
- HTTP connection
- AsyncTask (to not have ANR problems)
- JSON parsing
- Data model (that holds the JSON data)
At the end we will obtain:
App Layout
The first step is creating the layout. As you can see from the image below our layout is divided in two sections: one that holds the current weather condition and the other that holds the weather forecast. In the weather forecast we have to move among different days so we can use a ViewPager.
We can use in this case a LinearLayout:
Now we have our layout we can start working on each sections. In the first section (current weather) we can use a RelativeLayout to place every widget at the right place:
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 53 | < RelativeLayout android:layout_width = "match_parent" android:layout_height = "match_parent" android:paddingBottom = "@dimen/activity_vertical_margin" android:paddingLeft = "@dimen/activity_horizontal_margin" android:paddingRight = "@dimen/activity_horizontal_margin" android:paddingTop = "@dimen/activity_vertical_margin" tools:context = ".MainActivity" android:layout_weight = "1" > < TextView android:id = "@+id/cityText" style = "?android:attr/textAppearanceMedium" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:layout_alignParentLeft = "true" > </ TextView > < TextView android:id = "@+id/temp" style = "@style/tempStyle" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:layout_below = "@id/cityText" android:layout_centerHorizontal = "true" > </ TextView > < TextView android:id = "@+id/unittemp" android:layout_width = "wrap_content" style = "?android:attr/textAppearanceMedium" android:layout_height = "wrap_content" android:layout_below = "@id/cityText" android:layout_toRightOf = "@id/temp" android:layout_alignBaseline = "@id/temp" > </ TextView > < TextView android:id = "@+id/skydesc" android:layout_width = "wrap_content" style = "?android:attr/textAppearanceMedium" android:layout_height = "wrap_content" android:layout_below = "@id/temp" android:layout_alignStart = "@id/temp" android:layout_toRightOf = "@id/temp" > </ TextView > <!-- Image weather condition --> < ImageView android:id = "@+id/condIcon" android:layout_height = "wrap_content" android:layout_width = "wrap_content" android:layout_alignTop = "@id/temp" android:layout_toRightOf = "@id/temp" /> </ RelativeLayout > |
In the second section, as we said we have to show several day forecast. In this case we can use a ViewPager that helps us to swipe between pages, so we have:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 | < android.support.v4.view.ViewPager android:id = "@+id/pager" android:layout_width = "match_parent" android:layout_height = "match_parent" android:layout_weight = "6" > < android.support.v4.view.PagerTitleStrip android:id = "@+id/pager_title_strip" android:layout_width = "match_parent" android:layout_height = "wrap_content" android:layout_gravity = "top" android:background = "#E6E6E6" android:paddingBottom = "4dp" android:paddingTop = "4dp" android:textColor = "#fff" /> </ android.support.v4.view.ViewPager > |
This is our layout. We have now to fill it we the weather data.
HTTP, AsyncTask and json data parsing
The next step is retrieve data using HTTP connection with an asynctask to avoid a ANR problem. Once we have our data we will parse it using JSON parser. Let’s ask we our browser the weather forecast using this link:
http://api.openweathermap.org/data/2.5/forecast/daily?q=Rome,IT&mode=json&units=metric&cnt=3
where cnt is the number of the days we want to have the weather condition. The result is shown below:
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 | { "cod": "200", "message": 0.0192, "city": { "id": "3169070", "name": "Rome", "coord": { "lon": 12.4958, "lat": 41.903 }, "country": "Italy", "population": 0 }, "cnt": 7, "list": [ { "dt": 1377774000, "temp": { "day": 26.83, "min": 16.41, "max": 29.12, "night": 16.41, "eve": 24.81, "morn": 26.83 }, "pressure": 1007.2, "humidity": 72, "weather": [ { "id": 800, "main": "Clear", "description": "sky is clear", "icon": "01d" } ], "speed": 0.71, "deg": 146, "clouds": 0 }, { "dt": 1377860400, "temp": { "day": 29.84, "min": 16.8, "max": 29.84, "night": 16.8, "eve": 24.81, "morn": 17.93 }, "pressure": 1006.68, "humidity": 50, "weather": [ { "id": 800, "main": "Clear", "description": "sky is clear", "icon": "02d" } ], "speed": 2.2, "deg": 235, "clouds": 8 }, { "dt": 1377946800, "temp": { "day": 29.14, "min": 14.11, "max": 29.14, "night": 14.11, "eve": 24.08, "morn": 18.32 }, "pressure": 1005.17, "humidity": 45, "weather": [ { "id": 801, "main": "Clouds", "description": "few clouds", "icon": "02d" } ], "speed": 1.56, "deg": 320, "clouds": 20 }, { "dt": 1378033200, "temp": { "day": 28.43, "min": 12.11, "max": 28.95, "night": 14.28, "eve": 23.89, "morn": 12.11 }, "pressure": 1005.23, "humidity": 39, "weather": [ { "id": 800, "main": "Clear", "description": "sky is clear", "icon": "01d" } ], "speed": 1.55, "deg": 289, "clouds": 0 }, { "dt": 1378119600, "temp": { "day": 29.83, "min": 16.02, "max": 29.83, "night": 18.86, "eve": 25.35, "morn": 16.02 }, "pressure": 1006.57, "humidity": 32, "weather": [ { "id": 800, "main": "Clear", "description": "sky is clear", "icon": "01d" } ], "speed": 3.3, "deg": 292, "clouds": 0 }, { "dt": 1378206000, "temp": { "day": 27.76, "min": 19.68, "max": 27.76, "night": 19.68, "eve": 25, "morn": 20.25 }, "pressure": 1014.89, "humidity": 0, "weather": [ { "id": 800, "main": "Clear", "description": "sky is clear", "icon": "01d" } ], "speed": 4.05, "deg": 298, "clouds": 0 }, { "dt": 1378292400, "temp": { "day": 27.85, "min": 19.91, "max": 27.85, "night": 19.91, "eve": 24.78, "morn": 20.13 }, "pressure": 1017.2, "humidity": 0, "weather": [ { "id": 800, "main": "Clear", "description": "sky is clear", "icon": "01d" } ], "speed": 1.91, "deg": 252, "clouds": 0 } ] } |
Analyzing the result we find out that the information we are looking for are inside the list tag and this an array. To hold this information we can create two classes on that hold the daily forecast and the other that hold the daily forecast classes:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 | public class DayForecast { private static SimpleDateFormat sdf = new SimpleDateFormat( "dd/MM/yyyy" ); public Weather weather = new Weather(); public ForecastTemp forecastTemp = new ForecastTemp(); public long timestamp; public class ForecastTemp { public float day; public float min; public float max; public float night; public float eve; public float morning; } public String getStringDate() { return sdf.format( new Date(timestamp)); } } |
and the other one:
01 02 03 04 05 06 07 08 09 10 11 12 13 | public class WeatherForecast { private List<DayForecast> daysForecast = new ArrayList<DayForecast>(); public void addForecast(DayForecast forecast) { daysForecast.add(forecast); System.out.println( "Add forecast [" +forecast+ "]" ); } public DayForecast getForecast( int dayNum) { return daysForecast.get(dayNum); } } |
Now we have our data model and we have simply parse the JSON data using a simple class like the one used in the previous post (JSONWeatherParser).
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 | public static WeatherForecast getForecastWeather(String data) throws JSONException { WeatherForecast forecast = new WeatherForecast(); // We create out JSONObject from the data JSONObject jObj = new JSONObject(data); JSONArray jArr = jObj.getJSONArray( "list" ); // Here we have the forecast for every day // We traverse all the array and parse the data for ( int i= 0 ; i < jArr.length(); i++) { JSONObject jDayForecast = jArr.getJSONObject(i); // Now we have the json object so we can extract the data DayForecast df = new DayForecast(); // We retrieve the timestamp (dt) df.timestamp = jDayForecast.getLong( "dt" ); // Temp is an object JSONObject jTempObj = jDayForecast.getJSONObject( "temp" ); df.forecastTemp.day = ( float ) jTempObj.getDouble( "day" ); df.forecastTemp.min = ( float ) jTempObj.getDouble( "min" ); df.forecastTemp.max = ( float ) jTempObj.getDouble( "max" ); df.forecastTemp.night = ( float ) jTempObj.getDouble( "night" ); df.forecastTemp.eve = ( float ) jTempObj.getDouble( "eve" ); df.forecastTemp.morning = ( float ) jTempObj.getDouble( "morn" ); // Pressure & Humidity df.weather.currentCondition.setPressure(( float ) jDayForecast.getDouble( "pressure" )); df.weather.currentCondition.setHumidity(( float ) jDayForecast.getDouble( "humidity" )); // ...and now the weather JSONArray jWeatherArr = jDayForecast.getJSONArray( "weather" ); JSONObject jWeatherObj = jWeatherArr.getJSONObject( 0 ); df.weather.currentCondition.setWeatherId(getInt( "id" , jWeatherObj)); df.weather.currentCondition.setDescr(getString( "description" , jWeatherObj)); df.weather.currentCondition.setCondition(getString( "main" , jWeatherObj)); df.weather.currentCondition.setIcon(getString( "icon" , jWeatherObj)); forecast.addForecast(df); } return forecast; } |
Well done! What do we need more?…Well we have to handle the ViewPager and create the adapter to hold every day forecast.
ViewPager, Fragments and the adapter
The first thing we have to create our adapter that handles each fragment that shows the weather daily details. In this case to create our adapter we have to extend FragmentPagerAdapter:
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 | public class DailyForecastPageAdapter extends FragmentPagerAdapter { private int numDays; private FragmentManager fm; private WeatherForecast forecast; private final static SimpleDateFormat sdf = new SimpleDateFormat( "E, dd-MM" ); public DailyForecastPageAdapter( int numDays, FragmentManager fm, WeatherForecast forecast) { super (fm); this .numDays = numDays; this .fm = fm; this .forecast = forecast; } // Page title @Override public CharSequence getPageTitle( int position) { // We calculate the next days adding position to the current date Date d = new Date(); Calendar gc = new GregorianCalendar(); gc.setTime(d); gc.add(GregorianCalendar.DAY_OF_MONTH, position); return sdf.format(gc.getTime()); } @Override public Fragment getItem( int num) { DayForecast dayForecast = (DayForecast) forecast.getForecast(num); DayForecastFragment f = new DayForecastFragment(); f.setForecast(dayForecast); return f; } /* * Number of the days we have the forecast */ @Override public int getCount() { return numDays; } |
Two methods are important: one that “create” the fragment and another one that “create” the page title. The first one instantiate a fragment that shows the daily forecat passing the dayForecast data and the other one create the page tile using GregorianCalendar.
The last step is coding the fragment:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 | public class DayForecastFragment extends Fragment { ... @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v = inflater.inflate(R.layout.dayforecast_fragment, container, false ); TextView tempView = (TextView) v.findViewById(R.id.tempForecast); TextView descView = (TextView) v.findViewById(R.id.skydescForecast); tempView.setText( ( int ) (dayForecast.forecastTemp.min - 275.15 ) + "-" + ( int ) (dayForecast.forecastTemp.max - 275.15 ) ); descView.setText(dayForecast.weather.currentCondition.getDescr()); iconWeather = (ImageView) v.findViewById(R.id.forCondIcon); // Now we retrieve the weather icon JSONIconWeatherTask task = new JSONIconWeatherTask(); task.execute( new String[]{dayForecast.weather.currentCondition.getIcon()}); return v; } ... } |
In the onCreateView we inflate our layout and retrieve with (AsyncTask) the weather icon.
Source code available @ github
A really nice tutorial! I really liked it.. Going to build it soon! :D
I want to implement such a feature as part of my app but I want this to be in a fragment and not a separate activity. Any help will be much appreciated. I have tried a lot to convert this code to fit inside a fragment but the pager.setAdapter(adapter) crashes my app each time and I don’t understand how to fix it.
All I did was convert the MainActivity to extend fragment (which is inside a FragmentActivity) and made the required changes as per a fragment.
The github link throws a 404 error
The source code related to the post is here:
https://github.com/survivingwithandroid/Swa-app/tree/master/WeatherForecastApp
If you want you can checkout my Weatherlib that is based on the above post and helps you to build weather app easily without worrying about protocol implementation details, ANR problem and so on.
Give a look here:
https://github.com/survivingwithandroid/WeatherLib