Android development blog
Tutorials about Android dev topics

Using Android Location API in Weather App - Search city

Topics covered

Android Location API

Search city in openweathermap

LocationManager

Geographic coordinates

In this post I would like to describe how to search a city using openweathermap to get weather conditions. There are two way we can use to find a city:
  • Using name pattern
  • Using geographic coordinates (Android Location API)
We will introduce, moreover, some concepts about Android location API and Location-based service.

Searching city by name

In this kind of search, an user inserts a part of city name and the app will show all the possible result matching the pattern. From the UI perspective, we want to obtain something like the image shown below:
android_weather_app_search_city
As first step, we can use EditText so that the user can insert the city name:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

....
    <EditText
        android:id="@+id/cityEdtText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@+id/textView1"
        android:layout_marginTop="5dp"
        android:imeOptions="actionSearch"
        android:inputType="text" >
        <requestFocus />
    </EditText>
....

    <ListView android:id="@+id/cityList"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content" 
              android:layout_below="@id/txt1"
              android:layout_marginTop="3dp"/>

</RelativeLayout>



Notice that at line 13, we used imeOptions with “actionSearch” value to specify search action when user completes inserting the city name pattern.

Now in the Activity that holds this layout we have:
final EditText edt = (EditText) findViewById(R.id.cityEdtText);

edt.setOnEditorActionListener(new TextView.OnEditorActionListener() {
    
    @Override
    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
        if (actionId == EditorInfo.IME_ACTION_SEARCH) {
            bar.setVisibility(View.VISIBLE);
            JSONSearchTask task = new JSONSearchTask();
            String pattern = edt.getEditableText().toString();
            task.execute(new String[]{pattern});
            return true;
        }
        
        return false;
    }
});

So when user clicks on search icon, we starts searching for matching city names and at the end we will populate a ListView (line 18) . As you can notice, we start an AsyncTask, because the search operation can require quite long time and we don’t want to have ANR problems.

Now it is time to implement the logic that calls the remote openweathermap service and retrieve the cities. We know from API that the URL to call is:
http://api.openweathermap.org/data/2.5/find?mode=json&type=like&q=...&cnt=10

where after q we will add our pattern name and cnt represents the number of items we want to get (in our case we want 10 results at maximum).

Once we have the data, we parse it. We look for list tag that holds the result and for each item in the result we look for name tag that holds the name, the id and the country. The parser is very simple:
public static List<City> getCityList(String data) throws JSONException {
    JSONObject jObj = new JSONObject(data);
    JSONArray jArr = jObj.getJSONArray("list");
    
    List<City> cityList = new ArrayList<City>();
    
    for (int i=0; i < jArr.length(); i++) {
        JSONObject obj = jArr.getJSONObject(i);
        
        String name = obj.getString("name");
        String id = obj.getString("id");
        
        JSONObject sys = obj.getJSONObject("sys");
        String country = sys.getString("country");
        
        City c = new City(id,name,country);
        
        cityList.add(c);
    }
    
    
    return cityList;
}

..and finally we populate the ListView to show the results to the user. This happen in  onPostExecute method of the AsyncTask. Notice that you can create a custom Adapter to show this information or you can use a standard Adapter.

Search city using geographic coordinates: Android Location API

Another way to look for the city is using the current user position. We can use Location based service to find the current device position using GPS or WI-FI or cell network. There are two main element when implementing Location based service:

  • Location Manager that is the entry point to the Location based service
  • Location Provider representing the location technology used to find the device position

The first step is getting the LocationManager reference. This is a system service, then we can use:
LocationManager locManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);

once we have our Location Manager reference we can find the provider. In our case, we don’t want to specify a specific provider (like GPS for example) but let the system find the best matching the search criteria we set:
private static Criteria searchProviderCriteria = new Criteria();

// Location Criteria
static {
    searchProviderCriteria.setPowerRequirement(Criteria.POWER_LOW);
    searchProviderCriteria.setAccuracy(Criteria.ACCURACY_COARSE);
    searchProviderCriteria.setCostAllowed(false);
}

and then we are ready to get the provider:
String provider = locManager.getBestProvider(searchProviderCriteria, true);

and:
Location loc = locManager.getLastKnownLocation(provider);

Now we have to check if the location is null or is too old, in this case we get a new position:
if (loc == null || (SystemClock.elapsedRealtime() - loc.getTime()) > timeOut) {
    // We request another update Location
    Log.d("SwA", "Request location");
    locManager.requestSingleUpdate(provider, locListener, null);
}
else {
    JSONSearchTask task = new JSONSearchTask();
    task.execute(new Location[]{loc});
}

otherwise we simply call the service to get the city. Notice at line 4, that we need a listener that implements some callback methods, that will be called when the current position is available:
private LocationListener locListener = new LocationListener() {
  
  @Override
  public void onStatusChanged(String provider, int status, Bundle extras) {
  }
  
  @Override
  public void onProviderEnabled(String provider) {
  }
  
  @Override
  public void onProviderDisabled(String provider) {
  }
  
  @Override
  public void onLocationChanged(Location location) {        
      Log.d("SwA", "Location changed!");
      String sLat =   "" + location.getLatitude();        
      String sLon =  "" + location.getLongitude();
      Log.d("SwA", "Lat ["+sLat+"] - sLong ["+sLon+"]");
      
      LocationManager locManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
      locManager.removeUpdates(locListener);
      JSONSearchTask task = new JSONSearchTask();
      task.execute(new Location[]{location});
  }
};

The last step is creating the URL that will be called to retrieve the city from the geographic coordinates. In this case the url to call is:
http://api.openweathermap.org/data/2.5/find?lat=%lat%&lon=%lon%

where the %lat% and &lon% will be substituted by the real value extracted from the current position.

Using emulator to test the app

The last step is testing the app passing the coordinates. We can use DDMSDDMS and pass the coordinates:

android_ddms_location

Please notice that you should separate the decimal part according to your language and number format.

Another way is using

  telnet iphost port

and when connected send
geo fix <longitude value> <latitude value>

5 comments:

  1. Nice tutorial! But it would be great to get the whole source code... is that be possible? :)

    ReplyDelete
  2. survivingwithandroidAugust 26, 2014 at 9:59 AM

    You can find the source code at github (https://github.com/survivingwithandroid/WeatherLib)

    ReplyDelete
  3. this page showme page not found!! :(

    I use this code but i have an error: " JSONSearchTask cannot be resolved to a type", why?

    ReplyDelete

Related Posts with Thumbnails