1

I have implemented my fragment to the Android's best practice of using newInstance with parameters. It works, but with a hatch.

The issue the newInstance with parameters fixes is that Android will call the default no-args constructor on screen rotation. That means you will lose data on rotation. The newInstance will allow you to setArguments that will be retained on rotation.

Using the overloaded constructor will mean your data will show correctly the first time until you rotate your device. The newInstance will allow your fragment to work correctly the first time and on rotations.

Issues

However, it seems like I am having the opposite effect. My code works correctly on rotations, but does not work correctly on the first try. The same effect occurs if I use the constructor with arguments.

On app load, it will display my tabs (3) in a Tablayout but none of my RecyclerViews will have items in them.

Here is how I can load data into the RecyclerViews the fastest. All on app load, and all are from fresh starts:

  1. Rotate the device. All 3 tabs will load their data. Current tab right now, and other tabs on selection (sliding to them).
  2. From 1st Tab, click on Tab 3, then go back to Tab 1. Tab 1 data will load. Tab 2 won't load. Tab 3 will load on selection. To get Tab 2 data to load, I need to rotate the device

Code

Fragment

public class MovieListFragment extends Fragment implements OnItemClickListener {

    MovieListAdapter adapter;
    WebAPI webAPI;     // Advanced Enum

    private static final String API_STRING = "api";

    public MovieListFragment() {}

    public static MovieListFragment newInstance(WebAPI API) {

        MovieListFragment fragment = new MovieListFragment();

        Bundle args = new Bundle();
        args.putSerializable(API_STRING, API);
        fragment.setArguments(args);

//        if (API != null) fragment.webAPI = API;

        return fragment;

    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {

        View rootView = inflater.inflate(R.layout.fragment_movies_list, container, false);

        webAPI = (WebAPI) getArguments().getSerializable(API_STRING);

        GetMoviesAsync getMovies = new GetMoviesAsync();
        getMovies.execute();

        adapter = new MovieListAdapter(getActivity(), webAPI, this);

        RecyclerView movieListView;

        movieListView = (RecyclerView) rootView.findViewById(R.id.movie_list);
        movieListView.setAdapter(adapter);
        movieListView.setLayoutManager(
                new GridLayoutManager(  // Changes number of columns based on form-factor and orientation
                        getActivity(),
                        getResources().getInteger(R.integer.columns)));


        return rootView;

    }

    @Override
    public void onClick(View v, int position) {

        startActivity(new Intent(this.getActivity(), SecondaryActivity.class)); // TODO pass data to second activity

    }

    // ASYNC_TASK
    public class GetMoviesAsync extends AsyncTask<Void, Void, Void> {

        // CALLBACK
        private final Handler.Callback getMoviesFromJSON = new Handler.Callback() {

            @Override
            public boolean handleMessage(Message msg) {

                        // Take the JSON (msg) and convert it to a Java object (Movie)
                        Movie movie = new Movie(title, uri);
                        webAPI.addMovie(movie);

                    }

                } catch (JSONException e) { return false; }

                return true;

            }

        };

        @Override
        protected Void doInBackground(Void... params) {

            try {

                // This will get the JSON from the webservice, and will use the CallBack to assign the List of movies.
                new WebService(webAPI.getUri()).acquireDataWithCallback(getMoviesFromJSON);

            } catch (JSONException e) { e.printStackTrace(); }

            return null;
        }

    }

}

BaseActivity

public abstract class BaseSearchTabbedActivity extends AppCompatActivity
        implements SearchView.OnQueryTextListener {

    EnumSet<WebAPI> tabs;
    ...

    public void setTabs(EnumSet<WebAPI> tabs) { this.tabs = tabs; }

    void initializeToolbar() {   // Called in Activity's OnCreate

        // Setup the toolbar
        toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        // Initialize all components. Tabs, Pager, and Adapter
        tabLayout = (TabLayout) findViewById(R.id.tabs);
        viewPager = (ViewPager) findViewById(R.id.pager);
        adapter = new ViewPagerFragmentAdapter(getSupportFragmentManager());

        // Add the tabs
        collectTabValues(); // Set tabs from Activity
        addFragments(tabs);

        // Bind the adapter to the tabs
        viewPager.setAdapter(adapter);
        tabLayout.setupWithViewPager(viewPager);

    }

    private void addFragments(EnumSet<WebAPI> tabs) {

        for (WebAPI tab : tabs) {

            adapter.addFragment(MovieListFragment.newInstance(tab), tab);

        }

    }
Community
  • 1
  • 1
Christopher Rucinski
  • 4,737
  • 2
  • 27
  • 58
  • Extra info: I did get this to work with just 1 web service call on app load before. However, every tab requires a different (but very very similar) `uri` call to get `JSON` data. I restructured my `Fragment` to encaspulate the `uri`. This way I can pass in the `uri` from the `BaseActivity` – Christopher Rucinski Jul 04 '15 at 13:33
  • Also `WebAPI` is an **advanced enum**. In `newInstance(WebAPI)` I just use the default `Serializable` implementation for `enum`. Is that a cause for issue? – Christopher Rucinski Jul 04 '15 at 13:38

1 Answers1

1

Whenever the async task completes its work, you would need to update the adapter and notify its items there were changes.

Updating adapter views needs to be in the main thread, so please add the following method in your task:

@Override
protected void onPostExecute(Void nothing) {
    adapter.notifyDataSetChanged();
}
Dimitar Genov
  • 2,056
  • 13
  • 11
  • My rotation works as expected. What does not work is my first load with no interaction. Nothing displays; however on screen rotation, everything works perfect – Christopher Rucinski Jul 04 '15 at 13:35
  • You are executing an `AsyncTask` but never update your `RecyclerView` adapter with newly download movies when the task is done. In your task override [AsyncTask.onPostExecute()](http://developer.android.com/reference/android/os/AsyncTask.html#onPostExecute(Result)) and add `adapter.notifyDatasetChanged();` – Dimitar Genov Jul 04 '15 at 13:41
  • I added a `notifyDataSetChanged` after `webAPI.addMovie(movie);` in my Fragment's Async Callback. However, that causes an error `android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.` So where do I call it from??? – Christopher Rucinski Jul 04 '15 at 14:08
  • You are in a separate thread. You can call and update views from your main thread only. That's why I mentioned to override `onPostExecute()` since it is called in the main thread. – Dimitar Genov Jul 04 '15 at 14:12