Skip to content

Commit

Permalink
rewritten to use OkHttp and a new NWS API
Browse files Browse the repository at this point in the history
  • Loading branch information
commonsguy committed Aug 7, 2017
1 parent 843e26d commit 8ae3487
Show file tree
Hide file tree
Showing 31 changed files with 486 additions and 493 deletions.
2 changes: 1 addition & 1 deletion Location/Background/app/build.gradle
@@ -1,7 +1,7 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'


android { android {
compileSdkVersion 25 compileSdkVersion 26
buildToolsVersion "25.0.3" buildToolsVersion "25.0.3"
defaultConfig { defaultConfig {
applicationId "com.commonsware.android.location.background" applicationId "com.commonsware.android.location.background"
Expand Down
Expand Up @@ -88,11 +88,7 @@ public boolean onPreferenceChange(Preference pref, Object newValue) {


if (value) { if (value) {
if (Build.VERSION.SDK_INT>Build.VERSION_CODES.N_MR1) { if (Build.VERSION.SDK_INT>Build.VERSION_CODES.N_MR1) {
Notification fg=LocationPollerService.buildNotification(getActivity()); getActivity().startForegroundService(i);
NotificationManager mgr=
getActivity().getSystemService(NotificationManager.class);

launchServiceInForeground(mgr, fg, i);
} }
else { else {
getActivity().startService(i); getActivity().startService(i);
Expand All @@ -107,21 +103,6 @@ public boolean onPreferenceChange(Preference pref, Object newValue) {
return(true); return(true);
} }


private void launchServiceInForeground(NotificationManager mgr,
Notification fg, Intent i) {
try {
Method startServiceInForeground=
mgr.getClass().getMethod("startServiceInForeground", Intent.class,
int.class, Notification.class);

startServiceInForeground.invoke(mgr, i, 1337, fg);
}
catch (Exception e) {
Log.e(getClass().getSimpleName(), "Could not invoke startServiceInForeground()", e);
Toast.makeText(getActivity(), R.string.msg_error, Toast.LENGTH_LONG).show();
}
}

private void updateSummary(ListPreference pref, String value) { private void updateSummary(ListPreference pref, String value) {
if (value==null || value.length()==0) { if (value==null || value.length()==0) {
pref.setSummary(R.string.missing_text); pref.setSummary(R.string.missing_text);
Expand Down
2 changes: 1 addition & 1 deletion Location/Background/build.gradle
Expand Up @@ -5,7 +5,7 @@ buildscript {
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:2.3.2' classpath 'com.android.tools.build:gradle:2.3.3'


// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files
Expand Down
15 changes: 8 additions & 7 deletions Location/Classic/app/build.gradle
@@ -1,17 +1,18 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'


dependencies { dependencies {
compile 'com.android.support:support-v4:24.2.0' compile 'com.android.support:support-compat:26.0.0'
compile 'com.squareup.picasso:picasso:2.4.0' compile 'com.squareup.picasso:picasso:2.5.2'
compile 'com.squareup.okhttp3:okhttp:3.4.1' compile 'com.squareup.retrofit2:converter-gson:2.3.0'
compile 'com.squareup.okhttp3:okhttp:3.8.1'
} }


android { android {
compileSdkVersion 24 compileSdkVersion 26
buildToolsVersion "25.0.3" buildToolsVersion "26.0.1"


defaultConfig { defaultConfig {
minSdkVersion 14 minSdkVersion 15
targetSdkVersion 24 targetSdkVersion 26
} }
} }
@@ -1,45 +1,27 @@
/*** /***
Copyright (c) 2008-2012 CommonsWare, LLC Copyright (c) 2013-2016 CommonsWare, LLC
Licensed under the Apache License, Version 2.0 (the "License"); you may not Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance with the License. You may obtain a copy use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0. Unless required of the License at http://www.apache.org/licenses/LICENSE-2.0. Unless required
by applicable law or agreed to in writing, software distributed under the by applicable law or agreed to in writing, software distributed under the
License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
OF ANY KIND, either express or implied. See the License for the specific OF ANY KIND, either express or implied. See the License for the specific
language governing permissions and limitations under the License. language governing permissions and limitations under the License.
Covered in detail in the book _The Busy Coder's Guide to Android Development_ Covered in detail in the book _The Busy Coder's Guide to Android Development_
https://commonsware.com/Android https://commonsware.com/Android
*/ */


package com.commonsware.android.weather2; package com.commonsware.android.weather2;


class Forecast { import retrofit2.Call;
String time=""; import retrofit2.http.GET;
Integer temp=null; import retrofit2.http.Headers;
String iconUrl=""; import retrofit2.http.Path;

String getTime() {
return(time);
}


void setTime(String time) { public interface NWSInterface {
this.time=time.substring(0,16).replace('T', ' '); @Headers("Accept: application/geo+json")
} @GET("/points/{lat},{lon}/forecast")

Call<WeatherResponse> getForecast(@Path("lat") double latitude,
Integer getTemp() { @Path("lon") double longitude);
return(temp);
}

void setTemp(Integer temp) {
this.temp=temp;
}

String getIcon() {
return(iconUrl);
}

void setIcon(String iconUrl) {
this.iconUrl=iconUrl;
}
} }
Expand Up @@ -14,14 +14,15 @@


package com.commonsware.android.weather2; package com.commonsware.android.weather2;


import android.annotation.SuppressLint;
import android.app.ListFragment; import android.app.ListFragment;
import android.content.Context; import android.content.Context;
import android.location.Location; import android.location.Location;
import android.location.LocationListener; import android.location.LocationListener;
import android.location.LocationManager; import android.location.LocationManager;
import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
Expand All @@ -30,35 +31,40 @@
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.squareup.picasso.Picasso; import com.squareup.picasso.Picasso;
import org.w3c.dom.Document; import java.text.ParseException;
import org.w3c.dom.Element; import java.text.SimpleDateFormat;
import org.w3c.dom.NodeList; import java.util.Date;
import org.xml.sax.InputSource;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.xml.parsers.DocumentBuilder; import retrofit2.Call;
import javax.xml.parsers.DocumentBuilderFactory; import retrofit2.Callback;
import okhttp3.OkHttpClient; import retrofit2.Response;
import okhttp3.Request; import retrofit2.Retrofit;
import okhttp3.Response; import retrofit2.converter.gson.GsonConverterFactory;


public class WeatherFragment extends ListFragment implements public class WeatherFragment extends ListFragment implements
LocationListener { LocationListener {
private String template; @SuppressLint("SimpleDateFormat")
private static final SimpleDateFormat ISO8601=
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
private LocationManager mgr; private LocationManager mgr;
private ForecastAdapter adapter; private ForecastAdapter adapter;
private final OkHttpClient client=new OkHttpClient(); private NWSInterface nws;


@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setRetainInstance(true); setRetainInstance(true);


template=getActivity().getString(R.string.url);
mgr=(LocationManager)getActivity() mgr=(LocationManager)getActivity()
.getSystemService(Context.LOCATION_SERVICE); .getSystemService(Context.LOCATION_SERVICE);

Retrofit retrofit=
new Retrofit.Builder()
.baseUrl("https://api.weather.gov")
.addConverterFactory(GsonConverterFactory.create())
.build();

nws=retrofit.create(NWSInterface.class);
} }


@Override @Override
Expand Down Expand Up @@ -89,7 +95,32 @@ public void onStop() {


@Override @Override
public void onLocationChanged(Location location) { public void onLocationChanged(Location location) {
new FetchForecastTask().execute(location); double roundedLat=(double)Math.round(location.getLatitude()*10000d)/10000d;
double roundedLon=(double)Math.round(location.getLongitude()*10000d)/10000d;

nws.getForecast(roundedLat, roundedLon)
.enqueue(new Callback<WeatherResponse>() {
@Override
public void onResponse(Call<WeatherResponse> call,
Response<WeatherResponse> response) {
if (response.code()==200) {
adapter=new ForecastAdapter(response.body().properties.periods);
setListAdapter(adapter);
}
else {
Toast.makeText(getActivity(), R.string.msg_nws,
Toast.LENGTH_LONG).show();
}
}

@Override
public void onFailure(Call<WeatherResponse> call, Throwable t) {
Toast.makeText(getActivity(), t.getMessage(),
Toast.LENGTH_LONG).show();
Log.e(getClass().getSimpleName(),
"Exception from Retrofit request to National Weather Service", t);
}
});
} }


@Override @Override
Expand All @@ -107,118 +138,50 @@ public void onStatusChanged(String s, int i, Bundle bundle) {
// required for interface, not used // required for interface, not used
} }


private String getForecastXML(String path) throws IOException { private class ForecastAdapter extends ArrayAdapter<WeatherResponse.Period> {
Request request=new Request.Builder().url(path).build(); private int size;
Response response=client.newCall(request).execute(); private java.text.DateFormat dateFormat;

private java.text.DateFormat timeFormat;
return(response.body().string());
}

private ArrayList<Forecast> buildForecasts(String raw)
throws Exception {
ArrayList<Forecast> forecasts=new ArrayList<Forecast>();
DocumentBuilder builder=
DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document doc=builder.parse(new InputSource(new StringReader(raw)));
NodeList times=doc.getElementsByTagName("start-valid-time");

for (int i=0; i < times.getLength(); i++) {
Element time=(Element)times.item(i);
Forecast forecast=new Forecast();

forecasts.add(forecast);
forecast.setTime(time.getFirstChild().getNodeValue());
}

NodeList temps=doc.getElementsByTagName("value");

for (int i=0; i < temps.getLength(); i++) {
Element temp=(Element)temps.item(i);
Forecast forecast=forecasts.get(i);

forecast.setTemp(Integer.valueOf(temp.getFirstChild()
.getNodeValue()));
}

NodeList icons=doc.getElementsByTagName("icon-link");

for (int i=0; i < icons.getLength(); i++) {
Element icon=(Element)icons.item(i);
Forecast forecast=forecasts.get(i);

forecast.setIcon(icon.getFirstChild().getNodeValue());
}

return(forecasts);
}

private class FetchForecastTask
extends AsyncTask<Location, Void, List<Forecast>> {
private Exception e=null;


@Override ForecastAdapter(List<WeatherResponse.Period> items) {
protected List<Forecast> doInBackground(Location... locs) {
try {
Location loc=locs[0];
String url=
String.format(template, loc.getLatitude(),
loc.getLongitude());

return(buildForecasts(getForecastXML(url)));
}
catch (Exception e) {
this.e=e;
}

return(null);
}

@Override
protected void onPostExecute(List<Forecast> forecasts) {
if (e == null) {
adapter=new ForecastAdapter(forecasts);
setListAdapter(adapter);
}
else {
Log.e(getClass().getSimpleName(), "Exception fetching data", e);
Toast.makeText(getActivity(),
String.format(getString(R.string.error),
e.toString()), Toast.LENGTH_LONG)
.show();
}
}
}

private class ForecastAdapter extends ArrayAdapter<Forecast> {
int size;

ForecastAdapter(List<Forecast> items) {
super(getActivity(), R.layout.row, R.id.date, items); super(getActivity(), R.layout.row, R.id.date, items);


size=getActivity() size=getActivity()
.getResources() .getResources()
.getDimensionPixelSize(R.dimen.icon); .getDimensionPixelSize(R.dimen.icon);
dateFormat=DateFormat.getDateFormat(getActivity());
timeFormat=DateFormat.getTimeFormat(getActivity());
} }


@Override @Override
public View getView(int position, View convertView, ViewGroup parent) { public View getView(int position, View convertView, ViewGroup parent) {
View row=super.getView(position, convertView, parent); View row=super.getView(position, convertView, parent);
Forecast item=getItem(position); WeatherResponse.Period item=getItem(position);


if (!TextUtils.isEmpty(item.getIcon())) { if (!TextUtils.isEmpty(item.icon)) {
ImageView icon=(ImageView)row.findViewById(R.id.icon); ImageView icon=row.findViewById(R.id.icon);


Picasso.with(getActivity()).load(item.getIcon()) Picasso.with(getActivity()).load(item.icon)
.resize(size, size).centerCrop().into(icon); .resize(size, size).centerCrop().into(icon);
} }


TextView title=(TextView)row.findViewById(R.id.date); TextView title=row.findViewById(R.id.date);


title.setText(item.getTime()); try {
Date parsedStartTime=ISO8601.parse(item.startTime);
String date=dateFormat.format(parsedStartTime);
String time=timeFormat.format(parsedStartTime);

title.setText(date+" "+time);
}
catch (ParseException e) {
title.setText(item.startTime);
}


TextView temp=(TextView)row.findViewById(R.id.temp); TextView temp=row.findViewById(R.id.temp);


temp.setText("Temperature: "+String.valueOf(item.getTemp())+"F"); temp.setText(getString(R.string.temp, item.temperature,
item.temperatureUnit));


return(row); return(row);
} }
Expand Down

0 comments on commit 8ae3487

Please sign in to comment.