
Android Design Pattern - MVVM (Part 1)
Hello reader,
I have challenged myself one more time to make a series of instructions on How to Build Weather Application for Android using Jetpack, Retrofit and Open Weather Api. This guide will be divided into four notes:
1. Creating a project and connecting it to an API using Android Architectual Components
2. Separating Retrofit interface within MVVM Component called Repository
3. Designing and styling UI
4. Debugging and testing
Android Jetpack
Jetpack is "a suite of libraries, tools, and guidance for Android developers" created and released by Google. It holds a collection of components libraries such as Architecture components. The architecture components help us in building:
- Robust Apps
- Testable Apps
- Maintainable Apps
Architecture components we will use in this project:
LiveData: Notify views of any database changes.
ViewModel: Manage UI-related data in a lifecycle conscious way.
Why should we use Jetpack?
It helps us managing activity lifecycles, surviving configuration changes and controlling memory leaks. Practically Jetpack is used to help developers avoid writing a boiler-plate code, to follow best Android development architecture, and to simplify complex setup. In my humble opinion, it is a very powerful tool for developers to develop great modular applications. The focus of Jetpack is to help us implement modularization and to maintain scalability of our applications.
Prerequisite
You should be familiar with Java or Kotlin syntax, OOP concepts and RESTful API. You need to have installed the Android Studio or equivalent tool for mobile app development.
I am using Version Control system from Android Studio to connect to my GitHub repo. It is very simple to use, you can find instructions on How to connect GitHub to Android Studio.
Let's begin with the first part of the project:
1. Open Android Studio and create a new project with Empty Activity, I have named mine WeatherApp and chosen Java for language.
2. Add dependencies for Retrofit and ViewModel inside Gradle Scripts/build.gradle Module:app and Sync project
//retrofit
implementation 'com.squareup.retrofit2:retrofit:2.4.0'
implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
//Android Jetpack
implementation "android.arch.lifecycle:extensions:1.1.0"
implementation "android.arch.lifecycle:viewmodel:1.1.0"
3. Next, we are going to use the free weather API from https://openweathermap.org/api. We need to log in or to create a new account if we don't already have one, to be able to use API and to get API key (unique ID) and construct the desired URL (http://api.openweathermap.org/data/2.5/weather?q=belgrade&appid=YOURKEY). There is quite good documentation for you to use the desired location and parameters.
4. When our API URL is ready, open it and select and copy the whole JSON response, then navigate to jsonschema2pojo, select GSON annotation style and generate model classes.
5. Grant network access permissions and internet connection in AndroidManifest.xml
<!--permissions to use internet-->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
6. This is the part when it all starts to be interesting. Using the Retrofit library we are going to connect our application to Wheater API. Inside of com.example.wheaterapp create a new package and name it api.service then create Java interface and name it RetrifitApiService:
package com.example.weatherapp.api.service;
import com.example.weatherapp.pojo.Model;
import retrofit2.Call;
import retrofit2.http.GET;
public interface RetrofitApiService {
String BASE_URL = "https://api.openweathermap.org/data/2.5/";
@GET("weather")
Call<Model> getWeatherData(@Query("q") String q,
@Query("appid") String appid,
@Query("units") String units);
}
7. Create a new package for the ViewModel and new Java class WeatherViewModel.java
package com.example.weatherapp.view.model;
import android.util.Log;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import com.example.weatherapp.api.service.RetrofitApiService;
import com.example.weatherapp.pojo.Model;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class WeatherViewModel extends ViewModel {
private MutableLiveData<Model> weatherData;
}
8. Inside WeatherViewModel class add LiveData method to return data from the server
public LiveData<Model> getWeather() {
//if the response is null
if (weatherData == null) {
weatherData = new MutableLiveData<Model>();
//we will load it asynchronously from server in method below
loadWeatherData();
}
//returning the data
return weatherData;
}
9. Copy the code below to declare a method which uses Retrofit to get the JSON data our API
private void loadWeatherData() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(RetrofitApiService.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
RetrofitApiService api = retrofit.create(RetrofitApiService.class);
Call<Model> call = api.getWeatherData();
call.enqueue(new Callback<Model>() {
@Override
public void onResponse(Call<Model> call, Response<Model> response) {
//finally we are setting the response to our MutableLiveData
Log.d("responser", "Response body successfuly written");
weatherData.setValue(response.body());
}
@Override
public void onFailure(Call<Model> call, Throwable t) {
Log.d("responser", t.getMessage());
}
});
}
10. Use ViewModel to display data to the UI through Fragment or Activity:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d("init", "Hello world");
//creating an viewmodel instance
WeatherViewModel model = ViewModelProviders.of(this).get(WeatherViewModel.class);
//setting the observer
model.getWeather().observe(this, new Observer<Model>() {
@Override
public void onChanged(@Nullable Model model) {
//later this data will be used to update UI
Log.d("responser", "City: " + model.getName());
Log.d("responser", "Current temperature in F: " + model.getMain().getTemp().toString());
}
});
}
}
Run the app on an emulator and check the logcat.
If you have any struggles with the code you can always clone/download source code from GitHub.
Thank you for reading, see you soon with the next phase of the project.