Loading and displaying images from the internet with Retrofit into your android app

Osarugue Enehizena
5 min readJan 2, 2021

--

In this tutorial, we’ll learn how to load and display photos from a web URL. We’ll use Glide to display a single image from a web service. Let’s get started…

Step 1: Create a new project and call it DisplayDataImage

  • Open Android Studio if it is not already opened.
  • In the main Welcome to Android Studio dialog, click Start a new Android Studio project.

The “Choose your project” dialog appears. Select Empty Activity as shown below, and click Next.

Step 2: Add various dependencies:

  • Inside the dependencies block, add the Gradle dependency for the Retrofit, Moshi library, Glide library and ViewModel.
//Retrofit
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.retrofit2:converter-moshi:2.9.0"
//Moshi Library
implementation "com.squareup.moshi:moshi-kotlin:1.9.3"
//Glide Library
implementation "com.github.bumptech.glide:glide:4.8.0"
//ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
  • Add the data binding dependency to Gradle
android{...buildFeatures {
dataBinding true
}
  • Apply the kotlin-kapt plugin at the top of the build.gradle file
id 'kotlin-kapt'
  • Ensure the support for Java 8 language features are added
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}

Click on Sync Now

Step 3: Implement the DataProperty

  • Create a data class, call it DataProperty and add the following code
data class DataProperty(
val id: String,
val name: String,
val propellant: String,
val destination: String,
@Json(name = "imageurl") val imgSrcUrl: String,
val technologyexists: String
)

Each of the variables in the DataProperty data class corresponds to a key name in the JSON object.

Step 4: Setup ViewModel and connect with DisplayDataFragment

  • Create a new Kotlin class called DisplayDataViewModel class and make the class extend the abstract class ViewModel
class DisplayDataViewModel : ViewModel() {
}
  • In DisplayDataViewModel, we create both internal and external LiveData for the status String
private val _status = MutableLiveData<String>()val status: LiveData<String>
get() = _status
  • Add internal and external LiveData for the property DataProperty object.
private val _property= MutableLiveData<DataProperty>()val property: LiveData<DataProperty>
get() = _property

Add a Fragment class and create a binding object

  • Select File > New > Fragment > Fragment (Blank).
  • For the Fragment Name, enter DisplayDataFragment.
  • For the Fragment layout name, enter fragment_display_data
  • For source language, select Kotlin and click Finish.
  • Open the DisplayDataFragment.kt fragment file, if it is not already open.
  • Delete the onCreate() method, the fragment initialization parameters, companion object and the code inside onCreateView().
  • Next, create a binding object and inflate the Fragment’s view (which is equivalent to using setContentView() for an Activity). Make sure your DisplayDataFragment class looks like the following:
class DisplayDataFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding = FragmentDisplayDataBinding.inflate(inflater)
return binding.root
}
}

Add the new fragment to the main layout file

  • Open res > layout > activity_main.xml and select the Code tab to view the layout XML code.
  • Delete the codes Inside the existing main layout file.
  • Add the following code:
<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/displayDataFragment"
android:name="com.example.displaydataimage.DisplayDataFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
  • At the top of DisplayDataFragment class, make the DisplayDataFragment lazily initializes the DisplayDataViewModel.
private val viewModel: DisplayDataViewModel by lazy {
ViewModelProvider(this).get(DisplayDataViewModel::class.java)
}
  • Inside the fragment_display_data.xml layout, wrap your root layout inside <layout> tags. This adds the data binding for the view model. Import the DisplayDataViewModel. The fragment_display_data.xml should look like this:
<layout ...>
<data>
<variable
name="viewModel"
type="com.example.displaydataimage.DisplayDataViewModel" />
</data>
</layout>
  • In the fragment_display_data, inside the ****root ****layout create the ImageView, and add an app:imageUrl attribute to the ImageView element to use the new image loading binding adapter:
<ImageView
android:id="@+id/mars_image"
android:layout_width="match_parent"
android:layout_height="170dp"
android:adjustViewBounds="true"
android:padding="2dp"
app:imageUrl="@{viewModel.property.imgSrcUrl}"
android:scaleType="centerCrop"
tools:src="@tools:sample/backgrounds/scenic" />
  • Inside the DisplayDataFragment file, import the com.example.displaydataimage.databinding.FragmentDisplayDataBinding and Specify the DisplayDataFragment view as the lifecycle owner of the binding then pass the viewModel into the data binding.
binding.lifecycleOwner = this
binding.viewModel = viewModel
return binding.root

Step 5: Implement the DisplayDataApiService

  • Create DisplayDataApiService.kt file and add a constant Base Url at the top of the file. We will use a fake Online REST API.

private const val BASE_URL = "https://raw.githubusercontent.com/"

  • Below the constant, add the following code to create the Moshi instance.
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
  • Below the Moshi instance, use a Retrofit builder to create a Retrofit object.
private val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.baseUrl(BASE_URL)
.build()
  • Just below the call to the Retrofit builder, define an interface that defines how Retrofit talks to the web server using HTTP requests.
interface DisplayDataApiService {
@GET("Oclemy/SampleJSON/338d9585/spacecrafts.json")
suspend fun getProperties():
List<DataProperty>
}
  • Below the DisplayDataApiService interface, define a public object called DataApi to initialize the Retrofit service.
object DataApi {
val retrofitService : DisplayDataApiService by lazy {
retrofit.create(DisplayDataApiService::class.java) }
}

Step 6: Call the web service in DisplayDataViewModel.

  • In the DisplayDataViewModel, at the bottom of the class, we create the init block and call getDetails() method inside.
  • Create the getDetails method
  • Inside getDetails(), launch the coroutine using viewModelScope.
  • Inside the launch block, add a try/catch block to handle exceptions:
  • Inside the try {} block, call getProperties() on the retrofitService object. Calling getProperties() from the DataApi service creates and starts the network call on a background thread.
  • Inside the catch {} block, handle the error response to a status value.

Your code should look like this:

init {
getDetails()
}
private fun getDetails() {
viewModelScope.launch {
try {
val listResult = DataApi.retrofitService.getProperties()
if (listResult.size > 0) {
_property.value = listResult[0]
}
} catch (e: Exception) {
_status.value = "Failure: ${e.message}"
}
}
}

Step 7: Create a binding adapter and call Glide.

In this step, you use a binding adapter to take the URL from an XML attribute associated with an ImageView, and you use Glide to load the image.

Create BindingAdapters.kt to hold the binding adapters.

  • Create a bindImage() function that takes an ImageView and a String as parameters. Annotate the function with @BindingAdapter.
  • Inside the bindImage() function, add a let {} block for the imgUrl argument
  • Inside the let {} block, add the line shown below to convert the URL string (from the XML) to a Uri object.
  • Still inside let {}, call Glide.with() to load the image from the Uri object into the ImageView.
  • of course, add the loading_animation and the ic_broken_image into the drawable folder.
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {
imgUrl?.let {
val imgUri =
imgUrl.toUri().buildUpon().scheme("https").build()
Glide.with(imgView.context)
.load(imgUri)
.into(imgView)
}
}

Step 8: Define the internet permission.

<uses-permission android:name=”android.permission.INTERNET” />
  • Compile and run the app. If everything is working correctly with your internet connection, you see:

Cover Photo by Markus Winkler on Unsplash

Originally published at https://blog.nehirugue.com.

--

--

Osarugue Enehizena
Osarugue Enehizena

Written by Osarugue Enehizena

Software Engineering & Project Leading | Building Bridges Between Faith & Tech🌍🙏💻