Download as pdf or txt
Download as pdf or txt
You are on page 1of 13

Mobile Applications Development

Tutorial 7

Agenda
1. Open the starter project
2. Lecture recap
3. Set up Retrofit
4. Check sample data in Postman
5. Create classes for JSON serialization
6. Get all movies request with Retrofit
7. MovieRepository and getAllMovies request
8. ViewModel and fetch all movies from the repository
9. Link LiveData and Jetpack Compose state

1. Open the starter project


1.1 In the Intranet open TW 7 section and download the MovieListStarter.zip project.
1.2 Unzip it and open in the Android Studio.
1.3 Check the right bottom corner of the Android Studio. The project contains two git branches:
starter and solution:

1.4 Checkout the starter branch and launch the project in an emulator device.
2. Lecture recap
1. Name the UI layer components.
2. Name the Data layer components.
3. To which layer ViewModel belongs?
4. What is LiveData?
5. What are coroutines?

3. Set up Retrofit
3.1 Inside your module level gradle file find the “dependencies” section, add these
dependencies and sync the project with Gradle. These include all the dependencies you will
need for this tutorial (not only Retrofit library).
//ViewModel

implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2"

// ViewModel utilities for Compose

implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2")

//Coroutine

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3"

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"

// Networking

implementation "com.squareup.retrofit2:retrofit:2.9.0"

implementation "com.squareup.okhttp3:okhttp:4.11.0"

implementation "com.squareup.okhttp3:logging-interceptor:4.9.0"

implementation "com.squareup.retrofit2:converter-gson:2.9.0"

//Livedata

implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.2"

implementation "androidx.compose.runtime:runtime-livedata:1.5.4"
3.2 Inside the data package create a new package “network”
3.3 Inside the “network” package create a new class RetrofitInstance. Copy paste this
code inside the RetrofitInstance class (note that imports are included in the sample code):
package com.example.movielist.data.network

import com.google.gson.GsonBuilder

import okhttp3.OkHttpClient

import okhttp3.logging.HttpLoggingInterceptor

import retrofit2.Retrofit

import retrofit2.converter.gson.GsonConverterFactory

import java.util.concurrent.TimeUnit

class RetrofitInstance {

companion object {

val BASE_URL = ""

val interceptor = HttpLoggingInterceptor().apply {

this.level = HttpLoggingInterceptor.Level.BODY

val client = OkHttpClient.Builder().apply {

this.addInterceptor(interceptor)

.connectTimeout(20, TimeUnit.SECONDS)

}.build()

fun getRetrofitInstance(): Retrofit {

return Retrofit.Builder()

.baseUrl(BASE_URL)

.client(client)

.addConverterFactory(GsonConverterFactory.create(GsonBuilder().create()))

.build()

3.4 Set the value of BASE_URL to https://wiutmadcw.uz/api/v1/ inside the builder.


3.5 You’ve completed basic set up for the Retrofit library.
4. Check sample data in Postman
4.1 Open Postman web version and login: https://identity.getpostman.com/login
4.2 You should have the MAD CW API collection already imported in your Postman account (if
not, check Tutorial 5 for instructions)
4.3 Open getAllRecords method
4.4 Set student_id parameter as “movie”
4.5 Send the request
4.6 Check what kind of data will come in the response

5. 5. Create classes for JSON serialization and deserialization

5.1 Serialization and deserialization means converting an object into text format and vice
versa.
5.2 Inside the “network” package Create a new Kotlin data class MyListResponse:
package com.example.movielist.data.network

import com.google.gson.annotations.SerializedName

data class MyListResponse<T>(

@SerializedName("code")

val code: String,

@SerializedName("status")

val status: String,

@SerializedName("message")

val message: String,

@SerializedName("data")

val data: List<T>?

5.3 Inside the network package create a new package movie.


5.4 Inside the movie package create a new Kotlin data class MovieResponse:
package com.example.movielist.data.network.movie

import com.google.gson.annotations.SerializedName

data class MovieResponse(

@SerializedName("id")

val id: Int,

@SerializedName("title")

val name: String,

@SerializedName("description")

val description: String?

)
6. Get all movies request with Retrofit
6.1 Inside the network package create a new interface MovieService.
6.2 Inside the MovieService interface create a new Retrofit request for fetching a list of movies.
The keyword suspend means that this function has to be called within a coroutine (i.e.
asynchronously).
package com.example.movielist.data.network

import com.example.movielist.data.network.movie.MovieResponse

import retrofit2.http.GET

import retrofit2.http.Query

import com.example.movielist.data.network.MyListResponse

interface MovieService {

@GET("records/all")

suspend fun getAllMovies(@Query("student_id") student_id: String):


MyListResponse<MovieResponse>

6.3 Inside the RetrofitInstance.kt class include the reference to the MovieService like this
(note the code in bold):
...

fun getRetrofitInstance(): Retrofit {

return Retrofit.Builder()

.baseUrl(BASE_URL)

.client(client)

.addConverterFactory(GsonConverterFactory.create(GsonBuilder().create()))

.build()

val movieService: MovieService = getRetrofitInstance()

.create(MovieService::class.java)

}
7. MovieRepository and getAllMovies request
7.1 Inside the data package find and open the class MovieRepository.kt
7.2 It has the getMovieList() function which returns dummy data for the movies list.
7.3 Modify the getMoviesList() function like this:
package com.example.movielist.data

import com.example.movielist.models.Movie

class MovieRepository {

fun getMovieList(): List<Movie> {

val movies = mutableListOf<Movie>()

return movies

7.4 Next, modify the getMoviesList() function like the code below. Now it contains the
repository request to fetch list of all movies.
package com.example.movielist.data

import com.example.movielist.data.network.MyListResponse

import com.example.movielist.data.network.RetrofitInstance

import com.example.movielist.data.network.movie.MovieResponse

import com.example.movielist.models.Movie

class MovieRepository {

suspend fun getMovieList(): List<Movie> {

val movies = mutableListOf<Movie>()

val response: MyListResponse<MovieResponse> =

RetrofitInstance.movieService.getAllMovies("movie")

val moviesFromResponse = response.data

return movies

}
7.5 Let’s map the response data to our internal Movie class:
suspend fun getMovieList(): List<Movie> {

val movies = mutableListOf<Movie>()

val response: MyListResponse<MovieResponse> =

RetrofitInstance.movieService.getAllMovies("movie")

val moviesFromResponse = response.data

if (moviesFromResponse != null) {

for (movieFromResponse in moviesFromResponse) {

if (movieFromResponse.description != null) {

movies.add(

Movie(

movieFromResponse.name.uppercase(),

movieFromResponse.description

return movies

}
7.6 Wrap the function logic inside a Try…Catch block, in case something goes wrong with the
network request:
suspend fun getMovieList(): List<Movie> {

val movies = mutableListOf<Movie>()

try {

val response: MyListResponse<MovieResponse> =

RetrofitInstance.movieService.getAllMovies("movie")

val moviesFromResponse = response.data

if (moviesFromResponse != null) {

for (movieFromResponse in moviesFromResponse) {

if (movieFromResponse.description != null) {

movies.add(

Movie(

movieFromResponse.name.uppercase(),

movieFromResponse.description

} catch (ex: Exception) {

ex.printStackTrace()

return movies

8. ViewModel and fetch all movies from the repository


8.1 Inside the list package find and open the class MovieListViewModel.kt
8.2 It contains the class inheriting from the system class ViewModel() and it accepts
movieRepository as a variable in the constructor

8.3 Add a new function getAllMovies():


package com.example.movielist.list

import androidx.lifecycle.ViewModel

import androidx.lifecycle.viewModelScope

import com.example.movielist.data.MovieRepository

import kotlinx.coroutines.launch

class MovieListViewModel(

private val movieRepository: MovieRepository

) : ViewModel() {

fun getAllMovies() {

viewModelScope.launch {

val movies = movieRepository.getMovieList()

}
8.4 Create a new LiveData object at the top of the class:
package com.example.movielist.list

import androidx.lifecycle.MutableLiveData

import androidx.lifecycle.ViewModel

import androidx.lifecycle.viewModelScope

import com.example.movielist.data.MovieRepository

import com.example.movielist.models.Movie

import kotlinx.coroutines.launch

class MovieListViewModel(

private val movieRepository: MovieRepository

) : ViewModel() {

val moviesLiveData: MutableLiveData<List<Movie>> by lazy {

MutableLiveData<List<Movie>>()

fun getAllMovies() {

viewModelScope.launch {

val movies = movieRepository.getMovieList()

8.5 Update the LiveData object from the result of the repository request inside the
getAllMovies() function:
fun getAllMovies() {

viewModelScope.launch {

val movies = movieRepository.getMoviesList()

moviesLiveData.value = movies

}
8.6 Include the init() method and call our getAllMovies() function there. This will make
the call happen on MoviesListViewModel() instance creation:
class MoviesListViewModel(private val movieRepository: MovieRepository) : ViewModel() {

val moviesLiveData: MutableLiveData<List<Movie>> by lazy {

MutableLiveData<List<Movie>>()

init {

getAllMovies()

fun getAllMovies() {

viewModelScope.launch {

val movies = movieRepository.getMoviesList()

moviesLiveData.value = movies

9. Link LiveData and Jetpack Compose state


9.1 Inside the list package find and open the file MovieList.kt
9.2 Edit the MoviesList() composable like the code below. You are:
a) adding reference to the MovieListViewModel()
b) adding reference to the LiveData object and wrapping it into Jetpack Compose state
container
c) checking if movies variable is not empty and converting it to list
@Composable

fun MoviesList(

viewModel: MovieListViewModel = MovieListViewModel(MovieRepository())

) {

//val movies = MovieRepository().getMovieList()

val movies by viewModel.moviesLiveData.observeAsState()

if (!movies.isNullOrEmpty()) {

LazyColumn(modifier = Modifier.fillMaxHeight()) {

items(items = movies!!, itemContent = { item ->

MovieItem(movie = item)

})

9.3 Make sure to include these imports:


import androidx.compose.runtime.getValue

import androidx.compose.runtime.livedata.observeAsState

9.4 Launch the application in the emulator to check the result. You should be able to see the list
of movies matching the database data.

NOTE: It is allowed to use this sample project as a template for your CW Application part

You might also like