Open In App

How to Build Spotify Clone in Android?

Last Updated : 23 Apr, 2025
Comments
Improve
Suggest changes
Like Article
Like
Report

There are many android applications present in the market which are available to play songs on mobile phones. The famous one of all these music players is Spotify. In this article, we will be building a Spotify clone application using Spotify Web APIs.

 

How-to-Build-Spotify-Clone-in-Android


Step by Step Implementation

Step 1: Create a New Project

To create a new project in Android Studio please refer to How to Create/Start a New Project in Android Studio.

Step 2: Adding dependency

Navigate to Gradle Scripts > build.gradle.kts (Module :app) file and add the below dependency to it in the dependencies section.

dependencies {
...
implementation ("com.android.volley:volley:1.2.1")
implementation ("com.github.bumptech.glide:glide:4.16.0")
}

After adding the above dependency simply sync your project to install it. 

Step 3: Adding internet permissions

Navigate to app > AndroidManifest.xml file and add below internet permissions to it. 

<uses-permission android:name="android.permission.INTERNET" />

Step 4: Get Spotify client id and client secret key

  • Navigate to Spotify Developer Console.
  • Login with your existing spotify account or create a new account in Spotify - Signup.
  • Click on your profile picture in the top right corner of the screen and select Dashboard or just go to Spotify for Developers - Dashboard.
  • Now you will get to see an option Create app. Simply click on that option and then specify the app name and app description and create a new application.
  • Now you will be navigated to the application inside which we will get to see the client id and client secret key.  We have to simply copy the client id and client secret key.

We will be using these 2 parameters in our MainActivity file in our project in Step 10.


Step 5: Updating colors.xml

Navigate to app > res > values > colors.xml file and add the below colors to it. Below is the code for the colors.xml file. 

colors.xml:

XML
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="black">#FF000000</color>
    <color name="white">#FFFFFFFF</color>

    <color name="grey">#BDBDBD</color>
    <color name="darker_grey">#808080</color>
    <color name="fab_color">#20D761</color>
</resources>


Step 6: Adding drawables

Navigate to app > res > drawables, right click on the folder and choose New > Drawable Resource File and create two such files as play.xml and search.xml. Then, add the following code to both of the files respectively.

play.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="32dp"
    android:height="32dp"
    android:viewportWidth="283"
    android:viewportHeight="283">
  <path
      android:pathData="M211.99,211.9C250.99,172.9 250.99,109.67 211.99,70.67C172.99,31.68 109.76,31.68 70.77,70.67C31.77,109.67 31.77,172.9 70.77,211.9C109.76,250.89 172.99,250.89 211.99,211.9Z"
      android:fillColor="@color/fab_color"/>
  <path
      android:pathData="M197.5,141.29L113.32,192.72L113.32,89.85L197.5,141.29Z"
      android:fillColor="#ffffff"/>
</vector>
search.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="960"
    android:viewportHeight="960">
  <path
      android:pathData="M784,840 L532,588q-30,24 -69,38t-83,14q-109,0 -184.5,-75.5T120,380q0,-109 75.5,-184.5T380,120q109,0 184.5,75.5T640,380q0,44 -14,83t-38,69l252,252 -56,56ZM380,560q75,0 127.5,-52.5T560,380q0,-75 -52.5,-127.5T380,200q-75,0 -127.5,52.5T200,380q0,75 52.5,127.5T380,560Z"
      android:fillColor="#e8eaed"/>
</vector>


Step 7: Working with activity_main.xml

Navigate to app > res > layout > activity_main.xml and add the following lines of code. Then, create a layout, album_rv_item.xml and add the below code.

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
    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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/white"
        android:orientation="vertical">

        <!-- text view to display greeting-->
        <TextView
            android:id="@+id/idTVGreetHeading"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Good morning"
            android:layout_marginStart="24dp"
            android:layout_marginTop="32dp"
            android:textColor="@color/black"
            android:textSize="20sp"
            android:textStyle="bold" />

        <!-- edit text to search songs-->
        <EditText
            android:id="@+id/idEdtSearch"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="24dp"
            android:layout_marginEnd="24dp"
            android:layout_marginTop="16dp"
            android:background="@color/grey"
            android:drawableStart="@drawable/search"
            android:drawablePadding="8dp"
            android:drawableTint="@color/white"
            android:textColor="@color/white"
            android:hint="What do you want to listen to?"
            android:imeOptions="actionDone"
            android:lines="1"
            android:padding="10dp"
            android:singleLine="true"
            android:textColorHint="@color/white"
            android:textStyle="bold" />

        <!-- text view to display heading-->
        <TextView
            android:id="@+id/idTVHeading1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="24dp"
            android:paddingTop="16dp"
            android:text="Recommended for Today"
            android:textColor="@color/black"
            android:textSize="20sp"
            android:textStyle="bold" />

        <!-- recycler view for various albums-->
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/idRVAlbums"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
            tools:listitem="@layout/album_rv_item" />

        <!-- text view to display heading-->
        <TextView
            android:id="@+id/idTVHeading2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="24dp"
            android:layout_marginTop="16dp"
            android:paddingTop="16dp"
            android:text="Popular Albums"
            android:textColor="@color/black"
            android:textSize="20sp"
            android:textStyle="bold" />

        <!-- recycler view for popular albums-->
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/idRVPopularAlbums"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
            tools:listitem="@layout/album_rv_item" />

        <!-- text view to display heading-->
        <TextView
            android:id="@+id/idTVHeading3"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="24dp"
            android:layout_marginTop="16dp"
            android:paddingTop="16dp"
            android:text="Trending now"
            android:textColor="@color/black"
            android:textSize="20sp"
            android:textStyle="bold" />

        <!-- recycler view for various albums-->
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/idRVTrendingAlbums"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
            tools:listitem="@layout/album_rv_item" />

    </LinearLayout>

</ScrollView>
album_rv_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerInParent="true"
    android:orientation="vertical"
    android:background="@color/white"
    android:paddingTop="8dp"
    android:layout_marginStart="24dp">

    <!--image view for displaying album image-->
    <ImageView
        android:id="@+id/idIVAlbum"
        android:layout_width="120dp"
        android:layout_height="120dp" />

    <!-- text view for displaying album name -->
    <TextView
        android:id="@+id/idTVAlbumName"
        android:layout_width="120dp"
        android:layout_height="wrap_content"
        android:layout_below="@id/idIVAlbum"
        android:layout_marginTop="3dp"
        android:lines="1"
        android:maxLines="1"
        android:text="Album Name"
        android:textColor="@color/black"
        android:textSize="12sp"
        android:textStyle="bold" />

    <!-- text view for displaying album artist-->
    <TextView
        android:id="@+id/idTVALbumDetails"
        android:layout_width="120dp"
        android:layout_height="wrap_content"
        android:layout_below="@id/idTVAlbumName"
        android:layout_marginTop="3dp"
        android:maxLines="1"
        android:text="Album Details"
        android:textColor="@color/black"
        android:textSize="12sp" />

</LinearLayout>


Design UI:

spotify-home


Step 8: Create an album model class

Create a Java/Kotlin data class file AlbumModel and add the following code

AlbumModel.java
package org.geeksforgeeks.demo;

public class AlbumModel {
    private String album_type;
    private String artistName;
    private String external_ids;
    private String external_urls;
    private String href;
    private String id;
    private String imageUrl;
    private String label;
    private String name;
    private int popularity;
    private String release_date;
    private int total_tracks;
    private String type;

    public AlbumModel(String album_type, String artistName, String external_ids, String external_urls,
                      String href, String id, String imageUrl, String label, String name,
                      int popularity, String release_date, int total_tracks, String type) {
        this.album_type = album_type;
        this.artistName = artistName;
        this.external_ids = external_ids;
        this.external_urls = external_urls;
        this.href = href;
        this.id = id;
        this.imageUrl = imageUrl;
        this.label = label;
        this.name = name;
        this.popularity = popularity;
        this.release_date = release_date;
        this.total_tracks = total_tracks;
        this.type = type;
    }

    // Add getters (and setters if needed)

    public String getAlbum_type() { return album_type; }
    public String getArtistName() { return artistName; }
    public String getExternal_ids() { return external_ids; }
    public String getExternal_urls() { return external_urls; }
    public String getHref() { return href; }
    public String getId() { return id; }
    public String getImageUrl() { return imageUrl; }
    public String getLabel() { return label; }
    public String getName() { return name; }
    public int getPopularity() { return popularity; }
    public String getRelease_date() { return release_date; }
    public int getTotal_tracks() { return total_tracks; }
    public String getType() { return type; }
AlbumModel.kt
package org.geeksforgeeks.demo

data class AlbumModel(
    val album_type: String,
    val artistName: String,
    val external_ids: String,
    val external_urls: String,
    val href: String,
    val id: String,
    val imageUrl: String,
    val label: String,
    val name: String,
    val popularity: Int,
    val release_date: String,
    val total_tracks: Int,
    val type: String
)


Step 9: Creating an Adapter for each album

Create a new Java/Kotlin file AlbumAdapter and add the following code

AlbumAdapter.java
package org.geeksforgeeks.demo;

import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import com.bumptech.glide.Glide;

import org.geeksforgeeks.demo.databinding.AlbumRvItemBinding;

import java.util.List;

public class AlbumAdapter extends RecyclerView.Adapter<AlbumAdapter.AlbumViewHolder> {

    private final List<AlbumModel> albums;
    private final Context context;

    public AlbumAdapter(List<AlbumModel> albums, Context context) {
        this.albums = albums;
        this.context = context;
    }

    @NonNull
    @Override
    public AlbumViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
        AlbumRvItemBinding binding = AlbumRvItemBinding.inflate(inflater, parent, false);
        return new AlbumViewHolder(binding);
    }

    @Override
    public void onBindViewHolder(@NonNull AlbumViewHolder holder, int position) {
        AlbumModel album = albums.get(position);
        holder.bind(album);

        holder.itemView.setOnClickListener(v -> {
            Intent intent = new Intent(context, AlbumDetailActivity.class);
            intent.putExtra("id", album.getId());
            intent.putExtra("name", album.getName());
            intent.putExtra("img", album.getImageUrl());
            intent.putExtra("artist", album.getArtistName());
            intent.putExtra("albumUrl", album.getExternal_urls());
            context.startActivity(intent);
        });
    }

    @Override
    public int getItemCount() {
        return albums.size();
    }

    public class AlbumViewHolder extends RecyclerView.ViewHolder {
        private final AlbumRvItemBinding binding;

        public AlbumViewHolder(AlbumRvItemBinding binding) {
            super(binding.getRoot());
            this.binding = binding;
        }

        public void bind(AlbumModel album) {
            Glide.with(context)
                    .load(album.getImageUrl())
                    .into(binding.idIVAlbum);

            binding.idTVAlbumName.setText(album.getName());
            binding.idTVALbumDetails.setText(album.getArtistName());
        }
    }
}
AlbumAdapter.kt
package org.geeksforgeeks.demo

import android.content.Context
import android.content.Intent
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import org.geeksforgeeks.demo.databinding.AlbumRvItemBinding

class AlbumAdapter(
    private val albums: List<AlbumModel>,
    private val context: Context
) : RecyclerView.Adapter<AlbumAdapter.AlbumViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AlbumViewHolder {
        val binding = AlbumRvItemBinding.inflate(
            LayoutInflater.from(parent.context),
            parent,
            false
        )
        return AlbumViewHolder(binding)
    }

    override fun onBindViewHolder(holder: AlbumViewHolder, position: Int) {
        val album = albums[position]
        holder.bind(album)

        holder.itemView.setOnClickListener {
            context.startActivity(
                Intent(context, AlbumDetailActivity::class.java).apply {
                    putExtra("id", album.id)
                    putExtra("name", album.name)
                    putExtra("img", album.imageUrl)
                    putExtra("artist", album.artistName)
                    putExtra("albumUrl", album.external_urls)
                }
            )
        }
    }

    override fun getItemCount() = albums.size

    inner class AlbumViewHolder(private val binding: AlbumRvItemBinding) :
        RecyclerView.ViewHolder(binding.root) {

        fun bind(album: AlbumModel) {
            Glide.with(context)
                .load(album.imageUrl)
                .into(binding.idIVAlbum)

            binding.idTVAlbumName.text = album.name
            binding.idTVALbumDetails.text = album.artistName
        }
    }
}


Step 10: Working with MainActivity

Navigate to app > java > {package-name} > MainActivity.kt/.java and add the following code. Here we will be writing the code for the spotify home page.

MainActivity.java
package org.geeksforgeeks.demo;

import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Base64;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import com.android.volley.AuthFailureError;
import com.android.volley.Request;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MainActivity extends AppCompatActivity {

    private RecyclerView albumsRV, popularAlbumsRV, trendingAlbumsRV;
    private EditText searchEdt;
    private boolean isTokenGenerated = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initializeViews();   // Link UI elements with code
        setupSearchView();   // Set listener for the search action
        generateToken();     // Get Spotify API token
    }

    private void initializeViews() {
        albumsRV = findViewById(R.id.idRVAlbums);
        popularAlbumsRV = findViewById(R.id.idRVPopularAlbums);
        trendingAlbumsRV = findViewById(R.id.idRVTrendingAlbums);
        searchEdt = findViewById(R.id.idEdtSearch);

        // Set horizontal layout for each RecyclerView
        RecyclerView[] recyclerViews = {albumsRV, popularAlbumsRV, trendingAlbumsRV};
        for (RecyclerView rv : recyclerViews) {
            rv.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
            rv.setHasFixedSize(true);
        }
    }

    private void setupSearchView() {
        searchEdt.setOnEditorActionListener((v, actionId, event) -> {
            if (actionId == EditorInfo.IME_ACTION_DONE) {
                searchTracks(searchEdt.getText().toString().trim());
                return true;
            }
            return false;
        });
    }

    private void searchTracks(String searchQuery) {
        if (!searchQuery.isEmpty()) {
            // Navigate to SearchActivity with query
            Intent intent = new Intent(this, SearchActivity.class);
            intent.putExtra("searchQuery", searchQuery);
            startActivity(intent);
        } else {
            Toast.makeText(this, "Please enter a search term", Toast.LENGTH_SHORT).show();
        }
    }

    private void generateToken() {
        String url = "https://accounts.spotify.com/api/token";

        StringRequest request = new StringRequest(Request.Method.POST, url,
                response -> {
                    try {
                        JSONObject jsonObject = new JSONObject(response);
                        String token = jsonObject.getString("access_token");

                        // Save token in SharedPreferences
                        SharedPreferences sharedPreferences = getSharedPreferences("MySharedPref", MODE_PRIVATE);
                        sharedPreferences.edit().putString("token", "Bearer " + token).apply();

                        isTokenGenerated = true;
                        loadAllAlbumData(); // Load albums after token is ready
                    } catch (JSONException e) {
                        showError("Failed to parse token response: " + e.getMessage());
                    }
                },
                error -> showError("Failed to get token: " + error.getMessage())
        ) {
            @Override
            public Map<String, String> getHeaders() {
                String clientId = "Enter your own client id";
                String clientSecret = "Enter your own client secret";
                String credentials = clientId + ":" + clientSecret;
                String auth = Base64.encodeToString(credentials.getBytes(), Base64.NO_WRAP);

                Map<String, String> headers = new HashMap<>();
                headers.put("Authorization", "Basic " + auth);
                headers.put("Content-Type", "application/x-www-form-urlencoded");
                return headers;
            }

            @Override
            protected Map<String, String> getParams() {
                Map<String, String> params = new HashMap<>();
                params.put("grant_type", "client_credentials");
                return params;
            }
        };

        Volley.newRequestQueue(this).add(request);
    }

    private void loadAllAlbumData() {
        if (!isTokenGenerated) {
            showError("Token not generated yet");
            return;
        }

        // Load Recommended Albums
        loadAlbums(albumsRV, List.of(
                "5Nwsra93UQYJ6xxcjcE10x", "0z7bJ6UpjUw8U4TATtc5Ku", "36UJ90D0e295TvlU109Xvy",
                "3uuu6u13U0KeVQsZ3CZKK4", "45ZIondgVoMB84MQQaUo9T", "15CyNDuGY5fsG0Hn9rjnpG",
                "1HeX4SmCFW4EPHQDvHgrVS", "6mCDTT1XGTf48p6FkK9qFL"
        ));

        // Load Popular Albums
        loadAlbums(popularAlbumsRV, List.of(
                "0sjyZypccO1vyihqaAkdt3", "17vZRWjKOX7TmMktjQL2Qx", "5Nwsra93UQYJ6xxcjcE10x",
                "2zXKlf81VmDHIMtQe3oD0r", "7Gws1vUsWltRs58x8QuYVQ", "7uftfPn8f7lwtRLUrEVRYM",
                "7kSY0fqrPep5vcwOb1juye"
        ));

        // Load Trending Albums
        loadAlbums(trendingAlbumsRV, List.of(
                "1P4eCx5b11Tfmi4s1GmWmQ", "2SsEtiB6yJYn8hRRAmtVda", "7hhxms8KCwlQCWffIJpN9b",
                "3umvKIjsD484pa9pCyPK2x", "3OHC6XD29wXWADtAOP2geV", "3RZxrS2dDZlbsYtMRM89v8",
                "24C47633GRlozws7WBth7t"
        ));
    }

    private void loadAlbums(RecyclerView recyclerView, List<String> albumIds) {
        SharedPreferences sharedPreferences = getSharedPreferences("MySharedPref", MODE_PRIVATE);
        String token = sharedPreferences.getString("token", "");

        if (token.isEmpty()) {
            showError("Authentication token is missing");
            return;
        }

        String url = "https://api.spotify.com/v1/albums?ids=" + String.join(",", albumIds);

        JsonObjectRequest request = new JsonObjectRequest(Request.Method.GET, url, null,
                response -> {
                    try {
                        parseAlbumResponse(response, recyclerView);
                    } catch (Exception e) {
                        showError("Failed to parse album data: " + e.getMessage());
                    }
                },
                error -> showError("Failed to load albums: " + error.getMessage())
        ) {
            @Override
            public Map<String, String> getHeaders() {
                Map<String, String> headers = new HashMap<>();
                headers.put("Authorization", token);
                headers.put("Accept", "application/json");
                headers.put("Content-Type", "application/json");
                return headers;
            }
        };

        Volley.newRequestQueue(this).add(request);
    }

    private void parseAlbumResponse(JSONObject response, RecyclerView recyclerView) {
        try {
            JSONArray albumArray = response.getJSONArray("albums");
            List<AlbumModel> albumList = new ArrayList<>();

            for (int i = 0; i < albumArray.length(); i++) {
                JSONObject albumObj = albumArray.getJSONObject(i);
                JSONArray artists = albumObj.getJSONArray("artists");
                JSONArray images = albumObj.getJSONArray("images");

                String artistName = (artists.length() > 0)
                        ? artists.getJSONObject(0).optString("name", "Unknown Artist")
                        : "Unknown Artist";

                String imageUrl = (images.length() > 1)
                        ? images.getJSONObject(1).optString("url", "")
                        : "";

                albumList.add(new AlbumModel(
                        albumObj.optString("album_type", "album"),
                        artistName,
                        albumObj.getJSONObject("external_ids").optString("upc", ""),
                        albumObj.getJSONObject("external_urls").optString("spotify", ""),
                        albumObj.optString("href", ""),
                        albumObj.optString("id", ""),
                        imageUrl,
                        albumObj.optString("label", ""),
                        albumObj.optString("name", "Unknown Album"),
                        albumObj.optInt("popularity", 0),
                        albumObj.optString("release_date", ""),
                        albumObj.optInt("total_tracks", 0),
                        albumObj.optString("type", "album")
                ));
            }

            recyclerView.setAdapter(new AlbumAdapter(albumList, this));

        } catch (JSONException e) {
            showError("JSON parsing error: " + e.getMessage());
        } catch (Exception e) {
            showError("Error parsing album data: " + e.getMessage());
        }
    }

    private void showError(String message) {
        Toast.makeText(this, message, Toast.LENGTH_LONG).show();
    }
}
MainActivity.kt
package org.geeksforgeeks.demo

import android.content.Intent
import android.os.Bundle
import android.util.Base64
import android.view.inputmethod.EditorInfo
import android.widget.EditText
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.edit
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.android.volley.Request
import com.android.volley.toolbox.JsonObjectRequest
import com.android.volley.toolbox.StringRequest
import com.android.volley.toolbox.Volley
import org.json.JSONException
import org.json.JSONObject

class MainActivity : AppCompatActivity() {

    private lateinit var albumsRV: RecyclerView
    private lateinit var popularAlbumsRV: RecyclerView
    private lateinit var trendingAlbumsRV: RecyclerView
    private lateinit var searchEdt: EditText
    private var isTokenGenerated = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        initializeViews()
        setupSearchView()
        generateToken()
    }

    private fun initializeViews() {
        albumsRV = findViewById(R.id.idRVAlbums)
        popularAlbumsRV = findViewById(R.id.idRVPopularAlbums)
        trendingAlbumsRV = findViewById(R.id.idRVTrendingAlbums)
        searchEdt = findViewById(R.id.idEdtSearch)

        // Setup RecyclerViews with horizontal layout
        listOf(albumsRV, popularAlbumsRV, trendingAlbumsRV).forEach { recyclerView ->
            recyclerView.layoutManager = LinearLayoutManager(
                this,
                LinearLayoutManager.HORIZONTAL,
                false
            )
            recyclerView.setHasFixedSize(true)
        }
    }

    private fun setupSearchView() {
        searchEdt.setOnEditorActionListener { _, actionId, _ ->
            if (actionId == EditorInfo.IME_ACTION_DONE) {
                searchTracks(searchEdt.text.toString().trim())
                true
            } else {
                false
            }
        }
    }

    private fun searchTracks(searchQuery: String) {
        if (searchQuery.isNotEmpty()) {
            startActivity(
                Intent(this, SearchActivity::class.java).apply {
                    putExtra("searchQuery", searchQuery)
                }
            )
        } else {
            Toast.makeText(this, "Please enter a search term", Toast.LENGTH_SHORT).show()
        }
    }

    private fun generateToken() {
        val url = "https://accounts.spotify.com/api/token"
        val queue = Volley.newRequestQueue(this)

        val request = object : StringRequest(
            Request.Method.POST, url,
            { response ->
                try {
                    val token = JSONObject(response).getString("access_token")
                    getSharedPreferences("MySharedPref", MODE_PRIVATE).edit {
                        putString("token", "Bearer $token")
                        apply()
                    }
                    isTokenGenerated = true
                    // Load data after token is generated
                    loadAllAlbumData()
                } catch (e: JSONException) {
                    showError("Failed to parse token response: ${e.message}")
                }
            },
            { error -> showError("Failed to get token: ${error.message}") }
        ) {
            override fun getHeaders(): Map<String, String> {
                val clientId = "Enter your own client id"
                val clientSecret = "Enter your own client secret"
                val credentials = "$clientId:$clientSecret"
                val auth = Base64.encodeToString(credentials.toByteArray(), Base64.NO_WRAP)

                return mapOf(
                    "Authorization" to "Basic $auth",
                    "Content-Type" to "application/x-www-form-urlencoded"
                )
            }

            override fun getParams(): MutableMap<String, String> {
                return mutableMapOf("grant_type" to "client_credentials")
            }
        }
        queue.add(request)
    }

    private fun loadAllAlbumData() {
        if (!isTokenGenerated) {
            showError("Token not generated yet")
            return
        }

        // Recommended albums
        loadAlbums(
            recyclerView = albumsRV,
            albumIds = listOf(
                "5Nwsra93UQYJ6xxcjcE10x",
                "0z7bJ6UpjUw8U4TATtc5Ku",
                "36UJ90D0e295TvlU109Xvy",
                "3uuu6u13U0KeVQsZ3CZKK4",
                "45ZIondgVoMB84MQQaUo9T",
                "15CyNDuGY5fsG0Hn9rjnpG",
                "1HeX4SmCFW4EPHQDvHgrVS",
                "6mCDTT1XGTf48p6FkK9qFL"
            )
        )

        // Popular albums
        loadAlbums(
            recyclerView = popularAlbumsRV,
            albumIds = listOf(
                "0sjyZypccO1vyihqaAkdt3",
                "17vZRWjKOX7TmMktjQL2Qx",
                "5Nwsra93UQYJ6xxcjcE10x",
                "2zXKlf81VmDHIMtQe3oD0r",
                "7Gws1vUsWltRs58x8QuYVQ",
                "7uftfPn8f7lwtRLUrEVRYM",
                "7kSY0fqrPep5vcwOb1juye"
            )
        )

        // Trending albums
        loadAlbums(
            recyclerView = trendingAlbumsRV,
            albumIds = listOf(
                "1P4eCx5b11Tfmi4s1GmWmQ",
                "2SsEtiB6yJYn8hRRAmtVda",
                "7hhxms8KCwlQCWffIJpN9b",
                "3umvKIjsD484pa9pCyPK2x",
                "3OHC6XD29wXWADtAOP2geV",
                "3RZxrS2dDZlbsYtMRM89v8",
                "24C47633GRlozws7WBth7t"
            )
        )
    }

    private fun loadAlbums(recyclerView: RecyclerView, albumIds: List<String>) {
        val token = getSharedPreferences("MySharedPref", MODE_PRIVATE)
            .getString("token", "") ?: ""

        if (token.isEmpty()) {
            showError("Authentication token is missing")
            return
        }

        val url = "https://api.spotify.com/v1/albums?ids=${albumIds.joinToString(",")}"
        val queue = Volley.newRequestQueue(this)

        val request = object : JsonObjectRequest(
            Request.Method.GET, url, null,
            { response ->
                try {
                    parseAlbumResponse(response, recyclerView)
                } catch (e: Exception) {
                    showError("Failed to parse album data: ${e.message}")
                }
            },
            { error ->
                showError("Failed to load albums: ${error.message}")
            }
        ) {
            override fun getHeaders(): Map<String, String> {
                return mapOf(
                    "Authorization" to token,
                    "Accept" to "application/json",
                    "Content-Type" to "application/json"
                )
            }
        }
        queue.add(request)
    }

    private fun parseAlbumResponse(response: JSONObject, recyclerView: RecyclerView) {
        try {
            val albumArray = response.getJSONArray("albums")
            val albumList = mutableListOf<AlbumModel>()

            for (i in 0 until albumArray.length()) {
                val albumObj = albumArray.getJSONObject(i)
                val artists = albumObj.getJSONArray("artists")
                val images = albumObj.getJSONArray("images")

                albumList.add(AlbumModel(
                    album_type = albumObj.optString("album_type", "album"),
                    artistName = if (artists.length() > 0)
                        artists.getJSONObject(0).optString("name", "Unknown Artist")
                    else "Unknown Artist",
                    external_ids = albumObj.getJSONObject("external_ids").optString("upc", ""),
                    external_urls = albumObj.getJSONObject("external_urls").optString("spotify", ""),
                    href = albumObj.optString("href", ""),
                    id = albumObj.optString("id", ""),
                    imageUrl = if (images.length() > 1)
                        images.getJSONObject(1).optString("url", "")
                    else "",
                    label = albumObj.optString("label", ""),
                    name = albumObj.optString("name", "Unknown Album"),
                    popularity = albumObj.optInt("popularity", 0),
                    release_date = albumObj.optString("release_date", ""),
                    total_tracks = albumObj.optInt("total_tracks", 0),
                    type = albumObj.optString("type", "album")
                ))
            }

            recyclerView.adapter = AlbumAdapter(albumList, this)
        } catch (e: JSONException) {
            showError("JSON parsing error: ${e.message}")
        } catch (e: Exception) {
            showError("Error parsing album data: ${e.message}")
        }
    }

    private fun showError(message: String) {
        Toast.makeText(this, message, Toast.LENGTH_LONG).show()
    }
}


Step 11: Create a new Activity (Search Activity)

Create a new activity with the name SearchActivity. Now, navigate to app > res > layout > activity_search.xml and add the following code. Then, create a layout track_rv_item.xml and add the following code respectively.

activity_search.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white"
    android:orientation="vertical"
    tools:context=".SearchActivity">

    <!-- edit text to search songs-->
    <EditText
        android:id="@+id/idEdtSearch"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/darker_grey"
        android:hint="What do you want to listen to?"
        android:imeOptions="actionDone"
        android:lines="1"
        android:drawableStart="@drawable/search"
        android:drawablePadding="8dp"
        android:drawableTint="@color/grey"
        android:padding="16dp"
        android:singleLine="true"
        android:textColor="@color/white"
        android:textColorHint="@color/grey"
        android:textStyle="bold" />

    <!-- recycler view t display search results-->
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/idRVSongs"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        tools:listitem="@layout/track_rv_item" />

</LinearLayout>
track_rv_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="16dp"
    android:layout_marginHorizontal="8dp"
    android:background="@color/white"
    android:orientation="vertical"
    android:padding="4dp">

    <!-- text view for displaying track name-->
    <TextView
        android:id="@+id/idTVTrackName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Track Name"
        android:textColor="@color/black"
        android:textSize="17sp" />

    <!-- text view for displaying artist name-->
    <TextView
        android:id="@+id/idTVTrackArtist"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Track Artist"
        android:textColor="@color/darker_grey"
        android:textSize="12sp" />

</LinearLayout>


Design UI:

spotify-search


Step 12: Create a track model class

Create a Java/Kotlin data class file TrackModel and add the following code

TrackModel.java
package org.geeksforgeeks.demo;

public class TrackModel {
    private String trackName;
    private String trackArtist;
    private String id;

    // Constructor
    public TrackModel(String trackName, String trackArtist, String id) {
        this.trackName = trackName;
        this.trackArtist = trackArtist;
        this.id = id;
    }

    // Getters
    public String getTrackName() {
        return trackName;
    }

    public String getTrackArtist() {
        return trackArtist;
    }

    public String getId() {
        return id;
    }

    // Setters
    public void setTrackName(String trackName) {
        this.trackName = trackName;
    }

    public void setTrackArtist(String trackArtist) {
        this.trackArtist = trackArtist;
    }

    public void setId(String id) {
        this.id = id;
    }
}
TrackModel.kt
package org.geeksforgeeks.demo

class TrackModel(
    var trackName: String,
    var trackArtist: String,
    var id: String
)


Step 13: Create an Adapter for each music

Create a Java/Kotlin file TrackAdapter and add the following code.

TrackAdapter.java
package org.geeksforgeeks.demo;

import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.util.ArrayList;

public class TrackAdapter extends RecyclerView.Adapter<TrackAdapter.ViewHolder> {

    private final ArrayList<TrackModel> trackModels;
    private final Context context;

    public TrackAdapter(ArrayList<TrackModel> trackModels, Context context) {
        this.trackModels = trackModels;
        this.context = context;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        // inflating layout on below line.
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.track_rv_item, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        // setting data to text views.
        TrackModel trackRVModal = trackModels.get(position);
        holder.trackNameTV.setText(trackRVModal.getTrackName());
        holder.trackArtistTV.setText(trackRVModal.getTrackArtist());

        // adding click listener for track item view
        holder.itemView.setOnClickListener(v -> {
            String trackUrl = "https://open.spotify.com/track/" + trackRVModal.getId();
            Uri uri = Uri.parse(trackUrl); // missing 'http://' will cause crash
            Intent intent = new Intent(Intent.ACTION_VIEW, uri);
            context.startActivity(intent);
        });
    }

    @Override
    public int getItemCount() {
        return trackModels.size();
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {
        // creating and initializing variables for text views.
        TextView trackNameTV;
        TextView trackArtistTV;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            trackNameTV = itemView.findViewById(R.id.idTVTrackName);
            trackArtistTV = itemView.findViewById(R.id.idTVTrackArtist);
        }
    }
}
TrackAdapter.kt
package org.geeksforgeeks.demo

import android.content.Context
import android.content.Intent
import android.net.Uri
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView


class TrackAdapter(
    private val trackModels: ArrayList<TrackModel>, private val context: Context
) :
    RecyclerView.Adapter<TrackAdapter.ViewHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        // inflating layout on below line.
        val view: View =
            LayoutInflater.from(parent.context).inflate(R.layout.track_rv_item, parent, false)
        return ViewHolder(view)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        // setting data to text views.
        val trackRVModal = trackModels[position]
        holder.trackNameTV.text = trackRVModal.trackName
        holder.trackArtistTV.text = trackRVModal.trackArtist
        // adding click listener for track item view
        holder.itemView.setOnClickListener {
            val trackUrl = "https://open.spotify.com/track/" + trackRVModal.id
            val uri = Uri.parse(trackUrl) // missing 'http://' will cause crashed
            val intent = Intent(Intent.ACTION_VIEW, uri)
            context.startActivity(intent)
        }
    }

    override fun getItemCount(): Int {
        return trackModels.size
    }

    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        // creating and initializing variables for text views.
        internal val trackNameTV: TextView = itemView.findViewById<TextView>(R.id.idTVTrackName)
        val trackArtistTV: TextView = itemView.findViewById<TextView>(R.id.idTVTrackArtist)
    }
}


Step 14: Working with SearchActivity

Navigate to SearchActivity and add the following code

SearchActivity.java
package org.geeksforgeeks.demo;

import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.RecyclerView;

import com.android.volley.Request;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.Volley;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

public class SearchActivity extends AppCompatActivity {

    // on below line creating variables
    private String searchQuery = "";
    private EditText searchEdt;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_search);

        // on below line initializing variables.
        searchEdt = findViewById(R.id.idEdtSearch);
        searchQuery = getIntent().getStringExtra("searchQuery");
        searchEdt.setText(searchQuery);

        // on below line adding action listener
        // for search edit text
        searchEdt.setOnEditorActionListener((v, actionId, event) -> {
            if (actionId == EditorInfo.IME_ACTION_DONE) {
                // on below line calling method to get tracks.
                getTracks(searchEdt.getText().toString());
                return true;
            }
            return false;
        });

        // on below line getting tracks.
        getTracks(searchQuery);
    }

    // method to get token.
    private String getToken() {
        SharedPreferences sh = getSharedPreferences("MySharedPref", MODE_PRIVATE);
        return sh.getString("token", "Not Found");
    }

    private void getTracks(String searchQuery) {
        // on below line creating and initializing variables
        // for recycler view, list and adapter.
        RecyclerView songsRV = findViewById(R.id.idRVSongs);
        ArrayList<TrackModel> trackModels = new ArrayList<>();
        TrackAdapter trackAdapter = new TrackAdapter(trackModels, this);
        songsRV.setAdapter(trackAdapter);

        // on below line creating variable for url.
        String url = "https://api.spotify.com/v1/search?q=" + searchQuery + "&type=track";

        // on below line making json object request
        JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(
                Request.Method.GET, url, null,
                response -> {
                    try {
                        JSONObject trackObj = response.getJSONObject("tracks");
                        JSONArray itemsArray = trackObj.getJSONArray("items");
                        for (int i = 0; i < itemsArray.length(); i++) {
                            JSONObject itemObj = itemsArray.getJSONObject(i);
                            String trackName = itemObj.getString("name");
                            String trackArtist = itemObj.getJSONArray("artists").getJSONObject(0).getString("name");
                            String trackID = itemObj.getString("id");

                            // on below line adding data to array list
                            trackModels.add(new TrackModel(trackName, trackArtist, trackID));
                        }

                        // on below line notifying adapter
                        trackAdapter.notifyDataSetChanged();
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                },
                error -> Toast.makeText(
                        SearchActivity.this,
                        "Fail to get data : " + error, Toast.LENGTH_SHORT
                ).show()
        ) {
            @Override
            public Map<String, String> getHeaders() {
                // on below line adding headers.
                HashMap<String, String> headers = new HashMap<>();
                headers.put("Authorization", getToken());
                headers.put("Accept", "application/json");
                headers.put("Content-Type", "application/json");
                return headers;
            }
        };

        // adding json object request to queue.
        Volley.newRequestQueue(this).add(jsonObjectRequest);
    }
}
SearchActivity.kt
package org.geeksforgeeks.demo

import android.os.Bundle
import android.view.inputmethod.EditorInfo
import android.widget.EditText
import android.widget.TextView.OnEditorActionListener
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView
import com.android.volley.AuthFailureError
import com.android.volley.Response
import com.android.volley.toolbox.JsonObjectRequest
import com.android.volley.toolbox.Volley
import org.json.JSONException
import org.json.JSONObject

class SearchActivity : AppCompatActivity() {
    // on below line creating variables
    private var searchQuery: String? = ""
    private lateinit var searchEdt: EditText

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_search)
        // on below line initializing variables.
        searchEdt = findViewById(R.id.idEdtSearch)
        searchQuery = intent.getStringExtra("searchQuery")
        searchEdt.setText(searchQuery)
        // on below line adding action listener
        // for search edit text
        searchEdt.setOnEditorActionListener(OnEditorActionListener { v, actionId, event ->
            if (actionId == EditorInfo.IME_ACTION_DONE) {
                // on below line calling method to get tracks.
                getTracks(searchEdt.getText().toString())
                return@OnEditorActionListener true
            }
            false
        })
        // on below line getting tracks.
        getTracks(searchQuery)
    }

    private val token: String?
        // method to get token.
        get() {
            val sh = getSharedPreferences("MySharedPref", MODE_PRIVATE)
            return sh.getString("token", "Not Found")
        }


    private fun getTracks(searchQuery: String?) {
        // on below line creating and initializing variables
        // for recycler view,list and adapter.
        val songsRV = findViewById<RecyclerView>(R.id.idRVSongs)
        val trackModels = ArrayList<TrackModel>()
        val trackAdapter = TrackAdapter(trackModels, this)
        songsRV.adapter = trackAdapter
        // on below line creating variable for url.
        val url = "https://api.spotify.com/v1/search?q=$searchQuery&type=track"
        val queue = Volley.newRequestQueue(this@SearchActivity)
        // on below line making json object request
        val jsonObjectRequest: JsonObjectRequest = object : JsonObjectRequest(
            Method.GET, url, null,
            Response.Listener<JSONObject> { response ->
                try {
                    val trackObj = response.getJSONObject("tracks")
                    val itemsArray = trackObj.getJSONArray("items")
                    for (i in 0 until itemsArray.length()) {
                        val itemObj = itemsArray.getJSONObject(i)
                        val trackName = itemObj.getString("name")
                        val trackArtist =
                            itemObj.getJSONArray("artists").getJSONObject(0).getString("name")
                        val trackID = itemObj.getString("id")
                        // on below line adding data to array list
                        trackModels.add(TrackModel(trackName, trackArtist, trackID))
                    }
                    // on below line notifying adapter
                    trackAdapter.notifyDataSetChanged()
                } catch (e: JSONException) {
                    e.printStackTrace()
                }
            },
            Response.ErrorListener { error ->
                Toast.makeText(
                    this@SearchActivity,
                    "Fail to get data : $error", Toast.LENGTH_SHORT
                ).show()
            }) {
            @Throws(AuthFailureError::class)
            override fun getHeaders(): Map<String, String> {
                // on below line adding headers.
                val headers = HashMap<String, String>()
                headers["Authorization"] = token!!
                headers["Accept"] = "application/json"
                headers["Content-Type"] = "application/json"
                return headers
            }
        }
        // adding json object request to queue.
        queue.add(jsonObjectRequest)
    }
}


Step 15: Create a new Activity (Album Detail Activity)

Create a new activity with the name AlbumDetailActivity. Then, navigate to app > res > layout > activity_album_detail.xml and add the following code

activity_album_detail.xml:

XML
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white"
    tools:context=".AlbumDetailActivity">

    <!-- image view for displaying album image-->
    <ImageView
        android:id="@+id/idIVAlbum"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_centerHorizontal="true"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <!-- text view for displaying album name-->
    <TextView
        android:id="@+id/idTVAlbumName"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:text="Search"
        android:textColor="@color/black"
        android:textSize="20sp"
        android:textStyle="bold"
        android:maxLines="1"
        app:layout_constraintVertical_chainStyle="packed"
        app:layout_constraintBottom_toTopOf="@id/idTVArtistName"
        app:layout_constraintEnd_toStartOf="@+id/playButton"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/playButton" />

    <!-- text view for displaying album artist-->
    <TextView
        android:id="@+id/idTVArtistName"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:maxLines="1"
        android:text="Artist Name"
        android:textColor="@color/black"
        android:textSize="12sp"
        android:textStyle="bold"
        app:layout_constraintBottom_toBottomOf="@+id/playButton"
        app:layout_constraintEnd_toStartOf="@+id/playButton"
        app:layout_constraintStart_toStartOf="@+id/idTVAlbumName"
        app:layout_constraintTop_toBottomOf="@+id/idTVAlbumName" />

    <!-- button to play album-->
    <ImageView
        android:id="@+id/playButton"
        android:layout_width="82dp"
        android:layout_height="82dp"
        android:layout_alignParentEnd="true"
        android:layout_marginEnd="4dp"
        android:src="@drawable/play"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/idIVAlbum" />

    <!-- recycler view to display tracks-->
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rvAlbumDetails"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_margin="4dp"
        android:layout_marginTop="32dp"
        android:orientation="vertical"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        app:layout_constraintTop_toBottomOf="@+id/playButton"
        tools:layout_editor_absoluteX="4dp"
        tools:listitem="@layout/track_rv_item" />

</androidx.constraintlayout.widget.ConstraintLayout>


Design UI:

spotify-album-details


Step 16: Working with AlbumDetailActivity

Navigate to AlbumDetailActivity and add the following code

AlbumDetailActivity.java
package org.geeksforgeeks.demo;

public class AlbumModel {
    private String album_type;
    private String artistName;
    private String external_ids;
    private String external_urls;
    private String href;
    private String id;
    private String imageUrl;
    private String label;
    private String name;
    private int popularity;
    private String release_date;
    private int total_tracks;
    private String type;

    public AlbumModel(String album_type, String artistName, String external_ids, String external_urls,
                      String href, String id, String imageUrl, String label, String name,
                      int popularity, String release_date, int total_tracks, String type) {
        this.album_type = album_type;
        this.artistName = artistName;
        this.external_ids = external_ids;
        this.external_urls = external_urls;
        this.href = href;
        this.id = id;
        this.imageUrl = imageUrl;
        this.label = label;
        this.name = name;
        this.popularity = popularity;
        this.release_date = release_date;
        this.total_tracks = total_tracks;
        this.type = type;
    }

    // Add getters (and setters if needed)

    public String getAlbum_type() { return album_type; }
    public String getArtistName() { return artistName; }
    public String getExternal_ids() { return external_ids; }
    public String getExternal_urls() { return external_urls; }
    public String getHref() { return href; }
    public String getId() { return id; }
    public String getImageUrl() { return imageUrl; }
    public String getLabel() { return label; }
    public String getName() { return name; }
    public int getPopularity() { return popularity; }
    public String getRelease_date() { return release_date; }
    public int getTotal_tracks() { return total_tracks; }
    public String getType() { return type; }
}
AlbumDetailActivity.kt
package org.geeksforgeeks.demo

import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView
import com.android.volley.AuthFailureError
import com.android.volley.Response
import com.android.volley.toolbox.JsonObjectRequest
import com.android.volley.toolbox.Volley
import com.bumptech.glide.Glide
import org.json.JSONException

class AlbumDetailActivity : AppCompatActivity() {
    // creating variables on below line.
    private var albumID: String? = ""
    private var albumImgUrl: String? = null
    private var albumName: String? = null
    private var artist: String? = null
    private var albumUrl: String? = null

    private lateinit var albumNameTV: TextView
    private lateinit var artistTV: TextView
    private lateinit var albumIV: ImageView
    private lateinit var playButton: ImageView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)


        // initializing variables on below line.
        setContentView(R.layout.activity_album_detail)
        albumID = intent.getStringExtra("id")
        albumIV = findViewById(R.id.idIVAlbum)
        albumImgUrl = intent.getStringExtra("img")
        albumName = intent.getStringExtra("name")
        artist = intent.getStringExtra("artist")
        albumUrl = intent.getStringExtra("albumUrl")
        Log.e("TAG", "album id is : $albumID")
        albumNameTV = findViewById(R.id.idTVAlbumName)
        playButton = findViewById(R.id.playButton)
        artistTV = findViewById(R.id.idTVArtistName)
        // setting data on below line.
        albumNameTV.text = albumName
        artistTV.text = artist
        // adding click listener for fab on below line.
        playButton.setOnClickListener { // opening album from url on below line.
            val uri = Uri.parse(albumUrl) // missing 'http://' will cause crashed
            val intent = Intent(Intent.ACTION_VIEW, uri)
            startActivity(intent)
        }
        // loading image on below line.
        Glide.with(this).load(albumImgUrl).into(albumIV)
        // getting album tracks on below line.
        getAlbumTracks(albumID)
    }

    private val token: String?
        // method to get access token.
        get() {
            val sh = getSharedPreferences("MySharedPref", MODE_PRIVATE)
            return sh.getString("token", "Not Found")
        }

    // method to get tracks from albums.
    private fun getAlbumTracks(albumID: String?) {
        // on below line creating variable for url
        val url = "https://api.spotify.com/v1/albums/$albumID/tracks"
        // on below line creating list, initializing adapter and setting it to recycler view.
        val trackModels = ArrayList<TrackModel>()
        val trackAdapter = TrackAdapter(trackModels, this)
        val trackRV = findViewById<RecyclerView>(R.id.rvAlbumDetails)
        trackRV.adapter = trackAdapter
        val queue = Volley.newRequestQueue(this@AlbumDetailActivity)
        // on below line making json object request to parse json data.
        val trackObj: JsonObjectRequest = object : JsonObjectRequest(
            Method.GET, url, null,
            Response.Listener { response ->
                try {
                    val itemsArray = response.getJSONArray("items")
                    for (i in 0 until itemsArray.length()) {
                        val itemObj = itemsArray.getJSONObject(i)
                        val trackName = itemObj.getString("name")
                        val id = itemObj.getString("id")
                        val trackArtist =
                            itemObj.getJSONArray("artists").getJSONObject(0).getString("name")
                        // on below line adding data to array list.
                        trackModels.add(TrackModel(trackName, trackArtist, id))
                    }
                    trackAdapter.notifyDataSetChanged()
                } catch (e: JSONException) {
                    e.printStackTrace()
                }
            },
            Response.ErrorListener { error ->
                Toast.makeText(
                    this@AlbumDetailActivity,
                    "Fail to get Tracks$error", Toast.LENGTH_SHORT
                ).show()
            }) {
            @Throws(AuthFailureError::class)
            override fun getHeaders(): Map<String, String> {
                // on below line passing headers.
                val headers = HashMap<String, String>()
                headers["Authorization"] = token!!
                headers["Accept"] = "application/json"
                headers["Content-Type"] = "application/json"
                return headers
            }
        }
        // on below line adding
        // request to queue.
        queue.add(trackObj)
    }
}

Refer to the following github repo to get the entire code: Spotify-Clone-in-Android


Output:



Next Article

Similar Reads