Creating Weather App using Kotlin,an android application

There are a lots of weather application available in Google Play Store. Bt you may want to develop your own weather application in Android Studio. Oh, don’t worry, you don’t need to setup a weather station in each city on each country to get weather information in your app. There are free APIs available in the market to use them to get weather informations like Temperature, Pressure, Humidity, Weather status, Time of Sunrise and Sunset etc. Today in this tutorial I’ll walk you through creating an android weather app using Kotlin in Android Studio.
There are many websites you can get by searching on the Google which supply free weather Api, but In this tutorial we are going to use a Api provided by OpenWeatherMap. So let’s start to create a weather app.
What you’ll learn
Though today our task is to create an android weather app but throughout this tutorial we’ll learn something new if you didn’t know them already.
- Uses of AsyncTask
- Getting response from API
- Extracting JSON response
Before you start
Create a new Android Studio project. For this tutorial, I used the name of the project “Weather App”. Also don’t forget to select Kotlin as the project Language. You can follow my previous tutorial on Getting started with Android and Kotlin.
Get an API key from OpenWeatherMap
For retrieving data we will use OpenWeatherAPI, and we will be needing an API key for it. Before proceeding please get an API key by registering. You can either follow the attached video above or steps below.
Adding Internet Permission for our Weather App
Apps that need to access Internet must request Internet permission. You can request for internet permission in your AndroidManifest.xml using<uses-permission android:name="android.permission.INTERNET"/>
That’s my AndroidManifest.xml with the above permission:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.androdocs.weatherapp"> <uses-permission android:name="android.permission.INTERNET"/> <application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity"
android:theme="@style/CustomTheme"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application></manifest>
You can see that I have added android:screenOrientation="portrait" because I want my activity to be always in portrait mode even after use changes the device's orientation.
Also I used a custom style in my activity using android:theme="@style/CustomTheme" to disable the default ActionBar in this activity. In the next part, we'll implement this custom style.
Creating a Custom Style Theme
As I mentioned above that we’ll use a custom theme to disable the default ActionBar, we can use Theme.AppCompat.Light.NoActionBar to implement the theme. Also I’ll set the default text color to White so that I need not write the text color in all views later.
This is my styles.xml looks like after adding the custom theme.
<resources> <!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style> <style name="CustomTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorPrimary">#122259</item>
<item name="colorPrimaryDark">#122259</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:textColor">#FFFFFF</item>
</style></resources>
Please remember that I only did this for my design purpose. If you need the default action bar you shouldn’t do that. Also avoid adding the custom theme into your AndroidManifest.xml file then.
Designing the Layout — Weathe App
You have already noticed from the cover photo of this article that I’ll use a gradient background in this weather app tutorial. But we’ll not use any image for this gradient background. Rather this can be implemented using a Drawable Resource file. You can create this from Right click on “app” => “New” => “Android Resource file”. Then select Drawble as Resource type. I named it bg_gradient.xml
This is the code for our bg_gradient.xml file:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:angle="90"
android:startColor="#9561a1"
android:endColor="#122259"
android:type="linear"/>
</shape>Now for our designing purpose we’ll need to use some icons like for humidity, pressure, sunrise, sunset etc. Download the images I used for this tutorial. Extract the zip file and paste the images into your “res” => “drawable” directory.
Drawable images for Weather App
Download the images and place them into your res -> drawable directory.
Size: 26.60 KB
Download534 Downloads
Finally we’ll design our activity_main.xml layout resource file.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="25dp"
android:background="@drawable/bg_gradient"> <RelativeLayout android:id="@+id/mainContainer" android:layout_width="match_parent"
android:layout_height="match_parent" android:visibility="visible">
<LinearLayout android:id="@+id/addressContainer" android:layout_width="match_parent"
android:layout_height="wrap_content" android:orientation="vertical"
android:gravity="center">
<TextView android:id="@+id/address" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:textSize="24dp"
android:text="DHAKA, BD"/>
<TextView android:id="@+id/updated_at" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:textSize="14dp"
android:text="20 April 2012, 20:08 PM"/>
</LinearLayout>
<LinearLayout android:id="@+id/overviewContainer" android:layout_width="match_parent"
android:layout_height="wrap_content" android:orientation="vertical"
android:layout_centerInParent="true"> <TextView android:id="@+id/status" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:textSize="18dp"
android:layout_gravity="center"
android:text="Clear Sky"/>
<TextView android:id="@+id/temp" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:textSize="90dp"
android:fontFamily="sans-serif-thin" android:layout_gravity="center"
android:text="29°C"/> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"
android:orientation="horizontal" android:gravity="center">
<TextView android:id="@+id/temp_min" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:text="Min Temp: 05:05 AM"/>
<Space android:layout_width="50dp" android:layout_height="wrap_content"/> <TextView android:id="@+id/temp_max" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:text="Max Temp: 05:05 PM"/>
</LinearLayout> </LinearLayout>
<LinearLayout android:id="@+id/detailsContainer" android:layout_width="match_parent"
android:layout_height="wrap_content" android:orientation="vertical"
android:layout_alignParentBottom="true">
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"
android:orientation="horizontal" android:weightSum="3">
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content"
android:orientation="vertical" android:layout_weight="1"
android:gravity="center" android:padding="8dp"
android:background="#3CF1EBF1">
<ImageView android:layout_width="25dp" android:layout_height="25dp"
android:src="@drawable/sunrise" android:tint="#FFFFFF"/>
<Space android:layout_width="wrap_content" android:layout_height="5dp"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
android:textSize="12dp" android:text="Sunrise"/>
<TextView android:id="@+id/sunrise" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:textSize="14dp"
android:text="06:40 AM"/>
</LinearLayout>
<Space android:layout_width="10dp" android:layout_height="wrap_content"/>
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content"
android:orientation="vertical" android:layout_weight="1"
android:gravity="center" android:padding="8dp"
android:background="#3CF1EBF1">
<ImageView android:layout_width="25dp" android:layout_height="25dp"
android:src="@drawable/sunset" android:tint="#FFFFFF"/>
<Space android:layout_width="wrap_content" android:layout_height="5dp"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
android:textSize="12dp" android:text="Sunset"/>
<TextView android:id="@+id/sunset" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:textSize="14dp"
android:text="06:40 AM"/>
</LinearLayout>
<Space android:layout_width="10dp" android:layout_height="wrap_content"/>
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content"
android:orientation="vertical" android:layout_weight="1"
android:gravity="center" android:padding="8dp"
android:background="#3CF1EBF1">
<ImageView android:layout_width="25dp" android:layout_height="25dp"
android:src="@drawable/wind" android:tint="#FFFFFF"/>
<Space android:layout_width="wrap_content" android:layout_height="5dp"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
android:textSize="12dp" android:text="Wind"/>
<TextView android:id="@+id/wind" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:textSize="14dp"
android:text="06:40 AM"/>
</LinearLayout>
</LinearLayout> <Space android:layout_width="wrap_content" android:layout_height="10dp"/> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"
android:orientation="horizontal" android:weightSum="3">
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content"
android:orientation="vertical" android:layout_weight="1"
android:gravity="center" android:padding="8dp"
android:background="#3CF1EBF1">
<ImageView android:layout_width="25dp" android:layout_height="25dp"
android:src="@drawable/pressure" android:tint="#FFFFFF"/>
<Space android:layout_width="wrap_content" android:layout_height="5dp"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
android:textSize="12dp" android:text="Pressure"/>
<TextView android:id="@+id/pressure" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:textSize="14dp"
android:text="06:40 AM"/>
</LinearLayout>
<Space android:layout_width="10dp" android:layout_height="wrap_content"/>
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content"
android:orientation="vertical" android:layout_weight="1"
android:gravity="center" android:padding="8dp"
android:background="#3CF1EBF1">
<ImageView android:layout_width="25dp" android:layout_height="25dp"
android:src="@drawable/humidity" android:tint="#FFFFFF"/>
<Space android:layout_width="wrap_content" android:layout_height="5dp"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
android:textSize="12dp" android:text="Humidity"/>
<TextView android:id="@+id/humidity" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:textSize="14dp"
android:text="06:40 AM"/>
</LinearLayout>
<Space android:layout_width="10dp" android:layout_height="wrap_content"/>
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content"
android:orientation="vertical" android:layout_weight="1"
android:gravity="center" android:padding="8dp"
android:background="#3CF1EBF1">
<ImageView android:layout_width="25dp" android:layout_height="25dp"
android:src="@drawable/info" android:tint="#FFFFFF"/>
<Space android:layout_width="wrap_content" android:layout_height="5dp"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
android:textSize="12dp" android:text="Created By"/>
<TextView android:id="@+id/about" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:textSize="14dp"
android:text="AndroDocs"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</RelativeLayout>
<ProgressBar android:id="@+id/loader" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_centerInParent="true"
android:visibility="gone"/> <TextView android:id="@+id/errorText" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_centerInParent="true"
android:visibility="gone" android:text="Something went wrong"/>
</RelativeLayout>
Let’s start writing some Kotlin
Now we’ll need to write code to grab the weather information from the API. First I’m providing the full code of my MainActivity.kt file and after that I’ll describe them.
This is my MainActivity.kt file:
package com.androdocs.weatherappimport android.os.AsyncTask
import android.os.Bundle
import android.view.View
import android.widget.ProgressBar
import android.widget.RelativeLayout
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import org.json.JSONObject
import java.net.URL
import java.text.SimpleDateFormat
import java.util.*class MainActivity : AppCompatActivity() { val CITY: String = "dhaka,bd"
val API: String = "8118ed6ee68db2debfaaa5a44c832918" override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) weatherTask().execute() } inner class weatherTask() : AsyncTask<String, Void, String>() {
override fun onPreExecute() {
super.onPreExecute()
/* Showing the ProgressBar, Making the main design GONE */
findViewById<ProgressBar>(R.id.loader).visibility = View.VISIBLE
findViewById<RelativeLayout>(R.id.mainContainer).visibility = View.GONE
findViewById<TextView>(R.id.errorText).visibility = View.GONE
} override fun doInBackground(vararg params: String?): String? {
var response:String?
try{
response = URL("https://api.openweathermap.org/data/2.5/weather?q=$CITY&units=metric&appid=$API").readText(
Charsets.UTF_8
)
}catch (e: Exception){
response = null
}
return response
} override fun onPostExecute(result: String?) {
super.onPostExecute(result)
try {
/* Extracting JSON returns from the API */
val jsonObj = JSONObject(result)
val main = jsonObj.getJSONObject("main")
val sys = jsonObj.getJSONObject("sys")
val wind = jsonObj.getJSONObject("wind")
val weather = jsonObj.getJSONArray("weather").getJSONObject(0) val updatedAt:Long = jsonObj.getLong("dt")
val updatedAtText = "Updated at: "+ SimpleDateFormat("dd/MM/yyyy hh:mm a", Locale.ENGLISH).format(Date(updatedAt*1000))
val temp = main.getString("temp")+"°C"
val tempMin = "Min Temp: " + main.getString("temp_min")+"°C"
val tempMax = "Max Temp: " + main.getString("temp_max")+"°C"
val pressure = main.getString("pressure")
val humidity = main.getString("humidity") val sunrise:Long = sys.getLong("sunrise")
val sunset:Long = sys.getLong("sunset")
val windSpeed = wind.getString("speed")
val weatherDescription = weather.getString("description") val address = jsonObj.getString("name")+", "+sys.getString("country") /* Populating extracted data into our views */
findViewById<TextView>(R.id.address).text = address
findViewById<TextView>(R.id.updated_at).text = updatedAtText
findViewById<TextView>(R.id.status).text = weatherDescription.capitalize()
findViewById<TextView>(R.id.temp).text = temp
findViewById<TextView>(R.id.temp_min).text = tempMin
findViewById<TextView>(R.id.temp_max).text = tempMax
findViewById<TextView>(R.id.sunrise).text = SimpleDateFormat("hh:mm a", Locale.ENGLISH).format(Date(sunrise*1000))
findViewById<TextView>(R.id.sunset).text = SimpleDateFormat("hh:mm a", Locale.ENGLISH).format(Date(sunset*1000))
findViewById<TextView>(R.id.wind).text = windSpeed
findViewById<TextView>(R.id.pressure).text = pressure
findViewById<TextView>(R.id.humidity).text = humidity /* Views populated, Hiding the loader, Showing the main design */
findViewById<ProgressBar>(R.id.loader).visibility = View.GONE
findViewById<RelativeLayout>(R.id.mainContainer).visibility = View.VISIBLE } catch (e: Exception) {
findViewById<ProgressBar>(R.id.loader).visibility = View.GONE
findViewById<TextView>(R.id.errorText).visibility = View.VISIBLE
} }
}
}
First of all, as we need to grab weather information from an API, so we’ll make a http request to the URL of the API, and this is where we’ll use AsyncTask.
AsyncTask enables proper and easy use of the UI thread. This class allows you to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers.
Methods of AsyncTask
- onPreExecute() − Before doing background operation we should show something on screen like progressbar or any animation to user. we can directly comminicate background operation using on doInBackground() but for the best practice, we should call all asyncTask methods .
- doInBackground(Params) − In this method we have to do background operation on background thread. Operations in this method should not touch on any mainthread activities or fragments.
- onPostExecute(Result) − In this method we can update ui of background operation result.
As you can see in our AsyncTask named as “weatherTask()”, we displayed the ProgressBar in onPreExecute() so that user can understand that the app is making request to the url for getting weather information.
Then in the doInBackground(Params) we create a http request to the API url. Thanks to Kotlin that they provided a simple function URL() to get the response of a page. Previously in JAVA, we need to write quite a bit long lines. CITY is the value of our search query to find weather information of a specific place and API is the API Key that we got from OpenWeatherMap. Both of them are declared as:
val CITY: String = "dhaka,bd"
val API: String = "8118ed6ee68db2debfaaa5a44c832918"Use your own API key here.
We requested the url as:
response = URL("https://api.openweathermap.org/data/2.5/weather?q=$CITY&units=metric&appid=$API").readText(
Charsets.UTF_8
)Suppose you want to request weather information using a Latitude & Longitude of a place, then you should use:
response = URL("https://api.openweathermap.org/data/2.5/weather?lat=$LAT&lon=$LON&units=metric&appid=$API").readText(
Charsets.UTF_8
)where LAT and LON will be the Latitude & Longitude respectively. If you want to implement this project to display weather information of user’s current location you’ll just need detect the current latitude & longitude. I’ve already posted an article on Getting Current Location (latitude, longitude) in Android using Kotlin
Then the response from the url will be passed to the onPostExecute(Result). As the api passes weather information in JSON format, we’ll extract the data so that we can later set the extracted data into our views. I believe you already know the formation of JSON format. If not, I’ll try to explain a little bit here.
In JSON, the square brackets represents an Array and commas are used to separate different elements. and the Curly braces represents an Object. If you load this https://api.openweathermap.org/data/2.5/weather?q=dhaka,bd&units=metric&appid=8118ed6ee68db2debfaaa5a44c832918 into your browser, you’ll see a response like:
{
"coord":{
"lon":90.41,
"lat":23.71
},
"weather":[
{
"id":721,
...
...
"icon":"50n"
}
],
...
...
...
}Here you can see the parent is a Curly brace. That’s why we in onPostExecute(Result) passed the result in val jsonObj = JSONObject(result). Inside this main object say, "weather" is an array cause its elements are inside a square bracket. So to get its data we can use val weather = jsonObj.getJSONArray("weather")
This way we extracted our necessary weather information for our android weather app.
val jsonObj = JSONObject(result)
val main = jsonObj.getJSONObject("main")
val sys = jsonObj.getJSONObject("sys")
val wind = jsonObj.getJSONObject("wind")
val weather = jsonObj.getJSONArray("weather").getJSONObject(0)val updatedAt:Long = jsonObj.getLong("dt")
val updatedAtText = "Updated at: "+ SimpleDateFormat("dd/MM/yyyy hh:mm a", Locale.ENGLISH).format(Date(updatedAt*1000))
val temp = main.getString("temp")+"°C"
val tempMin = "Min Temp: " + main.getString("temp_min")+"°C"
val tempMax = "Max Temp: " + main.getString("temp_max")+"°C"
val pressure = main.getString("pressure")
val humidity = main.getString("humidity")val sunrise:Long = sys.getLong("sunrise")
val sunset:Long = sys.getLong("sunset")
val windSpeed = wind.getString("speed")
val weatherDescription = weather.getString("description")val address = jsonObj.getString("name")+", "+sys.getString("country")
SimpleDateFormat("dd/MM/yyyy hh:mm a", Locale.ENGLISH).format(Date(updatedAt*1000)) was used to format the timestamp into our desired format.
Finally I set those extracted weather data into my views
/* Populating extracted data into our views */
findViewById<TextView>(R.id.address).text = address
findViewById<TextView>(R.id.updated_at).text = updatedAtText
findViewById<TextView>(R.id.status).text = weatherDescription.capitalize()
findViewById<TextView>(R.id.temp).text = temp
findViewById<TextView>(R.id.temp_min).text = tempMin
findViewById<TextView>(R.id.temp_max).text = tempMax
findViewById<TextView>(R.id.sunrise).text = SimpleDateFormat("hh:mm a", Locale.ENGLISH).format(Date(sunrise*1000))
findViewById<TextView>(R.id.sunset).text = SimpleDateFormat("hh:mm a", Locale.ENGLISH).format(Date(sunset*1000))
findViewById<TextView>(R.id.wind).text = windSpeed
findViewById<TextView>(R.id.pressure).text = pressure
findViewById<TextView>(R.id.humidity).text = humidityAt last we called the AsyncTask class from our onCreate() method using weatherTask().execute()
Now run the application and if you find any issues please comment below.
Follow up am soon publishing my latest book written in kotlin 100%