Build a Simple Dictionary App Using Jetpack Compose and Retrofit
Learn to draw Charts π in Jetpack Compose without using any third party library?
What You'll Learn
- Setting up a Jetpack Compose project.
- Integrating Retrofit for API calls.
- Designing a responsive user interface with Jetpack Compose.
- Handling API responses and displaying word details.
- Managing state using ViewModel.
What is Retrofit?
Retrofit in Jetpack Compose
Why Use Retrofit?
- It abstracts the complexity of HTTP calls.
- You define APIs as simple interfaces using annotations (@GET, @POST, etc.).
- Automatically parses JSON or XML responses into Kotlin/Java objects using converters like Gson or Moshi.
- Handles HTTP errors (e.g., 404 or 500) effectively.
- Suitable for large projects with complex APIs.
- Works seamlessly with Kotlin and coroutines for asynchronous calls.
Why Choose Retrofit for a Dictionary App?
- Converting JSON responses directly into objects (e.g., ApiResponse).
- Allowing you to make asynchronous calls using coroutines, keeping the UI thread responsive.
- Handling common issues like timeouts, errors, and retries effectively.
Can you build a dictionary app without Retrofit?
- A modern framework for Kotlin applications, also supporting HTTP requests.
- The native way to make HTTP requests in Android. However, it's verbose and harder to use.
- A lower-level HTTP client (Retrofit is built on top of OkHttp).
- An older library for networking in Android. It's more suited for apps that require frequent updates like chat apps.
Step 1: Setting Up the Project
1. Create a New Project:
- Open Android Studio and create a new project.
- Choose the "Empty Compose Activity" template.
- Name your project (e.g., DictionaryApp).
2. Add Dependencies:
Sync your project after adding these dependencies.implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7")
implementation ("com.squareup.retrofit2:retrofit:2.9.0")
implementation ("com.squareup.retrofit2:converter-gson:2.9.0")
Step 2: Set Up Retrofit
package com.codingbihar.dictionaryappdemo
data class ApiResponse(
val word: String,
val meanings: List<Meaning>
)
data class Meaning(
val partOfSpeech: String,
val definitions: List<Definition>,
val synonyms: List<String> = emptyList(),
val antonyms: List<String> = emptyList()
)
data class Definition(
val definition: String
)
data class WordDetail(
val word: String,
val partOfSpeech: String,
val synonyms: List<String> = emptyList(),
val antonyms: List<String> = emptyList(),
val meanings: List<String>
)
2. Define API Interface:
package com.codingbihar.dictionaryappdemo
import retrofit2.http.GET
import retrofit2.http.Path
interface DictionaryApi {
@GET("entries/en/{word}")
suspend fun getDefinition(@Path("word") word: String): List<ApiResponse>
}
package com.codingbihar.dictionaryappdemo
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
object RetrofitInstance {
val api: DictionaryApi by lazy {
Retrofit.Builder()
.baseUrl("https://api.dictionaryapi.dev/api/v2/")
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(DictionaryApi::class.java)
}
}
Step 3: Design the UI with Jetpack Compose
Main UI:
package com.codingbihar.dictionaryappdemo
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
@Composable
fun DictionaryApp(viewModel: DictionaryViewModel = viewModel()) {
val searchQuery = remember { mutableStateOf("") }
val wordDetails by viewModel.wordDetails.observeAsState(emptyList())
val error by viewModel.error.observeAsState("")
val loading by viewModel.loading.observeAsState(false)
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
SearchBar(
query = searchQuery.value,
onQueryChange = { query ->
searchQuery.value = query
viewModel.searchWord(query)
}
)
if (loading) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize()
) {
CircularProgressIndicator()
}
} else {
if (error.isNotEmpty()) {
Text(
text = error,
color = Color.Red,
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(vertical = 8.dp)
)
} else {
WordDetailList(wordDetails = wordDetails)
}
}
}
}
@Composable
fun SearchBar(query: String, onQueryChange: (String) -> Unit) {
OutlinedTextField(
value = query,
onValueChange = onQueryChange,
modifier = Modifier.fillMaxWidth(),
label = { Text("Search for a word") }
)
}
@Composable
fun WordDetailList(wordDetails: List<WordDetail>) {
LazyColumn {
items(wordDetails) { detail ->
Column(modifier = Modifier.padding(vertical = 8.dp)) {
Text(
text = "${detail.word} (${detail.partOfSpeech})",
style = MaterialTheme.typography.headlineLarge,
modifier = Modifier.padding(bottom = 4.dp)
)
if (detail.synonyms.isNotEmpty()) {
Text(text = "Synonyms: ${detail.synonyms.joinToString(", ")}")
}
if (detail.antonyms.isNotEmpty()) {
Text(text = "Antonyms: ${detail.antonyms.joinToString(", ")}")
}
detail.meanings.forEach { meaning ->
Text(text = "• $meaning")
}
}
}
}
}
Step 4: Implement ViewModel
Create ViewModel:
Here, the searchWord function fetches word details from the API and updates the state accordingly.package com.codingbihar.dictionaryappdemo
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
import retrofit2.HttpException
import java.io.IOException
class DictionaryViewModel : ViewModel() {
private val _wordDetails = MutableLiveData<List<WordDetail>>()
val wordDetails: LiveData<List<WordDetail>> = _wordDetails
private val _error = MutableLiveData<String>()
val error: LiveData<String> = _error
private val _loading = MutableLiveData<Boolean>()
val loading: LiveData<Boolean> = _loading
fun searchWord(query: String) {
if (query.isBlank()) {
_wordDetails.value = emptyList()
_error.value = ""
_loading.value = false
return
}
viewModelScope.launch {
_loading.value = true
try {
val response = RetrofitInstance.api.getDefinition(query)
val extractedDetails = response.flatMap { apiResponse ->
apiResponse.meanings.map { meaning ->
WordDetail(
word = apiResponse.word,
partOfSpeech = meaning.partOfSpeech,
synonyms = meaning.synonyms,
antonyms = meaning.antonyms,
meanings = meaning.definitions.map { it.definition }
)
}
}
_wordDetails.value = extractedDetails
_error.value = ""
} catch (e: HttpException) {
_error.value = "HTTP error occurred."
} catch (e: IOException) {
_error.value = "Network error. Please check your connection."
} catch (e: Exception) {
_error.value = "An unexpected error occurred."
} finally {
_loading.value = false
}
}
}
}
Step 5: Run the App
- Build and run the app on an emulator or physical device.
- Enter a word in the search bar to fetch its details.
- The app displays the word's part of speech, synonyms, antonyms, and definitions.





