Search code examples
androidandroid-cardviewlag

How to cover up the loading time lag in android Studio?


I have made an app in Android Studio which imports data from a website...when I open the app the card views in which the data is to be imported are empty and get loaded after some time. I wanted to know if there is any way in which I can cover it up so that till the time it is being loaded my splash screen stays there. Here is a video showing the lag:

Card View loading time lag

Edit: Here is the movie Adapter:

class MoviesAdapter(
    private var movies: MutableList<Movie>,
    private val onMovieClick: (movie: Movie) -> Unit,
) : RecyclerView.Adapter<MoviesAdapter.MovieViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MovieViewHolder {
        val view = LayoutInflater
            .from(parent.context)
            .inflate(R.layout.item_movie, parent, false)
        return MovieViewHolder(view)
    }

    override fun getItemCount(): Int = movies.size

    override fun onBindViewHolder(holder: MovieViewHolder, position: Int) {
        holder.bind(movies[position])
    }

    fun appendMovies(movies: List<Movie>) {
        this.movies.addAll(movies)
        notifyItemRangeInserted(
            this.movies.size,
            movies.size - 1
        )
    }

    inner class MovieViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

        private val poster: ImageView = itemView.findViewById(R.id.item_movie_poster)

        fun bind(movie: Movie) {
            itemView.setOnClickListener { onMovieClick.invoke(movie) }
            Glide.with(itemView)
                .load("https://image.tmdb.org/t/p/w342${movie.posterPath}")
                .transform(CenterCrop())
                .into(poster)
        }
    }
}

My main_activity_xml:

<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView 
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg_gradient"
android:fitsSystemWindows="true">

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">


    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="10dp"
        android:layout_marginTop="25dp"
        android:layout_marginEnd="16dp"
        android:text="@string/discover"
        android:textColor="@android:color/white"
        android:textSize="45sp"
        android:textStyle="bold" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:text="@string/popular"
        android:textColor="@android:color/white"
        android:textSize="22sp"
        android:textStyle="bold" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="16dp"
        android:text="@string/most_popular_movies" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/popular_movies"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:clipToPadding="false"
        android:paddingStart="16dp"
        android:paddingEnd="16dp" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:text="@string/top_rated"
        android:textColor="@android:color/white"
        android:textSize="22sp"
        android:textStyle="bold" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="16dp"
        android:text="@string/highest_rated_movies" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/top_rated_movies"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:clipToPadding="false"
        android:paddingStart="16dp"
        android:paddingEnd="16dp" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:text="@string/upcoming"
        android:textColor="@android:color/white"
        android:textSize="22sp"
        android:textStyle="bold" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="16dp"
        android:text="@string/stay_updated" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/upcoming_movies"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:layout_marginBottom="16dp"
        android:clipToPadding="false"
        android:paddingStart="16dp"
        android:paddingEnd="16dp" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:text="@string/now_playing"
        android:textColor="@android:color/white"
        android:textSize="22sp"
        android:textStyle="bold" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="16dp"
        android:text="@string/now_in_Cinemas" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/now_playing_movies"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:layout_marginBottom="10dp"
        android:clipToPadding="false"
        android:paddingStart="16dp"
        android:paddingEnd="16dp" />

</LinearLayout>

</androidx.core.widget.NestedScrollView>

Api Key:

interface Api {

    @GET("movie/popular")
    fun getPopularMovies(
        @Query("api_key") apiKey: String = 
 "eff7d87eb14cdf1fb273414692d3e3a8",
        @Query("page") page: Int,
    ): Call<GetMoviesResponse>
}

Api Loader:

api.getPopularMovies(page = page)
            .enqueue(object : Callback<GetMoviesResponse> {
                override fun onResponse(
                    call: Call<GetMoviesResponse>,
                    response: Response<GetMoviesResponse>,
                ) {
                    if (response.isSuccessful) {
                        val responseBody = response.body()

                        if (responseBody != null) {
                            onSuccess.invoke(responseBody.movies)
                        } else {
                            onError.invoke()
                        }
                    } else {
                        onError.invoke()
                    }
                }

                override fun onFailure(call: Call<GetMoviesResponse>, t: 
    Throwable) {
                    onError.invoke()
                }
            })

Is there anything else that I should specify?


Solution

  • without code it's hard to answer the question, but you could always use a ViewSwitcher that displays a "Loading" state before the data for the cardviews are ready, but holds the space, so the UI won't jump, and then have the Card view displayed once the data is loaded.

    Could be something like:

    sealed class CardData {
    object Loading: CardData()
    class Info(val items: List<CardInfo>): CardData()
    }
    
    fun loadData() {
        viewModel.cardData.observe { cardData ->
            when(cardData) {
              is Loading -> {
                binding.switcher.displayedChild = 0
               }
              is Info -> {
                binding.switcher.displayedChild = 1
                binding.listOfCards.items = cardData.items
              }
        }
    }
    

    for your specific case:

    // display the loading view before making the call
    binding.switcher.displayedChild = 0
    api.getPopularMovies(page = page)
                .enqueue(object : Callback<GetMoviesResponse> {
                    override fun onResponse(
                        call: Call<GetMoviesResponse>,
                        response: Response<GetMoviesResponse>,
                    ) {
                        if (response.isSuccessful) {
                            val responseBody = response.body()
    
                            if (responseBody != null) {
                                onSuccess.invoke(responseBody.movies)
                            } else {
                                onError.invoke()
                            }
                        } else {
                            onError.invoke()
                        }
                    }
    
                    override fun onFailure(call: Call<GetMoviesResponse>, t: 
        Throwable) {
                        onError.invoke()
                    }
                })
    
    // in your onSuccess()
    private void onSuccess(List<Movies> movies) {
     binding.switcher.displayedChild = 1
     listAdapter.items = movies
     listAdapter.notifyDatasetChanged()
    }
    
    // in your onError()
    private void onError() {
     binding.switcher.displayedChild = 2 // you can add a third child for errors
    }
    

    then the XML would be something like (missing attributes):

    <ViewSwitcher
    android:id="@+id/switcher">
    <!-- child 0 - loading state -->
    <ProgressBar
    android:id="@+id/loader"
    />
    <!-- child 1 - movies state -->
    <RecyclerView 
    android:id="@+id/movies"
    />
    <!-- child 2 - error state -->
    <TextView 
    android:id="@id/error_text"
    />
    </ViewSwitcher>