Search code examples
androidandroid-fragmentsandroid-activityandroid-fragmentactivityandroid-lifecycle

Why does pressing back from detail activity after landscape-to-portrait-switch show an empty screen?


Below is the MainActivity class that I'm using. The code checks to see if the phone is in landscape or portrait. If it's in portrait, it will show the main fragment in the main activity only (the main fragment is a static fragment in the main_activity.xml file). Then if a "Recipe" is clicked it will open a detail activity with its own fragment. If the phone is in landscape mode, it will show the main fragment and the detail fragment side by side. Everything works perfectly fine however when I follow the procedure below I get a white screen instead of the main activity:

Procedure:

  1. Switch to landscape
  2. Switch back to portrait
  3. Choose an item and wait for the detail activity to open
  4. Press back
  5. Here instead of the main activity window I get a white screen

If I don't switch to landscape and just start with the portrait mode everything is fine. It seems like switching to landscape does something that causes the problem and I can't figure out what. Any tip on what's going on or where to look would be much appreciated.

public class MainActivity extends AppCompatActivity implements RecipesFragment.OnRecipeClickListener {

    private String RECIPE_PARCEL_KEY;
    private boolean mTwoPane;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        RECIPE_PARCEL_KEY = getString(R.string.ParcelKey_RecipeParcel);
        if (findViewById(R.id.linearLayoutTwoPane) != null) {
            mTwoPane = true;
            if (savedInstanceState == null) {
                RecipeFragment recipeFragment = new RecipeFragment();
                FragmentManager fragmentManager = getSupportFragmentManager();
                fragmentManager.beginTransaction()
                        .add(R.id.recipeFrameForTwoPane, recipeFragment)
                        .commit();
            }
        } else {
            mTwoPane = false;
        }
    }

    @Override
    public void OnRecipeClick(Recipe recipe) {
        if (mTwoPane) {
            RecipeFragment recipeFragment = new RecipeFragment();
            recipeFragment.setRecipe(recipe);
            FragmentManager fragmentManager = getSupportFragmentManager();
            fragmentManager.beginTransaction()
                    .replace(R.id.recipeFrameForTwoPane, recipeFragment)
                    .commit();
        } else {
            Class destinationClass = DetailActivity.class;
            Intent intentToStartDetailActivity = new Intent(this, destinationClass);
            intentToStartDetailActivity.putExtra(RECIPE_PARCEL_KEY, recipe);
            startActivity(intentToStartDetailActivity);
        }
    }
}

EDIT: Adding RecipeFragment's code below:

public class RecipeFragment extends Fragment {

    private Recipe mRecipe;
    @BindView(R.id.tv_recipeName) TextView recipeNameTextView;

    public RecipeFragment(){
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.recipe_fragment,container,false);
        ButterKnife.bind(this,view);
        if(mRecipe!=null) {
            recipeNameTextView.setText(mRecipe.getName());
        }else{
            recipeNameTextView.setText(getString(R.string.messageSelectARecipe));
        }
        return view;
    }

    public void setRecipe(Recipe recipe){
        mRecipe = recipe;
    }
}

EDIT: I followed @mt0s's advice and created different background colors for the fragments and activities and finally narrowed down the problem to a line in my recyclerview adapter code. My adapter code is below. Inside loadInBackground() on line URL url = new URL(getString(R.string.URL_RecipeJSON)); I get a Fragment RecipesFragment{96e9b6a} not attached to Activity exception. I don't understand why I'm getting this exception and what the best way to resolve this is. Have I placed the right code in the right fragment methods (ie OnCreate vs OnActivityCreated vs OnCreateView vs etc)?

public class RecipesFragment extends Fragment
        implements RecipeAdapter.RecipeAdapterOnClickHandler,
        LoaderManager.LoaderCallbacks<ArrayList<Recipe>> {

    @BindView(R.id.rv_recipes) RecyclerView mRecyclerView;
    private RecipeAdapter mRecipeAdapter;
    private static final int LOADER_ID = 1000;
    private static final String TAG = "RecipesFragment";

    private OnRecipeClickListener mOnRecipeClickListener;

    public RecipesFragment(){
    }

    public interface OnRecipeClickListener {
        void OnRecipeClick(Recipe recipe);
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.recipes_fragment, container, false);
        ButterKnife.bind(this, view);
        LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false);
        mRecyclerView.setLayoutManager(layoutManager);
        mRecyclerView.setHasFixedSize(true);
        mRecipeAdapter = new RecipeAdapter(this);
        mRecyclerView.setAdapter(mRecipeAdapter);
        return view;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getLoaderManager().initLoader(LOADER_ID, null, this);
    }

    @Override
    public void onPause() {
        super.onPause();
    }

    @Override
    public void onResume() {
        super.onResume();
    }

    @Override
    public void OnClick(Recipe recipe) {
        mOnRecipeClickListener.OnRecipeClick(recipe);
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        try{
            mOnRecipeClickListener = (OnRecipeClickListener) context;
        } catch (ClassCastException e){
            Log.e(TAG, "onAttach: Host activity class must implement OnRecipeClickListener.");
        }
    }

    @Override
    public Loader<ArrayList<Recipe>> onCreateLoader(int i, Bundle bundle) {
        return new AsyncTaskLoader<ArrayList<Recipe>>(getActivity()) {

            @Override
            protected void onStartLoading() {
                super.onStartLoading();
                forceLoad();
            }

            @Override
            public ArrayList<Recipe> loadInBackground() {
                String response;
                ArrayList<Recipe> recipes = null;
                try {
                    URL url = new URL(getString(R.string.URL_RecipeJSON)); //***I get an exception here***
                    response = NetworkUtils.getResponseFromHttpUrl(url, getActivity());
                    recipes = RecipeJsonUtils.getRecipeFromJson(getActivity(), response);
                } catch (Exception e) {
                    Log.e(TAG, "loadInBackground: " + e.getMessage());
                }
                return recipes;
            }
        };
    }

    @Override
    public void onLoadFinished(Loader<ArrayList<Recipe>> loader, ArrayList<Recipe> recipes) {
        mRecipeAdapter.setRecipeData(recipes);
    }

    @Override
    public void onLoaderReset(Loader<ArrayList<Recipe>> loader) {

    }
}

Solution

  • I finally figured out the problem and the solution. The problem is that onStartLoading() in the AsyncTaskLoader anonymous class in RecipesFragment class gets called every time the fragment is resumed whether the enclosing Loader is called or not. This causes the problem. I need to have control over when onStartLoading() is being called and I only want it to be called if and only if the enclosing Loader is being initialized or restarted. As such, I destroyed the loader in onPause() of the fragment and restarted it in onResume(). Hence, I added the following code to the RecipesFragment class:

    @Override
    public void onPause() {
        super.onPause();
        getLoaderManager().destroyLoader(LOADER_ID);
    }
    
    @Override
    public void onResume() {
        super.onResume();
        getLoaderManager().restartLoader(LOADER_ID, null, this);
    }
    

    I also removed initLoader() from onCreate(). This way, every time the fragment is resumed (or created) onStartLoading() will be called. I tried this and it solves my problem.