Weather App in Android Studio (Java): Full Tutorial
Build a good-looking weather app that fetches live weather for any city from an online API and shows it with weather icons. A great first project for working with APIs and JSON.
Add the internet permission and a networking library
Before you start
Android Studio (any recent version)
Basic Java: variables, methods and if/else
A free weather API key
An emulator or an Android phone
How it fits together
1Type a city
2WeatherClient (OkHttp)
3api.openweathermap.org
4Weather.fromJson
5Icon, temperature and tiles
Build it step by step
1. Get a free API key
Sign up at openweathermap.org and copy your API key from My API keys. New keys can take up to 2 hours to start working.
2. Create the project
In Android Studio choose New Project → Empty Views Activity, with Language Java and Minimum SDK API 24.
3. Keep the key out of your code
Open local.properties in the project folder and add:
properties
OPENWEATHER_API_KEY=your-key-here
This file is never committed, so your key isn't published with your code.
4. Add OkHttp and read the key
In app/build.gradle.kts, add the import at the very top:
kotlin
import java.util.Properties
Inside android { defaultConfig { } }, turn the key into a BuildConfig field:
kotlin
val localProperties =Properties().apply{
rootProject.file("local.properties").takeIf{ it.exists()}?.inputStream()?.use{load(it)}}buildConfigField("String","OPENWEATHER_API_KEY","\"${localProperties.getProperty("OPENWEATHER_API_KEY","")}\"")
Press Sync Now. Your code can now read BuildConfig.OPENWEATHER_API_KEY.
5. Allow internet access
In AndroidManifest.xml, add <uses-permission android:name="android.permission.INTERNET" /> above <application>.
6. Design the screen
The sky gradient is the theme's windowBackground, so it appears instantly. On top: a search box, a spinner, an error message, then the city, an icon, a big temperature and three tiles (feels like, humidity, wind).
7. Ask OpenWeather
WeatherClient builds the address with HttpUrl.Builder, which safely encodes names like "New York". enqueue downloads in the background, so the app never freezes. The answer is posted back to the main thread, because only the main thread may change the screen.
8. Read the reply
The reply is JSON. Weather.fromJson reads the city name, main.temp, main.feels_like, main.humidity, wind.speed and the first weather entry. Wind comes in metres per second, so multiply by 3.6 for km/h.
9. Handle errors
OpenWeather answers 401 for a bad key and 404 for an unknown city. Show a clear message for each, and another for no internet.
10. Pick the icon
Icon codes such as 10d start with two digits for the sky: 01 clear, 02 a few clouds, 09 and 10 rain, 11 storm, 13 snow, 50 mist. A switch maps them to the vector icons in res/drawable.
11. Remember the city
After a successful search, save the city in SharedPreferences. Next launch opens it straight away.
12. Run it
Search a few cities, then try a made-up name and airplane mode to see the error messages.
If something goes wrong
"OpenWeather rejected the API key": wait up to 2 hours for a new key, and check local.properties has no spaces or quotes.
"Add OPENWEATHER_API_KEY…": the key wasn't found. Check the spelling, then Build → Rebuild Project.
What's improved over the video
The key stays out of your code, requests run with OkHttp's enqueue and close properly, city names are encoded safely, wind shows the correct km/h, errors are clear, and the last city is remembered.
Files affected
Check what to add and what to replace before copying code.
Searches, shows the weather, picks the icon, and remembers the last city.
MainActivity.java
packagecom.example.weatherapp;importandroid.content.SharedPreferences;importandroid.os.Bundle;importandroid.view.View;importandroid.view.inputmethod.EditorInfo;importandroid.widget.EditText;importandroid.widget.ImageView;importandroid.widget.TextView;importandroidx.appcompat.app.AppCompatActivity;importandroidx.core.view.WindowCompat;importandroidx.core.view.WindowInsetsCompat;importokhttp3.Call;/**
* Search a city and see its weather. The last city you searched opens next time.
*/publicclassMainActivityextendsAppCompatActivity{privatestaticfinalStringDEFAULT_CITY="Mumbai";privateWeatherClient client;privateCall request;privateSharedPreferences prefs;privateEditText cityInput;privateView progress, content;privateTextView error, city, temperature, description, feelsLike, humidity, wind;privateImageView icon;@OverrideprotectedvoidonCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);Screens.setUp(this,R.layout.activity_main);
cityInput =findViewById(R.id.cityInput);
progress =findViewById(R.id.progress);
content =findViewById(R.id.content);
error =findViewById(R.id.textError);
city =findViewById(R.id.textCity);
temperature =findViewById(R.id.textTemperature);
description =findViewById(R.id.textDescription);
feelsLike =findViewById(R.id.textFeelsLike);
humidity =findViewById(R.id.textHumidity);
wind =findViewById(R.id.textWind);
icon =findViewById(R.id.weatherIcon);
client =newWeatherClient(BuildConfig.OPENWEATHER_API_KEY);
prefs =getSharedPreferences("weather",MODE_PRIVATE);findViewById(R.id.btnSearch).setOnClickListener(v ->search());// The keyboard's search key works too.
cityInput.setOnEditorActionListener((v, actionId, event)->{if(actionId !=EditorInfo.IME_ACTION_SEARCH)returnfalse;search();returntrue;});if(BuildConfig.OPENWEATHER_API_KEY.isEmpty()){showError(R.string.error_no_key);}else{load(prefs.getString("city",DEFAULT_CITY));}}@OverrideprotectedvoidonDestroy(){super.onDestroy();if(request !=null) request.cancel();}privatevoidsearch(){String name = cityInput.getText().toString().trim();if(name.isEmpty()){
cityInput.setError(getString(R.string.error_empty));return;}WindowCompat.getInsetsController(getWindow(), cityInput).hide(WindowInsetsCompat.Type.ime());load(name);}privatevoidload(String name){if(request !=null) request.cancel();
progress.setVisibility(View.VISIBLE);
error.setVisibility(View.GONE);
request = client.fetch(name,newWeatherClient.Listener(){@OverridepublicvoidonWeather(Weather weather){
progress.setVisibility(View.GONE);show(weather);
prefs.edit().putString("city", weather.city).apply();}@OverridepublicvoidonError(int message){showError(message);}});}privatevoidshow(Weather weather){
city.setText(weather.city);
temperature.setText(getString(R.string.degrees, weather.temperature));
description.setText(weather.description);
feelsLike.setText(getString(R.string.degrees, weather.feelsLike));
humidity.setText(getString(R.string.percent, weather.humidity));
wind.setText(getString(R.string.kmh, weather.windKmh));
icon.setImageResource(iconFor(weather.icon));
content.setVisibility(View.VISIBLE);}privatevoidshowError(int message){
progress.setVisibility(View.GONE);
error.setText(message);
error.setVisibility(View.VISIBLE);}/** OpenWeather icon codes start with two digits that name the sky, such as 01 for clear and 10 for rain. */staticinticonFor(String code){switch(code.substring(0,2)){case"01":returnR.drawable.ic_sun;case"02":returnR.drawable.ic_partly_cloudy;case"09":case"10":returnR.drawable.ic_rain;case"11":returnR.drawable.ic_storm;case"13":returnR.drawable.ic_snow;case"50":returnR.drawable.ic_mist;default:returnR.drawable.ic_cloud;}}}