tccpg288
tccpg288

Reputation: 3352

Cannot Maintain RecyclerView Position on Screen Rotation

I am loading a RecyclerView that is based on calls to Retrofit and data stored in a local SQLite database.

On Screen rotation, the RecyclerView keep resetting. In addition, when I favorite a movie (add the movie to my local room database), there are instances where my spinner doesn't align with the data to populate in the RecyclerView. Below is my code:

public class MainActivity extends AppCompatActivity {

private RecyclerView mMovieResults;
private MyAdapter mMovieResultsAdapter;
private String query;
private String mBaseURL;
private int lastFirstVisiblePosition;
//TODO: How is someone able to load my API Key if I am storing it


private ArrayList<String> mMovieURLS;
private ArrayList<Movie> mMovies;
private List<Movie> mMovieResultsList;

private static final String SPINNER_SELECTION = "SPINNER_SELECTION";

private boolean mIsFavoriteSelected;

private AppDatabase mDb;

private Spinner mSpinner;

private SharedPreferences mPrefs;
private SharedPreferences.Editor mPrefsEditor;

private static Bundle mBundleRecyclerViewState;


private int saved_selection = -1;


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.v("ONCREATE", "ONCREATE");
    if (savedInstanceState != null){
        saved_selection = savedInstanceState.getInt(SPINNER_SELECTION);
    }

    if (isOnline()) {
        setContentView(R.layout.activity_main);
        Stetho.initializeWithDefaults(this);
        mDb = AppDatabase.getInstance(getApplicationContext());
        mMovieResults = findViewById(R.id.main_recyclerview_image_results);
        GridLayoutManager glm = new GridLayoutManager(this, 3);
        glm.setOrientation(LinearLayoutManager.VERTICAL);
        mMovieResults.setLayoutManager(glm);
        mMovieURLS = new ArrayList<>();
        mMovies = new ArrayList<>();
        mMovieResultsList = new ArrayList<>();
        mMovieResultsAdapter = new MyAdapter(mMovieResultsList);
        mMovieResults.setAdapter(mMovieResultsAdapter);
        mDb = AppDatabase.getInstance(getApplicationContext());
        mPrefs = getSharedPreferences("prefs", MODE_PRIVATE);

        mPrefsEditor = mPrefs.edit();
        switch (saved_selection){
                case 0:
                    mBaseURL = "https://api.themoviedb.org/3/movie/popular/";
                    calltoRetrofit(mBaseURL);
                    break;
                case 1:
                    mBaseURL = "https://api.themoviedb.org/3/movie/top_rated/";
                    calltoRetrofit(mBaseURL);
                    break;
                case 2:
                    mIsFavoriteSelected = true;
                    mMovieURLS.clear();
                    retrieveMovies();
                    break;

                default:
                    mBaseURL = "https://api.themoviedb.org/3/movie/popular/";
                    break;
            }


    } else {
        setContentView(R.layout.activity_no_internet);
        return;
    }

}





@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.action_bar_spinner, menu);
    MenuItem item = menu.findItem(R.id.spinner);
    mSpinner = (Spinner) item.getActionView();
    if (isOnline()) {
        ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(getApplicationContext(), R.array.spiner_list_item_array, R.layout.custom_spinner);
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        mSpinner.setAdapter(adapter);
        if (saved_selection >= 0){
            mSpinner.setSelection(saved_selection);
        }
        mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
                mPrefsEditor.putInt(SPINNER_SELECTION, i);
                mPrefsEditor.commit();
                switch (i) {
                    case 0:
                        mBaseURL = "https://api.themoviedb.org/3/movie/popular/";
                        calltoRetrofit(mBaseURL);
                        break;
                    case 1:
                        mBaseURL = "https://api.themoviedb.org/3/movie/top_rated/";
                        calltoRetrofit(mBaseURL);
                        break;
                    case 2:
                        mIsFavoriteSelected = true;
                        mMovieURLS.clear();
                        retrieveMovies();
                        break;

                    default:
                        mBaseURL = "https://api.themoviedb.org/3/movie/popular/";
                        break;
                }


            }

            @Override
            public void onNothingSelected(AdapterView<?> adapterView) {

            }
        });
        return true;
    } else {
        return true;
    }
}

@Override
protected void onSaveInstanceState(Bundle outState) {
    outState.putInt(SPINNER_SELECTION, mSpinner.getSelectedItemPosition());
    super.onSaveInstanceState(outState);
}



@Override
public boolean onOptionsItemSelected(MenuItem item) {
    super.onOptionsItemSelected(item);
    int x = item.getItemId();
    Log.v("OPTIONS_ID", String.valueOf(x));
    return true;
}

private void retrieveMovies() {
    //TODO: I am unsure if I am handling correctly. When I favorite and unfavorite a movie, and then go back to the MainActivity, it is not displaying the correct item from the Spinner.
    //TODO: For example, favorites may be listed in the Spinner, however the screen may be displaying "TOP"
    //TODO: How could I tell if I am making unnecessary queries to Room? I know it is a project requirement, but I was not sure how to handle if I am making unnecessary calls
    //TODO: on screen rotation
    MainViewModel viewModel = ViewModelProviders.of(this).get(MainViewModel.class);
    final LiveData<List<Movie>> movies = viewModel.getMovies();
            movies.observe(this, new Observer<List<Movie>>() {
        @Override
        public void onChanged(@Nullable final List<Movie> movies) {
            for (Movie movie : movies) {
                String photoURL = "http://image.tmdb.org/t/p/w185" + movie.getPoster_path();
                Log.v("FAVORITE_URL", photoURL);
                mMovieURLS.add(photoURL);
            }
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    mMovieResultsList = movies;
                    mMovieResultsAdapter.notifyDataSetChanged();
                }
            });

        }
    });
}





@Override
protected void onResume() {
    super.onResume();
    ((GridLayoutManager) mMovieResults.getLayoutManager()).scrollToPositionWithOffset(lastFirstVisiblePosition,0);
}

private void calltoRetrofit(String mBaseURL) {
    mMovieURLS.clear();
    HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
    interceptor.setLevel(HttpLoggingInterceptor.Level.BASIC);
    OkHttpClient client = new OkHttpClient
            .Builder()
            .addInterceptor(interceptor)
            .build();

    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(mBaseURL)
            .client(client)
            .addConverterFactory(GsonConverterFactory.create())
            .build();

    final ApiInterface apiInterface = retrofit.create(ApiInterface.class);

    Log.v("API", API_KEY);
    Call<Movies> call = apiInterface.getImages(API_KEY);
    call.enqueue(new Callback<Movies>() {
        @Override
        public void onResponse(Call<Movies> call, Response<Movies> response) {
            Log.v("RESPONSE", response.body().toString());
            String totalPages = String.valueOf(response.body().getTotal_pages());
            Log.v("TOTAL", totalPages);
            mMovieResultsList = response.body().getResults();
            for (Movie movie : mMovieResultsList) {
                if (movie.getPoster_path() != null) {
                    String photoURL = "http://image.tmdb.org/t/p/w185" + movie.getPoster_path();
                    mMovieURLS.add(photoURL);
                }
                mMovieResultsAdapter.notifyDataSetChanged();
            }
        }

        @Override
        public void onFailure(Call<Movies> call, Throwable t) {
            Log.v("FAILURE", t.toString());
        }
    });
}



@Override
public void onPointerCaptureChanged(boolean hasCapture) {

}

@Override
protected void onPause() {
    super.onPause();
    lastFirstVisiblePosition = ((GridLayoutManager)mMovieResults.getLayoutManager()).findFirstCompletelyVisibleItemPosition();

}

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


    public class ViewHolder extends RecyclerView.ViewHolder {

        protected ImageView mResultImage;

        public ViewHolder(View itemView) {
            super(itemView);
            mResultImage = itemView.findViewById(R.id.movie_poster_image);
        }
    }


    public MyAdapter(List<Movie> mDataset) {
        mMovieResultsList = mDataset;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.movie_poster_image, parent, false);
        return new ViewHolder(v);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, final int position) {
        final String urlForPhoto = "http://image.tmdb.org/t/p/w185" + mMovieResultsList.get(position).getPoster_path();

        Picasso.get()
                .load(urlForPhoto)
                .into(holder.mResultImage);

        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent i = new Intent(getApplicationContext(), IndividualMovieActivity.class);
                i.putExtra("Movie", mMovieResultsList.get(position));
                startActivity(i);
            }
        });

    }

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

}

public interface ApiInterface {
    @GET("?language=en-US")
    Call<Movies> getImages(@Query("api_key") String api_key);
}

private boolean isOnline() {
    ConnectivityManager cm =
            (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo netInfo = cm.getActiveNetworkInfo();
    return netInfo != null && netInfo.isConnectedOrConnecting();
}

Upvotes: 2

Views: 1508

Answers (2)

masoud vali
masoud vali

Reputation: 1536

When you rotate your device, android detects the change in configuration and restarts the activity by default. in this way your activity calls the adapter again and your recyclerview returns to zero position. by adding the command below, you mean that you handle this configuration changes yourself, so the android won't restart your activity.

add this line to activity tag in manifest file

android:configChanges="screenSize|orientation"

like this:

    <activity
        android:name=".SearchActivity"
        android:configChanges="screenSize|orientation"
        android:label="@string/search"
        android:theme="@style/AppTheme.NoActionBar">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.DEFAULT" />
        </intent-filter>
    </activity>

Upvotes: 4

ianhanniballake
ianhanniballake

Reputation: 199815

RecyclerView only saves its scroll position up until you call setAdapter. By calling setAdapter while you still have no data, the scroll position is discarded (it can't scroll to position 4 if there's no position 4).

You should wait until you have data to call setAdapter.

Upvotes: 9

Related Questions