AsyncTaskLoader keeps reloading data when I come back to the MainActivity

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP



AsyncTaskLoader keeps reloading data when I come back to the MainActivity



I am quite new to the Android Development and I really need your help. My problem is in the MainActivity below. The app essentially displays a list of movies in the main activity and the movie details in another activity. And the problem is that whenever a user comes back from the MovieActivity to the MainActivity, the loader starts loading data again, although the movies are already there. And then it can not stop loading the data. It is really annoying. I want to get rid of this. So when a user comes back to the MainActivity, the loader will know that there is already loaded data and will not load anything again.If it helps, here is my full GitHub repo https://github.com/mateuszwojnarowicz/PopularMovies


MainActivity


MovieActivity


MainActivity



I am stuck for about 3 weeks and have tried hundreds of possible solutions. Nothing seems to work. I feel really desperate.



Thank you so much for help,



Matthew


public class MainActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<String>

private MovieAdapter mAdapter;
private ArrayList<Movie> mMoviesCollection;
private SharedPreferences sharedPreferences;
private Resources resources;
private LoaderManager loaderManager;
private Loader<String> loader;
private RecyclerView.LayoutManager layoutManager;
private String sortBy;

@BindView(R.id.pb)
ProgressBar progressBar;
@BindView(R.id.er)
TextView errorTextView;
@BindView(R.id.rv)
RecyclerView recyclerView;



@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
mMoviesCollection = new ArrayList<Movie>();
sharedPreferences = getSharedPreferences(Constants.SHARED_PREFS, Activity.MODE_PRIVATE);
resources = getResources();
sortBy = sharedPreferences.getString(Constants.KEY_SORT, null);
setSharedPref();

layoutManager = new GridLayoutManager(this, calculateNoOfColumns(this));
loaderManager = getLoaderManager();
loader = loaderManager.getLoader(Constants.LOADER_MOVIES_ID);
initialize();
makeOperationLoadMovies(sortBy);



public static int calculateNoOfColumns(Context context)
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
float dpWidth = displayMetrics.widthPixels / displayMetrics.density;
int noOfColumns = (int) (dpWidth / 150);
return noOfColumns;



//Set first-launch pref and set title according to pref
private void setSharedPref()
if(!sharedPreferences.contains(Constants.KEY_SORT))
saveData(Constants.VALUE_POP);
setTitle(resources.getString(R.string.title_pop));
else
if (Objects.equals(sharedPreferences.getString(Constants.KEY_SORT, null), Constants.VALUE_POP))
setTitle(resources.getString(R.string.title_pop));

if (Objects.equals(sharedPreferences.getString(Constants.KEY_SORT, null), Constants.VALUE_TOP))
setTitle(resources.getString(R.string.title_top));




//Set up the RecyclerView
private void initialize()
recyclerView.setLayoutManager(layoutManager);
recyclerView.setHasFixedSize(true);
mMoviesCollection = new ArrayList<>();
mAdapter = new MovieAdapter(mMoviesCollection, this, this);
recyclerView.setAdapter(mAdapter);


private void makeOperationLoadMovies(String SORT_BY)
Bundle bundle = new Bundle();
bundle.putString(Constants.LOADER_MOVIES_EXTRA, SORT_BY);
if(recyclerView.isDirty())


else if(loader==null)
loaderManager.initLoader(Constants.LOADER_MOVIES_ID, bundle, this);
else
loaderManager.restartLoader(Constants.LOADER_MOVIES_ID, bundle, this);




//Update shared pref
private void saveData(String SORT_VALUE)
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(Constants.KEY_SORT, SORT_VALUE);
editor.apply();


@Override
public boolean onCreateOptionsMenu(Menu menu)
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main, menu);
return true;


@Override
public boolean onOptionsItemSelected(MenuItem item)
int id = item.getItemId();

switch (id)
case R.id.menu_fav:
startActivity(new Intent(MainActivity.this, FavoritesActivity.class));
break;
case R.id.menu_pop:
saveData(Constants.VALUE_POP);
Toast.makeText(this, resources.getString(R.string.message_popularity),Toast.LENGTH_LONG).show();
break;
case R.id.menu_top:
saveData(Constants.VALUE_TOP);
Toast.makeText(this, resources.getString(R.string.message_rating),Toast.LENGTH_LONG).show();
break;


return super.onOptionsItemSelected(item);


@Override
protected void onPause()
super.onPause();
Parcelable recyclerViewState;
recyclerViewState = recyclerView.getLayoutManager().onSaveInstanceState();//save
recyclerView.getLayoutManager().onRestoreInstanceState(recyclerViewState);


@Override
protected void onPostResume()
super.onPostResume();
Parcelable recyclerViewState;
recyclerViewState = recyclerView.getLayoutManager().onSaveInstanceState();//save
recyclerView.getLayoutManager().onRestoreInstanceState(recyclerViewState);



@SuppressLint("StaticFieldLeak")
@Override
public Loader<String> onCreateLoader(int id, final Bundle args)
return new AsyncTaskLoader<String>(this)


@Override
protected void onStartLoading()
super.onStartLoading();
forceLoad();
progressBar.setVisibility(View.VISIBLE);
errorTextView.setVisibility(View.INVISIBLE);



@Override
public void deliverResult(String data)
super.deliverResult(data);


@Override
public String loadInBackground()



String jsonString = "";
URL url = NetworkUtils.buildUrl(args.getString(Constants.LOADER_MOVIES_EXTRA));
try
jsonString += NetworkUtils.getResponseFromHttpUrl(url);
catch (IOException e)
e.printStackTrace();



if(jsonString.isEmpty())


else

try
JSONObject jsonObject = new JSONObject(jsonString);
JSONArray jsonArray = jsonObject.getJSONArray(Constants.JSON_KEY_MOVIE_RESULTS);
for (int i = 0; i < jsonArray.length(); i++)
//Get 1 movie from JSON
String mTitle;
int mId;
String mPosterUrl;
String mPlot;
double mUserRating;
String mReleaseDate;

JSONObject Jmovie = (JSONObject) jsonArray.get(i);
mTitle = Jmovie.getString(Constants.JSON_KEY_MOVIE_TITLE);
mId = Jmovie.getInt(Constants.JSON_KEY_MOVIE_ID);
mPosterUrl = NetworkUtils.getPosterString(Jmovie.getString(Constants.JSON_KEY_MOVIE_POSTER_PATH));
mPlot = Jmovie.getString(Constants.JSON_KEY_MOVIE_OVERVIEW);
mUserRating = Jmovie.getDouble(Constants.JSON_KEY_MOVIE_VOTE_AVERAGE);
mReleaseDate = Jmovie.getString(Constants.JSON_KEY_MOVIE_RELEASE_DATE);
//Get videos
ArrayList<Video> mVideos = new ArrayList<Video>();
URL videosURL = NetworkUtils.buildUrlVideos(String.valueOf(mId));
String videosJSON = NetworkUtils.getResponseFromHttpUrl(videosURL);
JSONObject jsonObjectVideos = new JSONObject(videosJSON);
JSONArray jsonArrayVideos = jsonObjectVideos.getJSONArray(Constants.JSON_KEY_VIDEO_RESULTS);
if(jsonArrayVideos.length()==0)
mVideos = null;
else
for(int v = 0; v < jsonArrayVideos.length(); v++)
JSONObject Jvideo = (JSONObject) jsonArrayVideos.get(v);
String mVideoName;
String mVideoUrlString;
mVideoName = Jvideo.getString(Constants.JSON_KEY_VIDEO_NAME);
mVideoUrlString = "https://www.youtube.com/watch?v="+Jvideo.getString(Constants.JSON_KEY_VIDEO_KEY);
Video video = new Video(mVideoName, mVideoUrlString);
mVideos.add(video);


//GetReviews
ArrayList<Review> mReviews = new ArrayList<Review>();
URL reviewsURL = NetworkUtils.buildUrlReviews(String.valueOf(mId));
String reviewsJSON = NetworkUtils.getResponseFromHttpUrl(reviewsURL);
JSONObject jsonObjectReviews = new JSONObject(reviewsJSON);
JSONArray jsonArrayReviews = jsonObjectReviews.getJSONArray(Constants.JSON_KEY_REVIEW_RESULTS);
if(jsonArrayReviews.length()!=0)
for(int r = 0; r < jsonArrayReviews.length(); r++)
JSONObject Jreview = (JSONObject) jsonArrayReviews.get(r);
String mReviewName;
String mReviewText;
mReviewName = Jreview.getString(Constants.JSON_KEY_REVIEW_AUTHOR);
mReviewText = Jreview.getString(Constants.JSON_KEY_REVIEW_CONTENT);
Review review = new Review(mReviewName, mReviewText);
mReviews.add(review);


Movie movie = new Movie(mTitle, mId, mPosterUrl, mPlot, mUserRating, mReleaseDate, mVideos, mReviews);
mMoviesCollection.add(movie);

catch (JSONException e)
e.printStackTrace();
catch (IOException e)
e.printStackTrace();


return null;



;


@Override
public void onLoadFinished(Loader<String> loader, String data)

progressBar.setVisibility(View.GONE);

mAdapter.notifyDataSetChanged();


@Override
public void onLoaderReset(Loader<String> loader)







Which code do you use to call MovieActivity? I can't find that there. Also make sure you don't call finish() on MainActivity when you know you would back and to avoid data reloading
– olajide
Jul 31 at 9:45



MovieActivity


finish()


MainActivity





Please take a look into the Github repo. It is under "Adapter" directory in MovieAdapter.java class.
– Matthew
Jul 31 at 10:32





more precisely onBindViewHolder method
– Matthew
Jul 31 at 10:33




1 Answer
1



Because you are new to Android there is a lot wrong. So, many people probably won't want to chime in. Regardless, I'm new as well and in the same class as you are right now, so I'll give it a shot.



First, your loader is not returning the correct data type. Your loader should be of Loader<List<Movie>> and it should return a new AsyncTaskLoader<List<Movie>>. The reason you want this is to make use of everything the AsyncTaskLoader has to offer. I'll explain further.


Loader<List<Movie>>


new AsyncTaskLoader<List<Movie>>



Second, we'll cache the data inside the loader by moving the initial reference from the Activity into the loader.



So move private ArrayList<Movie> mMoviesCollection; as an instance variable of your AsyncTaskLoader. Remove the line mMoviesCollection = new ArrayList<Movie>(); from both your onCreate and initialize methods.


private ArrayList<Movie> mMoviesCollection;


mMoviesCollection = new ArrayList<Movie>();



In your AsyncTaskLoader, you need to check if your data exists already in your onStartLoading before forceLoad and implement deliverResult.



So, your onStartLoading() should look like this:


@Override
protected void onStartLoading()
super.onStartLoading();
if(mMoviesCollection.isEmpty())
forceLoad();
progressBar.setVisibility(View.VISIBLE);
errorTextView.setVisibility(View.INVISIBLE);
else
deliverResult(mMoviesCollection)




And your deliverResult should look like this:


@Override
public void deliverResult(List<Movie> data)
mMoviesCollection = data;
super.deliverResult(data);



Now you need to implement a setData(List<Movie> movies) method that sets your adapter's data instance variable and calls notifyDataSetChanged() in your Adapter. Like so:


setData(List<Movie> movies)


notifyDataSetChanged()


public void setData(List<Movie> movies)
mMovies = movies;
notifyDataSetChanged();



Get rid of the List<Movie> from your adapter's constructor. This way you can construct the adapter without any data. The adapter's getItemCount() should return 0 if the data is null and the recyclerView will not try to build the view.


List<Movie>


getItemCount()



With that done you can then call onLoadFinished like this:


@Override
public void onLoadFinished(Loader<List<Movie>> loader, List<Movie> data)
progressBar.setVisibility(View.GONE);
mAdapter.setData(data);



EDIT: Made a correction to account for the ArrayList instantiating as an Instance variable. You can either not instantiate the mMoviesCollection there and then do so later or just check if its empty with mMoviesCollection.isEmpty() as I changed above in onStartLoading.:



EDIT:
You need to get your libraries straight, you are using android.app in some places and android.support in others.



So in your imports change these:


import android.app.LoaderManager;
import android.content.AsyncTaskLoader;
import android.content.Loader;



all to:


import android.support.v4.app.LoaderManager;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader;



Now the TMDB.org API has a request limit of 40 requests per 10 seconds.
https://developers.themoviedb.org/3/getting-started/request-rate-limiting



Because of this, your Loader is not even completing everything and is throwing an exception. I would suggest breaking up when you call the videos and reviews into the MovieActivity by creating another AsyncTaskLoader there and calling each when the details screen loads.



You could also technically add a Thread.sleep(300) or less to your AsyncTaskLoader but it makes it seriously slow. In other words, you would have to push the data beyond the 10-second mark to load completely.



Now, with that and the changes we have made, everything does survive config changes such as screen rotation.



If you want the data to survive any further you will have to persist the data somehow. Like saving the json response as a string in onSaveInstanceState or saving the JSon String to the database you created.





Please, could you explain me the setData method more in detail?
– Matthew
Aug 6 at 11:08





Sure in your adapter, just create a method public void setData(List<Movie> movies) mMovies = movies; notifyDataSetChanged(); Ill update the answer to reflect this.
– Shawn Maybush
Aug 6 at 11:27





Also check your onStartLoading() so that it matches my edit. With the ArrayList instantiating in the AsyncTaskLoader as is, it will never be null. So we just check if its empty with isEmpty().
– Shawn Maybush
Aug 6 at 11:48





I've updated everything according to your guidance, however, the problem with reloading persists. github.com/mateuszwojnarowicz/PopularMovies How can I make sure that the mMoviesCollection will survive when a user goes to the details screen and comes back? Thanks
– Matthew
Aug 6 at 11:49






I caught this right after the last comment. Change the onStartLoading to what I have edited in the response. Because we instantiate the ArrayList as an instance variable instead of later, we need to check whether there is data in the list. So instead of mMoviesCollection == null, we check if mMoviesCollection.isEmpty().
– Shawn Maybush
Aug 6 at 11:53







By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

Firebase Auth - with Email and Password - Check user already registered

Dynamically update html content plain JS

Creating a leaderboard in HTML/JS