Building a Recipe Recommendation App with Kotlin and Spoonacular API
In this tutorial, we will build a recipe recommendation app using Kotlin and the Spoonacular API. The app will allow users to search for recipes, filter them based on ingredients, and provide personalized recommendations. We will start by setting up the development environment and integrating the Spoonacular API. Then, we will design the user interface and implement recipe filtering and search functionality. Finally, we will enhance the user experience by adding favorites functionality and personalized recommendations.
Benefits of Using Kotlin for App Development
Kotlin is a modern programming language that offers several benefits for app development. It is fully compatible with Java, which means you can easily integrate existing Java code into your Kotlin projects. Kotlin also provides several language features that make development more efficient, such as null safety, extension functions, and coroutines for asynchronous programming. Additionally, Kotlin has excellent support for Android development, making it a popular choice among developers.
Getting Started
To get started, we need to set up our development environment and create a new Kotlin project.
Setting up the Development Environment
Install Android Studio: Download and install the latest version of Android Studio from the official website.
Create a New Project: Open Android Studio and click on "Start a new Android Studio project". Choose an application name, domain, and project location. Select "Kotlin" as the language and choose the minimum SDK version.
Integrating Spoonacular API
To access recipe data, we will integrate the Spoonacular API into our app.
Registering for Spoonacular API Key
Create a Spoonacular Account: Visit the Spoonacular website and create a new account.
Get an API Key: Once you have an account, go to the API Dashboard and generate a new API key.
Making API Requests with Kotlin
To make API requests, we will use the Retrofit library in Kotlin.
- Add Retrofit Dependency: Open the project's
build.gradle
file and add the following line to the dependencies block:
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
- Create a Retrofit Service Interface: Create a new Kotlin file named
SpoonacularService.kt
and define an interface with the following code:
interface SpoonacularService {
@GET("recipes/random")
suspend fun getRandomRecipes(
@Query("apiKey") apiKey: String,
@Query("number") number: Int
): Response<RecipeResponse>
}
- Create a Retrofit Instance: In your main activity or application class, create a Retrofit instance with the following code:
val retrofit = Retrofit.Builder()
.baseUrl("https://api.spoonacular.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
val service = retrofit.create(SpoonacularService::class.java)
Handling API Responses
To handle API responses, we will use the Gson library in Kotlin.
- Add Gson Dependency: Open the project's
build.gradle
file and add the following line to the dependencies block:
implementation 'com.google.code.gson:gson:2.8.7'
- Define Response Models: Create a new Kotlin file named
RecipeResponse.kt
and define the response models with the following code:
data class RecipeResponse(
val recipes: List<Recipe>
)
data class Recipe(
val id: Int,
val title: String,
val image: String,
// Add additional properties as needed
)
- Parse API Responses: In your API service interface, update the return type of the
getRandomRecipes
function to beRecipeResponse
instead ofResponse<RecipeResponse>
. Add thesuspend
keyword to make the function coroutine compatible. Use thegson.fromJson
method to parse the JSON response into theRecipeResponse
object.
interface SpoonacularService {
@GET("recipes/random")
suspend fun getRandomRecipes(
@Query("apiKey") apiKey: String,
@Query("number") number: Int
): RecipeResponse
}
Designing the User Interface
Now that we have integrated the Spoonacular API, let's design the user interface for our recipe recommendation app.
Creating Layouts with XML
To create layouts, we will use XML files in Kotlin.
- Create a New Layout: Create a new XML file in the
res/layout
directory and define the layout for your main activity or fragment. Use XML tags to add views such asTextView
,ImageView
, andRecyclerView
.
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/titleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Recipe Recommendation App"
android:textSize="24sp"
android:layout_margin="16dp"/>
<ImageView
android:id="@+id/recipeImageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"/>
<!-- Add additional views as needed -->
</LinearLayout>
- Inflate the Layout: In your main activity or fragment, use the
LayoutInflater
to inflate the layout XML file and set it as the content view.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
Implementing Navigation
To implement navigation between screens, we will use the Navigation component in Kotlin.
- Add Navigation Dependency: Open the project's
build.gradle
file and add the following line to the dependencies block:
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.0'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.0'
- Define Navigation Graph: Create a new XML file named
nav_graph.xml
in theres/navigation
directory. Define the navigation graph with destinations and actions.
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
app:startDestination="@id/mainFragment">
<fragment
android:id="@+id/mainFragment"
android:name="com.example.recipeapp.MainFragment"
android:label="Main Fragment">
<action
android:id="@+id/action_mainFragment_to_recipeFragment"
app:destination="@id/recipeFragment" />
</fragment>
<fragment
android:id="@+id/recipeFragment"
android:name="com.example.recipeapp.RecipeFragment"
android:label="Recipe Fragment" />
<!-- Add additional fragments as needed -->
</navigation>
- Set Up Navigation: In your main activity or fragment, add the following code to set up navigation.
val navController = findNavController(R.id.nav_host_fragment)
val appBarConfiguration = AppBarConfiguration(navController.graph)
setupActionBarWithNavController(navController, appBarConfiguration)
Displaying Recipe Recommendations
Now that we have set up the user interface, let's display recipe recommendations using the Spoonacular API.
- Fetch Random Recipes: In your main activity or fragment, make an API request using the
getRandomRecipes
function and update the UI with the response.
CoroutineScope(Dispatchers.Main).launch {
val response = service.getRandomRecipes(apiKey, 5)
if (response.recipes.isNotEmpty()) {
val recipe = response.recipes.first()
titleTextView.text = recipe.title
Glide.with(this@MainActivity)
.load(recipe.image)
.into(recipeImageView)
}
}
Implementing Recipe Filtering
To allow users to filter recipes based on ingredients, we will add search functionality to our app.
Adding Search Functionality
- Create a Search View: In your main activity or fragment layout, add a
SearchView
to allow users to enter search queries.
<SearchView
android:id="@+id/searchView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:queryHint="Search for recipes"
android:layout_margin="16dp"/>
- Handle Search Queries: In your main activity or fragment, add a listener to the
SearchView
to handle search queries.
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
// Perform search based on query
return true
}
override fun onQueryTextChange(newText: String): Boolean {
// Perform search based on new text
return true
}
})
Filtering Recipes by Ingredients
To filter recipes based on ingredients, we will modify the API request and response handling.
- Update API Request: In your API service interface, add a new parameter to the
getRandomRecipes
function to specify the ingredients.
@GET("recipes/random")
suspend fun getRandomRecipes(
@Query("apiKey") apiKey: String,
@Query("number") number: Int,
@Query("ingredients") ingredients: String
): RecipeResponse
- Update API Call: In your main activity or fragment, update the API call to include the search query as ingredients.
val response = service.getRandomRecipes(apiKey, 5, query)
- Update Recipe Response: In your response models, add a new property to the
Recipe
model to store the list of ingredients.
data class Recipe(
val id: Int,
val title: String,
val image: String,
val ingredients: List<String>,
// Add additional properties as needed
)
- Update UI: In your main activity or fragment, update the UI to display the ingredients along with the recipe title and image.
val recipe = response.recipes.first()
val ingredientsText = recipe.ingredients.joinToString(", ")
titleTextView.text = recipe.title
ingredientsTextView.text = ingredientsText
Glide.with(this@MainActivity)
.load(recipe.image)
.into(recipeImageView)
Sorting and Categorizing Recipes
To enhance the filtering functionality, we can add sorting and categorizing options to our app.
- Add Sorting Options: In your main activity or fragment layout, add a
Spinner
to allow users to select a sorting option.
<Spinner
android:id="@+id/sortingSpinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:entries="@array/sorting_options"
android:layout_margin="16dp"/>
- Add Sorting Logic: In your main activity or fragment, add a listener to the
Spinner
to handle sorting options.
sortingSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
val selectedOption = parent.getItemAtPosition(position) as String
// Perform sorting based on selected option
}
override fun onNothingSelected(parent: AdapterView<*>) {
// Handle nothing selected
}
}
- Categorize Recipes: To categorize recipes, you can use tags or labels based on specific criteria such as cuisine, diet, or meal type. Add a new property to the
Recipe
model to store the category.
data class Recipe(
val id: Int,
val title: String,
val image: String,
val ingredients: List<String>,
val category: String,
// Add additional properties as needed
)
- Update UI: In your main activity or fragment, update the UI to display the category along with the recipe title and image.
val recipe = response.recipes.first()
val categoryText = recipe.category
titleTextView.text = recipe.title
categoryTextView.text = categoryText
Glide.with(this@MainActivity)
.load(recipe.image)
.into(recipeImageView)
Enhancing User Experience
To enhance the user experience, we can add favorites functionality, user ratings and reviews, and personalized recommendations.
Adding Favorites Functionality
- Add Favorites Button: In your main activity or fragment layout, add a
Button
orImageView
to allow users to add recipes to their favorites.
<Button
android:id="@+id/favoritesButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Add to Favorites"
android:layout_margin="16dp"/>
- Handle Favorites: In your main activity or fragment, add a listener to the favorites button to handle adding and removing recipes from favorites.
favoritesButton.setOnClickListener {
if (isFavorite) {
// Remove from favorites
favoritesButton.text = "Add to Favorites"
} else {
// Add to favorites
favoritesButton.text = "Remove from Favorites"
}
isFavorite = !isFavorite
}
Implementing User Ratings and Reviews
To allow users to rate and review recipes, we can add rating bars and text input fields.
- Add Rating Bar: In your recipe details layout, add a
RatingBar
to allow users to rate recipes.
<RatingBar
android:id="@+id/ratingBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"/>
- Add Review Text Field: In your recipe details layout, add an
EditText
to allow users to write reviews.
<EditText
android:id="@+id/reviewEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Write a review"
android:inputType="textMultiLine"
android:lines="4"
android:maxLines="4"
android:scrollbars="vertical"
android:layout_margin="16dp"/>
- Handle Rating and Review: In your recipe details activity or fragment, add code to handle rating and review submission.
ratingBar.setOnRatingBarChangeListener { _, rating, _ ->
// Handle rating change
}
submitButton.setOnClickListener {
val rating = ratingBar.rating
val review = reviewEditText.text.toString()
// Submit rating and review
}
Providing Personalized Recommendations
To provide personalized recommendations, we can use user preferences and behavior to suggest recipes.
Collect User Preferences: Add forms or settings screens to allow users to specify their dietary preferences, allergies, and favorite cuisines.
Analyze User Behavior: Track user interactions such as search queries, recipe views, and favorites to understand their preferences.
Implement Recommendation Algorithm: Based on user preferences and behavior, implement a recommendation algorithm to suggest personalized recipes.
Display Personalized Recommendations: Update the UI to display personalized recipe recommendations based on user preferences and behavior.
Conclusion
In this tutorial, we have built a recipe recommendation app using Kotlin and the Spoonacular API. We learned how to set up the development environment, integrate the API, design the user interface, and implement recipe filtering and search functionality. We also enhanced the user experience by adding favorites functionality, user ratings and reviews, and personalized recommendations. With these skills, you can now create your own recipe recommendation app and explore the possibilities of Kotlin in app development. Happy coding!