├── app
├── .gitignore
├── src
│ └── main
│ │ ├── ic_launcher-web.png
│ │ ├── res
│ │ ├── drawable-hdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-mdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-xhdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-xxhdpi
│ │ │ └── ic_launcher.png
│ │ ├── values
│ │ │ ├── colors.xml
│ │ │ ├── dimens.xml
│ │ │ ├── strings.xml
│ │ │ └── styles.xml
│ │ ├── animator
│ │ │ ├── fade_in.xml
│ │ │ └── fade_out.xml
│ │ ├── layout
│ │ │ ├── activity_weather.xml
│ │ │ ├── weather_forecast_list_item.xml
│ │ │ └── fragment_weather.xml
│ │ └── values-w820dp
│ │ │ └── dimens.xml
│ │ ├── java
│ │ └── mu
│ │ │ └── node
│ │ │ └── rexweather
│ │ │ └── app
│ │ │ ├── Helpers
│ │ │ ├── TemperatureFormatter.java
│ │ │ └── DayFormatter.java
│ │ │ ├── Models
│ │ │ ├── CurrentWeather.java
│ │ │ └── WeatherForecast.java
│ │ │ ├── Services
│ │ │ ├── LocationService.java
│ │ │ └── WeatherService.java
│ │ │ └── WeatherActivity.java
│ │ └── AndroidManifest.xml
├── proguard-rules.txt
└── build.gradle
├── settings.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── gradle.properties
├── LICENSE
├── gradlew.bat
├── README.md
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | *.iml
3 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vyshane/rex-weather/HEAD/app/src/main/ic_launcher-web.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vyshane/rex-weather/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vyshane/rex-weather/HEAD/app/src/main/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vyshane/rex-weather/HEAD/app/src/main/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vyshane/rex-weather/HEAD/app/src/main/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vyshane/rex-weather/HEAD/app/src/main/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu May 12 18:14:58 WIT 2016
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #33B5E5
4 | #333333
5 | #666666
6 | #999999
7 |
--------------------------------------------------------------------------------
/app/src/main/java/mu/node/rexweather/app/Helpers/TemperatureFormatter.java:
--------------------------------------------------------------------------------
1 | package mu.node.rexweather.app.Helpers;
2 |
3 | public class TemperatureFormatter {
4 |
5 | public static String format(float temperature) {
6 | return String.valueOf(Math.round(temperature)) + "°";
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/app/src/main/res/animator/fade_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/animator/fade_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_weather.xml:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | #built application files
2 | *.apk
3 |
4 | # files for the dex VM
5 | *.dex
6 |
7 | # Java class files
8 | *.class
9 |
10 | # generated files
11 | bin/
12 | gen/
13 |
14 | # Local configuration file (sdk path, etc)
15 | local.properties
16 |
17 | # Windows thumbnail db
18 | Thumbs.db
19 |
20 | # OSX files
21 | .DS_Store
22 |
23 | # Eclipse project files
24 | .classpath
25 | .project
26 |
27 | # Android Studio
28 | .idea/
29 | .gradle
30 | build/
31 | *.iml
32 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 20dp
3 | 20dp
4 |
5 | 30sp
6 | 60sp
7 | 16sp
8 | 8dp
9 | 12sp
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 | 120sp
7 | 240sp
8 | 64sp
9 | 32dp
10 | 48sp
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | RexWeather
5 | Weather data by OpenWeatherMap.org
6 | Could not fetch weather data
7 | Could not get current location
8 | Today
9 | Tomorrow
10 | Monday
11 | sky is clear
12 | 19°
13 | 15°
14 |
15 |
16 |
--------------------------------------------------------------------------------
/app/proguard-rules.txt:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Applications/Android Studio.app/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the ProGuard
5 | # include property in project.properties.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
--------------------------------------------------------------------------------
/app/src/main/java/mu/node/rexweather/app/Models/CurrentWeather.java:
--------------------------------------------------------------------------------
1 | package mu.node.rexweather.app.Models;
2 |
3 | public class CurrentWeather extends WeatherForecast {
4 | private final float mTemperature; // Current temperature.
5 |
6 | public CurrentWeather(final String locationName,
7 | final long timestamp,
8 | final String description,
9 | final float temperature,
10 | final float minimumTemperature,
11 | final float maximumTemperature) {
12 |
13 | super(locationName, timestamp, description, minimumTemperature, maximumTemperature);
14 | mTemperature = temperature;
15 | }
16 |
17 | public float getTemperature() {
18 | return mTemperature;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Settings specified in this file will override any Gradle settings
5 | # configured through the IDE.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
13 |
14 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 21
5 | buildToolsVersion "21.1.2"
6 |
7 | defaultConfig {
8 | minSdkVersion 16
9 | targetSdkVersion 21
10 | versionCode 1
11 | versionName "1.0"
12 | }
13 | lintOptions {
14 | disable 'InvalidPackage'
15 | abortOnError false
16 | }
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
21 | }
22 | }
23 | }
24 |
25 | dependencies {
26 | compile 'com.android.support:support-v4:21.+'
27 | compile 'io.reactivex:rxjava:1.0.+'
28 | compile 'io.reactivex:rxandroid:0.23.+'
29 | compile 'com.squareup.retrofit:retrofit:1.8.+'
30 | compile 'com.squareup.okio:okio:1.1.+'
31 | compile 'com.squareup.okhttp:okhttp:2.1.+'
32 | compile 'com.squareup.okhttp:okhttp-urlconnection:2.1.+'
33 | compile 'de.keyboardsurfer.android.widget:crouton:1.8.5@jar'
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/weather_forecast_list_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
14 |
15 |
20 |
21 |
26 |
27 |
32 |
33 |
--------------------------------------------------------------------------------
/app/src/main/java/mu/node/rexweather/app/Models/WeatherForecast.java:
--------------------------------------------------------------------------------
1 | package mu.node.rexweather.app.Models;
2 |
3 | public class WeatherForecast {
4 | private final String mLocationName;
5 | private final long mTimestamp;
6 | private final String mDescription;
7 | private final float mMinimumTemperature;
8 | private final float mMaximumTemperature;
9 |
10 | public WeatherForecast(final String locationName,
11 | final long timestamp,
12 | final String description,
13 | final float minimumTemperature,
14 | final float maximumTemperature) {
15 |
16 | mLocationName = locationName;
17 | mTimestamp = timestamp;
18 | mMinimumTemperature = minimumTemperature;
19 | mMaximumTemperature = maximumTemperature;
20 | mDescription = description;
21 | }
22 |
23 | public String getLocationName() {
24 | return mLocationName;
25 | }
26 |
27 | public long getTimestamp() {
28 | return mTimestamp;
29 | }
30 |
31 | public String getDescription() {
32 | return mDescription;
33 | }
34 |
35 | public float getMinimumTemperature() {
36 | return mMinimumTemperature;
37 | }
38 |
39 | public float getMaximumTemperature() {
40 | return mMaximumTemperature;
41 | }
42 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014, Vy-Shane Sin Fat
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | * Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | * Neither the name of the {organization} nor the names of its
15 | contributors may be used to endorse or promote products derived from
16 | this software without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/app/src/main/java/mu/node/rexweather/app/Helpers/DayFormatter.java:
--------------------------------------------------------------------------------
1 | package mu.node.rexweather.app.Helpers;
2 |
3 | import android.content.Context;
4 |
5 | import java.text.SimpleDateFormat;
6 | import java.util.Date;
7 |
8 | import mu.node.rexweather.app.R;
9 |
10 | /**
11 | * Quick and dirty day formatter helper class.
12 | */
13 | public class DayFormatter {
14 | private final static long MILLISECONDS_IN_SECONDS = 1000;
15 | private final Context mContext;
16 |
17 | public DayFormatter(Context context) {
18 | mContext = context;
19 | }
20 |
21 | /**
22 | * Format a Unix timestamp into a human readable week day String such as "Today", "Tomorrow"
23 | * and "Wednesday"
24 | */
25 | public String format(final long unixTimestamp) {
26 | final long milliseconds = unixTimestamp * MILLISECONDS_IN_SECONDS;
27 | String day;
28 |
29 | if (isToday(milliseconds)) {
30 | day = mContext.getResources().getString(R.string.today);
31 | } else if (isTomorrow(milliseconds)) {
32 | day = mContext.getResources().getString(R.string.tomorrow);
33 | } else {
34 | day = getDayOfWeek(milliseconds);
35 | }
36 |
37 | return day;
38 | }
39 |
40 | private String getDayOfWeek(final long milliseconds) {
41 | return new SimpleDateFormat("EEEE").format(new Date(milliseconds));
42 | }
43 |
44 | private boolean isToday(final long milliseconds) {
45 | final SimpleDateFormat dayInYearFormat = new SimpleDateFormat("yyyyD");
46 | final String nowHash = dayInYearFormat.format(new Date());
47 | final String comparisonHash = dayInYearFormat.format(new Date(milliseconds));
48 | return nowHash.equals(comparisonHash);
49 | }
50 |
51 | private boolean isTomorrow(final long milliseconds) {
52 | final SimpleDateFormat dayInYearFormat = new SimpleDateFormat("yyyyD");
53 | final int tomorrowHash = Integer.parseInt(dayInYearFormat.format(new Date())) + 1;
54 | final int comparisonHash = Integer.parseInt(dayInYearFormat.format(new Date(milliseconds)));
55 | return comparisonHash == tomorrowHash;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
14 |
15 |
19 |
20 |
26 |
27 |
34 |
35 |
38 |
39 |
42 |
43 |
47 |
48 |
--------------------------------------------------------------------------------
/app/src/main/java/mu/node/rexweather/app/Services/LocationService.java:
--------------------------------------------------------------------------------
1 | package mu.node.rexweather.app.Services;
2 |
3 | import android.location.Criteria;
4 | import android.location.Location;
5 | import android.location.LocationListener;
6 | import android.location.LocationManager;
7 | import android.os.Bundle;
8 | import android.os.Looper;
9 |
10 | import rx.Observable;
11 | import rx.Subscriber;
12 |
13 | /**
14 | * Implement an Rx-style location service by wrapping the Android LocationManager and providing
15 | * the location result as an Observable.
16 | */
17 | public class LocationService {
18 | private final LocationManager mLocationManager;
19 |
20 | public LocationService(LocationManager locationManager) {
21 | mLocationManager = locationManager;
22 | }
23 |
24 | public Observable getLocation() {
25 | return Observable.create(new Observable.OnSubscribe() {
26 | @Override
27 | public void call(final Subscriber super Location> subscriber) {
28 |
29 | final LocationListener locationListener = new LocationListener() {
30 | public void onLocationChanged(final Location location) {
31 | subscriber.onNext(location);
32 | subscriber.onCompleted();
33 |
34 | Looper.myLooper().quit();
35 | }
36 |
37 | public void onStatusChanged(String provider, int status, Bundle extras) {
38 | }
39 |
40 | public void onProviderEnabled(String provider) {
41 | }
42 |
43 | public void onProviderDisabled(String provider) {
44 | }
45 | };
46 |
47 | final Criteria locationCriteria = new Criteria();
48 | locationCriteria.setAccuracy(Criteria.ACCURACY_COARSE);
49 | locationCriteria.setPowerRequirement(Criteria.POWER_LOW);
50 | final String locationProvider = mLocationManager
51 | .getBestProvider(locationCriteria, true);
52 |
53 | Looper.prepare();
54 |
55 | mLocationManager.requestSingleUpdate(locationProvider,
56 | locationListener, Looper.myLooper());
57 |
58 | Looper.loop();
59 | }
60 | });
61 | }
62 | }
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_weather.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
17 |
18 |
19 |
24 |
25 |
26 |
33 |
34 |
40 |
41 |
46 |
47 |
48 |
49 |
50 |
55 |
56 |
57 |
58 |
59 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # RexWeather
2 |
3 | RexWeather is a sample Android Studio project demonstrating the use of
4 | Retrofit and RxJava to interact with web services.
5 | [Link to the blog post](https://vyshane.com/2014/07/02/using-retrofit-and-rxjava-to-interact-with-web-services-on-android/).
6 |
7 | 
8 |
9 |
10 | ## Retrofit, by Square
11 |
12 | [Retrofit](http://square.github.io/retrofit) is a REST client for Android and
13 | Java. It allows you to turn a REST API into a Java interface by using
14 | annotations to describe the HTTP requests. It can then generate an
15 | implementation of the interface for you. This means that you can go from
16 | `GET /users/{userId}/posts` to `webService.fetchUserPosts(userId)` in a few
17 | lines of code. Retrofit is very easy to use and it integrates well with RxJava.
18 |
19 |
20 | ## RxJava, by Netflix
21 |
22 | [RxJava](https://github.com/Netflix/RxJava) is a Java implementation of Rx, the
23 | Reactive Extensions library from the .NET world. It allows you to compose
24 | asynchronous and event-based programs in a declarative manner. Let's say that
25 | you want to implement something like this:
26 |
27 | * Start observing our current location. When a location change happens, in
28 | parallel,
29 | * Send a web service request A
30 | * Send a web service request B
31 | * Using the result from B, send a web service request C
32 | * When the results for both A and C are back, update the UI
33 |
34 | RxJava allows you to write out your program pretty much as above. It abstracts
35 | out concerns about things like threading, synchronization, thread-safety,
36 | concurrent data structures, and non-blocking I/O. You can tell RxJava to
37 | observe the location changes and perform the web service requests in a
38 | background thread, and pass you the final results in the UI thread. If any
39 | exceptions are thrown at any point in the chain of events, you get told about
40 | it in one convenient place. You're not drowning in a spaghetti of onSuccess and
41 | onError callbacks. You're building pipelines.
42 |
43 |
44 | ## Web Service Calls
45 |
46 | The app uses [OpenWeatherMap](http://openweathermap.org/) to fetch the current
47 | weather as well as the seven day forecast.
48 |
49 | Because of the new policy of [OpenWeatherMap](http://openweathermap.org/),
50 | we need to provide API key to the requests we made. You can see the detailed information
51 | in [here](http://openweathermap.org/faq#error401). Please insert your API key that you get
52 | after register from [OpenWeatherMap](http://openweathermap.org/) to WeatherService.java
53 |
54 | ### Current Weather
55 |
56 | The current weather can be obtained by making a call similar to the following:
57 |
58 | ```
59 | http://api.openweathermap.org/data/2.5/weather?lat=-31.9522&lon=115.8589&units=metric
60 | ```
61 |
62 | The returned JSON looks like this:
63 |
64 | ```javascript
65 | {
66 | "coord": {
67 | "lon": 115.86,
68 | "lat": -31.95
69 | },
70 | "sys": {
71 | "message": 0.1843,
72 | "country": "AU",
73 | "sunrise": 1404602233,
74 | "sunset": 1404638729
75 | },
76 | "weather": [
77 | {
78 | "id": 802,
79 | "main": "Clouds",
80 | "description": "scattered clouds",
81 | "icon": "03n"
82 | }
83 | ],
84 | "base": "cmc stations",
85 | "main": {
86 | "temp": 13,
87 | "pressure": 1015,
88 | "humidity": 71,
89 | "temp_min": 13,
90 | "temp_max": 13
91 | },
92 | "wind": {
93 | "speed": 3.1,
94 | "deg": 300
95 | },
96 | "rain": {
97 | "3h": 0
98 | },
99 | "clouds": {
100 | "all": 40
101 | },
102 | "dt": 1404657000,
103 | "id": 6692202,
104 | "name": "South Perth",
105 | "cod": 200
106 | }
107 | ```
108 |
109 | ### 7 Day Forecast
110 |
111 | The 7 day forecast can be obtained like so:
112 |
113 | ```
114 | http://api.openweathermap.org/data/2.5/forecast/daily?lat=-31.9522&lon=115.8589&mode=json&units=metric&cnt=7
115 | ```
116 |
117 | And the returned JSON looks like this:
118 |
119 | ```javascript
120 | {
121 | "cod": "200",
122 | "message": 0.0094,
123 | "city": {
124 | "id": 2063523,
125 | "name": "South Perth",
126 | "coord": {
127 | "lon": 115.833328,
128 | "lat": -31.933331
129 | },
130 | "country": "AU",
131 | "population": 0
132 | },
133 | "cnt": 7,
134 | "list": [
135 | {
136 | "dt": 1404619200,
137 | "temp": {
138 | "day": 13,
139 | "min": 13,
140 | "max": 13.31,
141 | "night": 13.31,
142 | "eve": 13,
143 | "morn": 13
144 | },
145 | "pressure": 1016.99,
146 | "humidity": 100,
147 | "weather": [
148 | {
149 | "id": 501,
150 | "main": "Rain",
151 | "description": "moderate rain",
152 | "icon": "10d"
153 | }
154 | ],
155 | "speed": 7.25,
156 | "deg": 225,
157 | "clouds": 92,
158 | "rain": 4
159 | },
160 | ]
161 |
162 | // Six more entries ...
163 |
164 | }
165 | ```
166 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/app/src/main/java/mu/node/rexweather/app/Services/WeatherService.java:
--------------------------------------------------------------------------------
1 | package mu.node.rexweather.app.Services;
2 |
3 | import com.google.gson.annotations.SerializedName;
4 |
5 | import org.apache.http.HttpException;
6 |
7 | import java.util.ArrayList;
8 | import java.util.List;
9 |
10 | import mu.node.rexweather.app.Models.CurrentWeather;
11 | import mu.node.rexweather.app.Models.WeatherForecast;
12 | import retrofit.RequestInterceptor;
13 | import retrofit.RestAdapter;
14 | import retrofit.http.GET;
15 | import retrofit.http.Query;
16 | import rx.Observable;
17 | import rx.functions.Func1;
18 |
19 | public class WeatherService {
20 | // We are implementing against version 2.5 of the Open Weather Map web service.
21 | private static final String WEB_SERVICE_BASE_URL = "http://api.openweathermap.org/data/2.5";
22 | private static final String API_KEY = "insert your api key here";
23 | private final OpenWeatherMapWebService mWebService;
24 |
25 | public WeatherService() {
26 | RequestInterceptor requestInterceptor = new RequestInterceptor() {
27 | @Override
28 | public void intercept(RequestInterceptor.RequestFacade request) {
29 | request.addHeader("Accept", "application/json");
30 | }
31 | };
32 |
33 | RestAdapter restAdapter = new RestAdapter.Builder()
34 | .setEndpoint(WEB_SERVICE_BASE_URL)
35 | .setRequestInterceptor(requestInterceptor)
36 | .setLogLevel(RestAdapter.LogLevel.FULL)
37 | .build();
38 |
39 | mWebService = restAdapter.create(OpenWeatherMapWebService.class);
40 | }
41 |
42 | private interface OpenWeatherMapWebService {
43 | @GET("/weather?units=metric&apikey=" + API_KEY)
44 | Observable fetchCurrentWeather(@Query("lon") double longitude,
45 | @Query("lat") double latitude);
46 |
47 | @GET("/forecast/daily?units=metric&cnt=7&apikey=" + API_KEY)
48 | Observable fetchWeatherForecasts(
49 | @Query("lon") double longitude, @Query("lat") double latitude);
50 | }
51 |
52 | public Observable fetchCurrentWeather(final double longitude,
53 | final double latitude) {
54 | return mWebService.fetchCurrentWeather(longitude, latitude)
55 | .flatMap(new Func1>() {
57 |
58 | // Error out if the request was not successful.
59 | @Override
60 | public Observable extends CurrentWeatherDataEnvelope> call(
61 | final CurrentWeatherDataEnvelope data) {
62 | return data.filterWebServiceErrors();
63 | }
64 |
65 | }).map(new Func1() {
66 |
67 | // Parse the result and build a CurrentWeather object.
68 | @Override
69 | public CurrentWeather call(final CurrentWeatherDataEnvelope data) {
70 | return new CurrentWeather(data.locationName, data.timestamp,
71 | data.weather.get(0).description, data.main.temp,
72 | data.main.temp_min, data.main.temp_max);
73 | }
74 | });
75 | }
76 |
77 | public Observable> fetchWeatherForecasts(final double longitude,
78 | final double latitude) {
79 | return mWebService.fetchWeatherForecasts(longitude, latitude)
80 | .flatMap(new Func1>() {
82 |
83 | // Error out if the request was not successful.
84 | @Override
85 | public Observable extends WeatherForecastListDataEnvelope> call(
86 | final WeatherForecastListDataEnvelope listData) {
87 | return listData.filterWebServiceErrors();
88 | }
89 |
90 | }).map(new Func1>() {
91 |
92 | // Parse the result and build a list of WeatherForecast objects.
93 | @Override
94 | public List call(final WeatherForecastListDataEnvelope listData) {
95 | final ArrayList weatherForecasts =
96 | new ArrayList<>();
97 |
98 | for (WeatherForecastListDataEnvelope.ForecastDataEnvelope data : listData.list) {
99 | final WeatherForecast weatherForecast = new WeatherForecast(
100 | listData.city.name, data.timestamp, data.weather.get(0).description,
101 | data.temp.min, data.temp.max);
102 | weatherForecasts.add(weatherForecast);
103 | }
104 |
105 | return weatherForecasts;
106 | }
107 | });
108 | }
109 |
110 | /**
111 | * Base class for results returned by the weather web service.
112 | */
113 | private class WeatherDataEnvelope {
114 | @SerializedName("cod")
115 | private int httpCode;
116 |
117 | class Weather {
118 | public String description;
119 | }
120 |
121 | /**
122 | * The web service always returns a HTTP header code of 200 and communicates errors
123 | * through a 'cod' field in the JSON payload of the response body.
124 | */
125 | public Observable filterWebServiceErrors() {
126 | if (httpCode == 200) {
127 | return Observable.just(this);
128 | } else {
129 | return Observable.error(
130 | new HttpException("There was a problem fetching the weather data."));
131 | }
132 | }
133 | }
134 |
135 | /**
136 | * Data structure for current weather results returned by the web service.
137 | */
138 | private class CurrentWeatherDataEnvelope extends WeatherDataEnvelope {
139 | @SerializedName("name")
140 | public String locationName;
141 | @SerializedName("dt")
142 | public long timestamp;
143 | public ArrayList weather;
144 | public Main main;
145 |
146 | class Main {
147 | public float temp;
148 | public float temp_min;
149 | public float temp_max;
150 | }
151 | }
152 |
153 | /**
154 | * Data structure for weather forecast results returned by the web service.
155 | */
156 | private class WeatherForecastListDataEnvelope extends WeatherDataEnvelope {
157 | public Location city;
158 | public ArrayList list;
159 |
160 | class Location {
161 | public String name;
162 | }
163 |
164 | class ForecastDataEnvelope {
165 | @SerializedName("dt")
166 | public long timestamp;
167 | public Temperature temp;
168 | public ArrayList weather;
169 | }
170 |
171 | class Temperature {
172 | public float min;
173 | public float max;
174 | }
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/app/src/main/java/mu/node/rexweather/app/WeatherActivity.java:
--------------------------------------------------------------------------------
1 | package mu.node.rexweather.app;
2 |
3 | import android.app.Activity;
4 | import android.app.Fragment;
5 | import android.content.Context;
6 | import android.location.Location;
7 | import android.location.LocationManager;
8 | import android.os.Bundle;
9 | import android.support.v4.widget.SwipeRefreshLayout;
10 | import android.util.Log;
11 | import android.view.LayoutInflater;
12 | import android.view.View;
13 | import android.view.ViewGroup;
14 | import android.widget.ArrayAdapter;
15 | import android.widget.ListView;
16 | import android.widget.TextView;
17 |
18 | import org.apache.http.HttpException;
19 |
20 | import java.util.ArrayList;
21 | import java.util.HashMap;
22 | import java.util.List;
23 | import java.util.concurrent.TimeUnit;
24 | import java.util.concurrent.TimeoutException;
25 |
26 | import de.keyboardsurfer.android.widget.crouton.Crouton;
27 | import de.keyboardsurfer.android.widget.crouton.Style;
28 | import mu.node.rexweather.app.Helpers.DayFormatter;
29 | import mu.node.rexweather.app.Helpers.TemperatureFormatter;
30 | import mu.node.rexweather.app.Models.CurrentWeather;
31 | import mu.node.rexweather.app.Models.WeatherForecast;
32 | import mu.node.rexweather.app.Services.LocationService;
33 | import mu.node.rexweather.app.Services.WeatherService;
34 | import retrofit.RetrofitError;
35 | import rx.Observable;
36 | import rx.Subscriber;
37 | import rx.android.schedulers.AndroidSchedulers;
38 | import rx.functions.Func1;
39 | import rx.functions.Func2;
40 | import rx.schedulers.Schedulers;
41 | import rx.subscriptions.CompositeSubscription;
42 |
43 | /**
44 | * Weather Activity.
45 | *
46 | * This is the main activity for our app. It simply serves as a container for the Weather Fragment.
47 | * We prefer to build our implementation in a fragment because that enables future reuse if, for
48 | * example we build a tablet version of this app.
49 | */
50 | public class WeatherActivity extends Activity {
51 |
52 | @Override
53 | protected void onCreate(final Bundle savedInstanceState) {
54 | super.onCreate(savedInstanceState);
55 | setContentView(R.layout.activity_weather);
56 |
57 | if (savedInstanceState == null) {
58 | getFragmentManager().beginTransaction()
59 | .add(R.id.container, new WeatherFragment())
60 | .commit();
61 | }
62 | }
63 |
64 | /**
65 | * Weather Fragment.
66 | *
67 | * Displays the current weather as well as a 7 day forecast for our location. Data is loaded
68 | * from a web service.
69 | */
70 | public static class WeatherFragment extends Fragment {
71 |
72 | private static final String KEY_CURRENT_WEATHER = "key_current_weather";
73 | private static final String KEY_WEATHER_FORECASTS = "key_weather_forecasts";
74 | private static final long LOCATION_TIMEOUT_SECONDS = 20;
75 | private static final String TAG = WeatherFragment.class.getCanonicalName();
76 |
77 | private CompositeSubscription mCompositeSubscription;
78 | private SwipeRefreshLayout mSwipeRefreshLayout;
79 | private TextView mLocationNameTextView;
80 | private TextView mCurrentTemperatureTextView;
81 | private ListView mForecastListView;
82 | private TextView mAttributionTextView;
83 |
84 | @Override
85 | public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
86 | final Bundle savedInstanceState) {
87 | mCompositeSubscription = new CompositeSubscription();
88 |
89 | final View rootView = inflater.inflate(R.layout.fragment_weather, container, false);
90 | mLocationNameTextView = (TextView) rootView.findViewById(R.id.location_name);
91 | mCurrentTemperatureTextView = (TextView) rootView
92 | .findViewById(R.id.current_temperature);
93 |
94 | // Set up list view for weather forecasts.
95 | mForecastListView = (ListView) rootView.findViewById(R.id.weather_forecast_list);
96 | final WeatherForecastListAdapter adapter = new WeatherForecastListAdapter(
97 | new ArrayList(), getActivity());
98 | mForecastListView.setAdapter(adapter);
99 |
100 | mAttributionTextView = (TextView) rootView.findViewById(R.id.attribution);
101 | mAttributionTextView.setVisibility(View.INVISIBLE);
102 |
103 | // Set up swipe refresh layout.
104 | mSwipeRefreshLayout = (SwipeRefreshLayout) rootView
105 | .findViewById(R.id.swipe_refresh_container);
106 | mSwipeRefreshLayout.setColorSchemeResources(R.color.brand_main,
107 | android.R.color.black,
108 | R.color.brand_main,
109 | android.R.color.black);
110 | mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
111 | @Override
112 | public void onRefresh() {
113 | updateWeather();
114 | }
115 | });
116 |
117 | updateWeather();
118 | return rootView;
119 | }
120 |
121 | @Override
122 | public void onDestroyView() {
123 | mCompositeSubscription.unsubscribe();
124 | super.onDestroyView();
125 | }
126 |
127 | /**
128 | * Provides items for our list view.
129 | */
130 | private class WeatherForecastListAdapter extends ArrayAdapter {
131 |
132 | public WeatherForecastListAdapter(final List weatherForecasts,
133 | final Context context) {
134 | super(context, 0, weatherForecasts);
135 | }
136 |
137 | @Override
138 | public boolean isEnabled(final int position) {
139 | return false;
140 | }
141 |
142 | @Override
143 | public View getView(final int position, View convertView, final ViewGroup parent) {
144 | ViewHolder viewHolder;
145 |
146 | if (convertView == null) {
147 | final LayoutInflater layoutInflater = LayoutInflater.from(getContext());
148 | convertView = layoutInflater.inflate(R.layout.weather_forecast_list_item, null);
149 |
150 | viewHolder = new ViewHolder();
151 | viewHolder.dayTextView = (TextView) convertView.findViewById(R.id.day);
152 | viewHolder.descriptionTextView = (TextView) convertView
153 | .findViewById(R.id.description);
154 | viewHolder.maximumTemperatureTextView = (TextView) convertView
155 | .findViewById(R.id.maximum_temperature);
156 | viewHolder.minimumTemperatureTextView = (TextView) convertView
157 | .findViewById(R.id.minimum_temperature);
158 | convertView.setTag(viewHolder);
159 | } else {
160 | viewHolder = (ViewHolder) convertView.getTag();
161 | }
162 |
163 | final WeatherForecast weatherForecast = (WeatherForecast) getItem(position);
164 |
165 | final DayFormatter dayFormatter = new DayFormatter(getActivity());
166 | final String day = dayFormatter.format(weatherForecast.getTimestamp());
167 | viewHolder.dayTextView.setText(day);
168 | viewHolder.descriptionTextView.setText(weatherForecast.getDescription());
169 | viewHolder.maximumTemperatureTextView.setText(
170 | TemperatureFormatter.format(weatherForecast.getMaximumTemperature()));
171 | viewHolder.minimumTemperatureTextView.setText(
172 | TemperatureFormatter.format(weatherForecast.getMinimumTemperature()));
173 |
174 | return convertView;
175 | }
176 |
177 | /**
178 | * Cache to avoid doing expensive findViewById() calls for each getView().
179 | */
180 | private class ViewHolder {
181 | private TextView dayTextView;
182 | private TextView descriptionTextView;
183 | private TextView maximumTemperatureTextView;
184 | private TextView minimumTemperatureTextView;
185 | }
186 | }
187 |
188 | /**
189 | * Get weather data for the current location and update the UI.
190 | */
191 | private void updateWeather() {
192 | mSwipeRefreshLayout.setRefreshing(true);
193 |
194 | final LocationManager locationManager = (LocationManager) getActivity()
195 | .getSystemService(Context.LOCATION_SERVICE);
196 | final LocationService locationService = new LocationService(locationManager);
197 |
198 | // Get our current location.
199 | final Observable fetchDataObservable = locationService.getLocation()
200 | .timeout(LOCATION_TIMEOUT_SECONDS, TimeUnit.SECONDS)
201 | .flatMap(new Func1>>() {
202 | @Override
203 | public Observable> call(final Location location) {
204 | final WeatherService weatherService = new WeatherService();
205 | final double longitude = location.getLongitude();
206 | final double latitude = location.getLatitude();
207 |
208 | return Observable.zip(
209 | // Fetch current and 7 day forecasts for the location.
210 | weatherService.fetchCurrentWeather(longitude, latitude),
211 | weatherService.fetchWeatherForecasts(longitude, latitude),
212 |
213 | // Only handle the fetched results when both sets are available.
214 | new Func2,
215 | HashMap>() {
216 | @Override
217 | public HashMap call(final CurrentWeather currentWeather,
218 | final List weatherForecasts) {
219 |
220 | HashMap weatherData = new HashMap();
221 | weatherData.put(KEY_CURRENT_WEATHER, currentWeather);
222 | weatherData.put(KEY_WEATHER_FORECASTS, weatherForecasts);
223 | return weatherData;
224 | }
225 | }
226 | );
227 | }
228 | });
229 |
230 | mCompositeSubscription.add(fetchDataObservable
231 | .subscribeOn(Schedulers.newThread())
232 | .observeOn(AndroidSchedulers.mainThread())
233 | .subscribe(new Subscriber>() {
234 | @Override
235 | public void onNext(final HashMap weatherData) {
236 | // Update UI with current weather.
237 | final CurrentWeather currentWeather = (CurrentWeather) weatherData
238 | .get(KEY_CURRENT_WEATHER);
239 | mLocationNameTextView.setText(currentWeather.getLocationName());
240 | mCurrentTemperatureTextView.setText(
241 | TemperatureFormatter.format(currentWeather.getTemperature()));
242 |
243 | // Update weather forecast list.
244 | final List weatherForecasts = (List)
245 | weatherData.get(KEY_WEATHER_FORECASTS);
246 | final WeatherForecastListAdapter adapter = (WeatherForecastListAdapter)
247 | mForecastListView.getAdapter();
248 | adapter.clear();
249 | adapter.addAll(weatherForecasts);
250 | }
251 |
252 | @Override
253 | public void onCompleted() {
254 | mSwipeRefreshLayout.setRefreshing(false);
255 | mAttributionTextView.setVisibility(View.VISIBLE);
256 | }
257 |
258 | @Override
259 | public void onError(final Throwable error) {
260 | mSwipeRefreshLayout.setRefreshing(false);
261 |
262 | if (error instanceof TimeoutException) {
263 | Crouton.makeText(getActivity(),
264 | R.string.error_location_unavailable, Style.ALERT).show();
265 | } else if (error instanceof RetrofitError
266 | || error instanceof HttpException) {
267 | Crouton.makeText(getActivity(),
268 | R.string.error_fetch_weather, Style.ALERT).show();
269 | } else {
270 | Log.e(TAG, error.getMessage());
271 | error.printStackTrace();
272 | throw new RuntimeException("See inner exception");
273 | }
274 | }
275 | })
276 | );
277 | }
278 | }
279 | }
280 |
--------------------------------------------------------------------------------