Home > Back-end >  App crashes when tapping multiple times on Bottom Navigation Views
App crashes when tapping multiple times on Bottom Navigation Views

Time:10-23

My app has an activity that hosts 3 fragments. These fragments can be navigated by tapping on the bottom navigation views. It works quite fine only that when I tried tapping on the bottom navigation views severally, it crashed with the following error at runtime:

java.lang.IllegalArgumentException: saveBackStack("48c3d9bf-beff-4ec0-8a1b-fb91b56a2765") must be self contained and not reference fragments from non-saved FragmentTransactions. Found reference to fragment SecondFragment{57f9be2} (dd3744e7-8aa3-4c45-b6bc-312a9d46afb4 id=0x7f0a00b0) in BackStackEntry{ba06b73 48c3d9bf-beff-4ec0-8a1b-fb91b56a2765} that were previously added to the FragmentManager through a separate FragmentTransaction.
        at androidx.fragment.app.FragmentManager.saveBackStackState(FragmentManager.java:2052)
        at androidx.fragment.app.FragmentManager$SaveBackStackState.generateOps(FragmentManager.java:3172)
        at androidx.fragment.app.FragmentManager.generateOpsForPendingActions(FragmentManager.java:1953)
        at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1643)
        at androidx.fragment.app.FragmentManager$4.run(FragmentManager.java:480)
        at android.os.Handler.handleCallback(Handler.java:873)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6819)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:497)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:912)

I've checked throughout this site and several other sites for a solution to the issue but found none. So i would like if anyone could help out.

Here's my current activity's code:

public class HomeActivity extends AppCompatActivity {
    private DrawerLayout drawer;
    // Last update time, click sound, search button, search panel.
    TextView time_field;
    MediaPlayer player;
    ImageView Search;
    EditText textfield;
    // For scheduling background image change(using constraint layout, start counting from dubai, down to statue of liberty.
    ConstraintLayout constraintLayout;
    public static int count = 0;
    int[] drawable = new int[]{R.drawable.dubai, R.drawable.central_bank_of_nigeria, R.drawable.eiffel_tower, R.drawable.hong_kong, R.drawable.statue_of_liberty};
    Timer _t;

    private WeatherDataViewModel viewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home);
        // use home activity layout.

        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        // Allow activity to make use of the toolbar

        drawer = findViewById(R.id.drawer_layout);

        viewModel = new ViewModelProvider(this).get(WeatherDataViewModel.class);

        ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar
                , R.string.navigation_drawer_open, R.string.navigation_drawer_close);
        drawer.addDrawerListener(toggle);
        toggle.syncState();

        time_field = findViewById(R.id.textView9);
        Search = findViewById(R.id.imageView4);
        textfield = findViewById(R.id.textfield);
        //  find the id's of specific variables.

        BottomNavigationView bottomNavigationView = findViewById(R.id.bottomNavigationView);
        // host 3 fragments along with bottom navigation.
        final NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.fragment);
        assert navHostFragment != null;
        final NavController navController = navHostFragment.getNavController();
        NavigationUI.setupWithNavController(bottomNavigationView, navController);

        // For scheduling background image change
        constraintLayout = findViewById(R.id.layout);
        constraintLayout.setBackgroundResource(R.drawable.dubai);
        _t = new Timer();
        _t.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                // run on ui thread
                runOnUiThread(() -> {
                    if (count < drawable.length) {

                        constraintLayout.setBackgroundResource(drawable[count]);
                        count = (count   1) % drawable.length;
                    }
                });
            }
        }, 5000, 5000);

        Search.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                // make click sound when search button is clicked.
                player = MediaPlayer.create(HomeActivity.this, R.raw.click);
                player.start();

                getWeatherData(textfield.getText().toString().trim());
                // make use of some fragment's data

                Fragment currentFragment = navHostFragment.getChildFragmentManager().getFragments().get(0);
                if (currentFragment instanceof FirstFragment) {
                    FirstFragment firstFragment = (FirstFragment) currentFragment;
                    firstFragment.getWeatherData(textfield.getText().toString().trim());
                } else if (currentFragment instanceof SecondFragment) {
                    SecondFragment secondFragment = (SecondFragment) currentFragment;
                    secondFragment.getWeatherData(textfield.getText().toString().trim());
                } else if (currentFragment instanceof ThirdFragment) {
                    ThirdFragment thirdFragment = (ThirdFragment) currentFragment;
                    thirdFragment.getWeatherData(textfield.getText().toString().trim());
                }
            }

            private void getWeatherData(String name) {

                ApiInterface apiInterface = ApiClient.getClient().create(ApiInterface.class);

                Call<Example> call = apiInterface.getWeatherData(name);

                call.enqueue(new Callback<Example>() {
                    @Override
                    public void onResponse(@NonNull Call<Example> call, @NonNull Response<Example> response) {

                        try {
                            assert response.body() != null;
                            time_field.setVisibility(View.VISIBLE);
                            time_field.setText("First Updated:"   " "   response.body().getDt());
                        } catch (Exception e) {
                            time_field.setVisibility(View.GONE);
                            time_field.setText("First Updated: Unknown");
                            Log.e("TAG", "No City found");
                            Toast.makeText(HomeActivity.this, "No City found", Toast.LENGTH_SHORT).show();
                        }
                    }

                    @Override
                    public void onFailure(@NotNull Call<Example> call, @NotNull Throwable t) {
                        t.printStackTrace();
                    }

                });
            }

        });
    }
}

EDIT

Second Fragment:

public class SecondFragment extends Fragment {

    // TODO: Rename parameter arguments, choose names that match
    // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
    private static final String ARG_PARAM1 = "param1";
    private static final String ARG_PARAM2 = "param2";

    // TODO: Rename and change types of parameters
    private String mParam1;
    private String mParam2;

    public SecondFragment() {
        // Required empty public constructor
    }

    /**
     * Use this factory method to create a new instance of
     * this fragment using the provided parameters.
     *
     * @param param1 Parameter 1.
     * @param param2 Parameter 2.
     * @return A new instance of fragment SecondFragment.
     */
    // TODO: Rename and change types and number of parameters
    public static SecondFragment newInstance(String param1, String param2) {
        SecondFragment fragment = new SecondFragment();
        Bundle args = new Bundle();
        args.putString(ARG_PARAM1, param1);
        args.putString(ARG_PARAM2, param2);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            mParam1 = getArguments().getString(ARG_PARAM1);
            mParam2 = getArguments().getString(ARG_PARAM2);
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_second, container, false);
    }

    public void getWeatherData(String trim) {
    }
}

CodePudding user response:

DISCLAIMER Im writing this as an answer as it would probably be too long for a comment. it is not a proper answer as I we would need to see more of your code for that:

the exception is telling you that there is an issue with your backstack of fragments (this is basically just the place where android remembers and stores the state of your past activities and fragments so that you see the same thing you saw before once you press the back button). I cannot tell for sure what the issue is as I dont see your fragment classes but it sounds like in your code there might be some sort of circular reference or smtng like that. Maybe add code from your fragments. In your position I would look at the fragment called SecondFragment that is references in the exception and in particular its saveInstanceState method. not sure if somehow artificially making your fragments singleTask or singleInstance could help. I recommend reading up on the BackStack. The following docs on a new FragmentManager relese seem to touch on your issue

CodePudding user response:

As you might have guessed already, the problem was about a race condition between the older fragment and the newer one. So here's what you can do.

static volatile - A gate keeper. This will ignore the newer ones if the older has not yet finish.

private static volatile boolean isClicking = false;

...
Search.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v)
                {
                    if(!isClicking) {
                        isClicking = true;

                        // make click sound when search button is clicked.
                        player = MediaPlayer.create(HomeActivity.this, R.raw.click);
                        player.start();

                        getWeatherData(textfield.getText().toString().trim());
                        // make use of some fragment's data

                        Fragment currentFragment = navHostFragment.getChildFragmentManager().getFragments().get(0);
                        if(currentFragment instanceof FirstFragment) {
                            FirstFragment firstFragment = (FirstFragment) currentFragment;
                            firstFragment.getWeatherData(textfield.getText().toString().trim());
                        } else if(currentFragment instanceof SecondFragment) {
                            SecondFragment secondFragment = (SecondFragment) currentFragment;
                            secondFragment.getWeatherData(textfield.getText().toString().trim());
                        } else if(currentFragment instanceof ThirdFragment) {
                            ThirdFragment thirdFragment = (ThirdFragment) currentFragment;
                            thirdFragment.getWeatherData(textfield.getText().toString().trim());
                        }
                        
                        isClicking = false;
                    }
                }
            });
...

static synchronized - Create a helper function that calls everything in onClick(). This will queue the tasks.

...
Search.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        syncOnClick();
    }
});
...

//Outside of "onCreate()".
private static synchronized void syncOnClick()
{
    // make click sound when search button is clicked.
    player = MediaPlayer.create(HomeActivity.this, R.raw.click);
    player.start();

    getWeatherData(textfield.getText().toString().trim());
    // make use of some fragment's data

    Fragment currentFragment = navHostFragment.getChildFragmentManager().getFragments().get(0);
    if(currentFragment instanceof FirstFragment) {
        FirstFragment firstFragment = (FirstFragment) currentFragment;
        firstFragment.getWeatherData(textfield.getText().toString().trim());
    } else if(currentFragment instanceof SecondFragment) {
        SecondFragment secondFragment = (SecondFragment) currentFragment;
        secondFragment.getWeatherData(textfield.getText().toString().trim());
    } else if(currentFragment instanceof ThirdFragment) {
        ThirdFragment thirdFragment = (ThirdFragment) currentFragment;
        thirdFragment.getWeatherData(textfield.getText().toString().trim());
    }
}
  • Related