Data resets when bottom navigation views are clicked
Asked Answered
E

4

1

I have a bottom navigation view on my weather app containing 3 panels(Today, hourly & Daily). My activity hosts 3 fragments for the 3 panels. Currently, if I search for any city on the today fragment, it gives the data for such cities. Now the problem is that if I click on any bottom nav view(down), it resets the data displayed on the today fragment.

Here is an illustration of the issue:

  • Data is displayed after a city is searched(the part with a red tick): enter image description here

  • Data goes on reset after clicking these bottom nav views(the part with a red tick): enter image description here

I want the data to remain intact irrespective of clicking those nav views.

I tried using https://mcmap.net/q/183564/-stop-fragment-refresh-in-bottom-nav-using-navhost suggestion. But it failed with this exception:

java.lang.IllegalArgumentException: No view found for id 0x7f0a0116 (com.viz.lightweatherforecast:id/my_nav) for fragment ThirdFragment{90bc0de} (8e129d17-010d-41dc-9311-82e273b4e522 id=0x7f0a0116 tag=3)
        at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:513)
        at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:282)
        at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:2189)
        at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:2100)
        at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:2002)
        at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:3134)
        at androidx.fragment.app.FragmentManager.dispatchActivityCreated(FragmentManager.java:3068)
        at androidx.fragment.app.FragmentController.dispatchActivityCreated(FragmentController.java:251)
        at androidx.fragment.app.FragmentActivity.onStart(FragmentActivity.java:501)
        at androidx.appcompat.app.AppCompatActivity.onStart(AppCompatActivity.java:210)
        at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1391)
        at android.app.Activity.performStart(Activity.java:7157)
        at android.app.ActivityThread.handleStartActivity(ActivityThread.java:3037)
        at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:180)
        at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:165)
        at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:142)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:70)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1861)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        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/weatherforecas: Compiler allocated 4MB to compile void android.widget.TextView.<init>(android.content.Context, android.util.AttributeSet, int, int)

I find it hard to implement his code and the remaining suggestions on that question are written in kotlin.

Please how can I resolve this?

Here are my codes:

my_nav.xml(navigation layout):

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/my_nav"
    app:startDestination="@id/firstFragment">

    <fragment
        android:id="@+id/firstFragment"
        android:name="com.viz.lightweatherforecast.FirstFragment"
        android:label="fragment_first"
        tools:layout="@layout/fragment_first" />
    <fragment
        android:id="@+id/secondFragment"
        android:name="com.viz.lightweatherforecast.SecondFragment"
        android:label="fragment_second"
        tools:layout="@layout/fragment_second" />
    <fragment
        android:id="@+id/thirdFragment"
        android:name="com.viz.lightweatherforecast.ThirdFragment"
        android:label="fragment_third"
        tools:layout="@layout/fragment_third" />
</navigation>

activity_home.xml:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/layout"
    android:background="@drawable/dubai"
    tools:context=".Activity.HomeActivity">

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottomNavigationView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#FFFFFF"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:menu="@menu/bottom_menu" />

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="409dp"
        android:layout_height="599dp"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toTopOf="@+id/bottomNavigationView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:navGraph="@navigation/my_nav"
        />

</androidx.constraintlayout.widget.ConstraintLayout>

HomeActivity:

public class HomeActivity extends AppCompatActivity {
    // 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;

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

        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
                FirstFragment firstFragment = (FirstFragment) navHostFragment.getChildFragmentManager().getFragments().get(0);
                firstFragment.getWeatherData(textfield.getText().toString().trim());

            }

            
                });
            }

        });
    }
}

EDIT

Fragment class:

public class FirstFragment extends Fragment {
    // User current time, current temperature, current condition, sunrise, sunset, temperature, pressure, humidity, wind_speed, visibility, clouds
    TextView current_temp, current_output, rise_time, set_time, temp_out, Press_out, Humid_out, Ws_out, Visi_out, Cloud_out;
    // 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 FirstFragment() {
        // 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 FirstFragment newInstance(String param1, String param2) {
        FirstFragment fragment = new FirstFragment();
        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
        View rootView = inflater.inflate(R.layout.fragment_first, container, false);
        // For displaying weather data
        current_temp = rootView.findViewById(R.id.textView10);
        current_output = rootView.findViewById(R.id.textView11);
        rise_time = rootView.findViewById(R.id.textView25);
        set_time = rootView.findViewById(R.id.textView26);
        temp_out = rootView.findViewById(R.id.textView28);
        Press_out = rootView.findViewById(R.id.textView29);
        Humid_out = rootView.findViewById(R.id.textView30);
        Ws_out = rootView.findViewById(R.id.textView33);
        Visi_out = rootView.findViewById(R.id.textView34);
        Cloud_out = rootView.findViewById(R.id.textView35);

        return rootView;
    }

    public 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;
                    current_temp.setVisibility(View.VISIBLE);
                    current_temp.setText(response.body().getMain().getTemp() + " ℃");
                    current_output.setVisibility(View.VISIBLE);
                    current_output.setText(response.body().getWeather().get(0).getDescription());
                    rise_time.setVisibility(View.VISIBLE);
                    rise_time.setText(response.body().getSys().getSunrise() + " ");
                    set_time.setVisibility(View.VISIBLE);
                    set_time.setText(response.body().getSys().getSunset() + " ");
                    temp_out.setVisibility(View.VISIBLE);
                    temp_out.setText(response.body().getMain().getTemp() + " ℃");
                    Press_out.setVisibility(View.VISIBLE);
                    Press_out.setText(response.body().getMain().getPressure() + " hpa");
                    Humid_out.setVisibility(View.VISIBLE);
                    Humid_out.setText(response.body().getMain().getHumidity() + " %");
                    Ws_out.setVisibility(View.VISIBLE);
                    Ws_out.setText(response.body().getWind().getSpeed() + " Km/h");
                    Visi_out.setVisibility(View.VISIBLE);
                    Visi_out.setText(response.body().getVisibility() + " m");
                    Cloud_out.setVisibility(View.VISIBLE);
                    Cloud_out.setText(response.body().getClouds().getAll() + " %");
                } catch (Exception e) {
                    Log.e("TAG", "No City found");
                    current_temp.setVisibility(View.GONE);
                    current_output.setVisibility(View.GONE);
                    rise_time.setVisibility(View.GONE);
                    set_time.setVisibility(View.GONE);
                    temp_out.setVisibility(View.GONE);
                    Press_out.setVisibility(View.GONE);
                    Humid_out.setVisibility(View.GONE);
                    Ws_out.setVisibility(View.GONE);
                    Visi_out.setVisibility(View.GONE);
                    Cloud_out.setVisibility(View.GONE);
                    Toast.makeText(getActivity(), "No City found", Toast.LENGTH_SHORT).show();
                }

            }

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

I didn't paste everything because I'm following https://stackoverflow.com/help/minimal-reproducible-example

Easterner answered 5/6, 2021 at 22:2 Comment(14)
Have you tried navView.setOnNavigationItemReselectedListener { }Minus
Does your fragment lose its state if you rotate your device? What if you enable the "Don't save activities" developer option? If either of those fail, then your problem is with your fragment not saving its state. If both of those succeed, what version of Navigation are you using?Pygmy
@zain I tried but he wrote it in Kotlin, so i couldn't interpret itEasterner
It's in java: navView.setOnNavigationItemReselectedListener(new BottomNavigationView.OnNavigationItemReselectedListener() { @Override public void onNavigationItemReselected(@NonNull MenuItem item) { } });Minus
@Pygmy No, I made the app not to rotate even if I rotate the device. I enabled it, it still resets. I use version 2.3.4'Easterner
@Minus Okay, now it stopped refreshing for the first panel(Today in First Fragment) but it still refreshes after I click the remaining views(hourly and daily)Easterner
In which class did you put this code snippet?Minus
If it still resets when you rotate your device your problem is with your fragment - it isn't saving and restoring its state properly. You need to fix those problems first, but you haven't shared that fragment's code.Pygmy
@zain my activity classEasterner
@Pygmy Okay, I've shared it nowEasterner
Finally, a post recognizing the issue with navigation component used with bottomNav... Navigation component uses replace() under the hood withoud addToBackStack(), so you fragments are being recreated with every click on bottonNav. I would suggest you getting rid of navigation component( just for bottom nav) and do frgament switching ma nually via show() hide(). or use fragNav library.Infantilism
@RinatDiushenov thanks for your suggestion. But let me wait to see if someone can come up with a solution. If not, I can try yours as a last resort.Easterner
Your HomeActivity references R.id.textView9, R.id.imageView4, and R.id.textfield, which aren't in your activity_home.xml file. Are those actually part of your layout? What is your Search.setOnClickListener supposed to do if you aren't on FirstFragment?Pygmy
ianhanniballake, no they are all in activity_home.xml.I just reduced the code for mre. The search on click listener connects both activity and fragment together. Whenever I search a city, it displays the results of both activity and fragment.Easterner
P
3

Your problem is that your FirstFragment is not saving its state properly. As per the Saving state with fragments guide:

To ensure the user's state is saved, the Android framework automatically saves and restores the fragments and the back stack. Therefore, you need to ensure that any data in your fragment is saved and restored as well.

But you aren't saving the last name that you pass to getWeatherData, nor are you saving your Example object that you get from your API call in order to repopulate your views when they are recreated.

So need to actually use the APIs described in that guide to save your state. Namely, you should file the Guide to app architecture, which explains how you can separate your from your data loading by using ViewModels to store data across configuration changes (like rotating your device) and LiveData to automatically populate your UI whenever your data is loaded.

The first thing we want to do is move the data loading to a ViewModel. This object survives configuration changes which means any data stored in this class is automatically saved when you rotate your device. This is how we can save your Example class and avoid calling the server over and over.

By using the APIs in the Saved State module for ViewModel (specifically, the SavedStateHandle class), any data you save in there will survive your process being killed and later recreated (say, if your device is low on memory, etc.). This is how we can save the last name so that we will automatically requery for your data.

Here, our ViewModel handles all of the loading from the server and uses a LiveData to let our UI automatically update as the data is loaded.

public class WeatherDataViewModel extends ViewModel {
    // This will save the city name
    private SavedStateHandle state;

    // This is where we'll store our result from the server
    private MutableLiveData<Example> mutableWeatherData = new MutableLiveData<>();

    public WeatherDataViewModel(SavedStateHandle savedStateHandle) {
        state = savedStateHandle;
        String savedCityName = state.get("name");
        if (savedCityName != null) {
            // We already had a previously saved name, so we'll
            // start loading right away
            loadData();
        }
    }

    // This is what our Fragment will use to get the latest weather data
    public LiveData<Example> getWeatherDataLiveData() {
        return mutableWeatherData;
    }

    // When you get a new city name, we'll save that in our
    // state, then load the new data from the server
    public void setCityName(String name) {
        state.set("name", name);
        loadData();
    }

    private void loadData() {
        // Get the last name that was set
        String name = state.get("name");

        // Now kick off a load from the server
        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) {
                // Save the response we've gotten
                // This will automatically update our UI
                mutableWeatherData.setValue(response.body());
            }

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

Now you can rewrite your FirstFragment to use the WeatherDataViewModel as the source of truth for your UI:

public class FirstFragment extends Fragment {

    private WeatherDataViewModel viewModel;

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

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View rootView = inflater.inflate(R.layout.fragment_first, container, false);
        // For displaying weather data
        final TextView current_temp = rootView.findViewById(R.id.textView10);
        final TextView current_output = rootView.findViewById(R.id.textView11);
        final TextView rise_time = rootView.findViewById(R.id.textView25);
        final TextView set_time = rootView.findViewById(R.id.textView26);
        final TextView temp_out = rootView.findViewById(R.id.textView28);
        final TextView Press_out = rootView.findViewById(R.id.textView29);
        final TextView Humid_out = rootView.findViewById(R.id.textView30);
        final TextView Ws_out = rootView.findViewById(R.id.textView33);
        final TextView Visi_out = rootView.findViewById(R.id.textView34);
        final TextView Cloud_out = rootView.findViewById(R.id.textView35);

        // Get our ViewModel instance
        viewModel = new ViewModelProvider(this).get(WeatherDataViewModel.class);

        // And whenever the data changes, refresh the UI
        viewModel.getWeatherDataLiveData().observe(getViewLifecycleOwner(), data -> {
            if (data != null) {
                current_temp.setVisibility(View.VISIBLE);
                current_temp.setText(data.getMain().getTemp() + " ℃");
                current_output.setVisibility(View.VISIBLE);
                current_output.setText(data.getWeather().get(0).getDescription());
                rise_time.setVisibility(View.VISIBLE);
                rise_time.setText(data.getSys().getSunrise() + " ");
                set_time.setVisibility(View.VISIBLE);
                set_time.setText(data.getSys().getSunset() + " ");
                temp_out.setVisibility(View.VISIBLE);
                temp_out.setText(data.getMain().getTemp() + " ℃");
                Press_out.setVisibility(View.VISIBLE);
                Press_out.setText(data.getMain().getPressure() + " hpa");
                Humid_out.setVisibility(View.VISIBLE);
                Humid_out.setText(data.getMain().getHumidity() + " %");
                Ws_out.setVisibility(View.VISIBLE);
                Ws_out.setText(data.getWind().getSpeed() + " Km/h");
                Visi_out.setVisibility(View.VISIBLE);
                Visi_out.setText(data.getVisibility() + " m");
                Cloud_out.setVisibility(View.VISIBLE);
                Cloud_out.setText(data.getClouds().getAll() + " %");
            } else {
                Log.e("TAG", "No City found");
                current_temp.setVisibility(View.GONE);
                current_output.setVisibility(View.GONE);
                rise_time.setVisibility(View.GONE);
                set_time.setVisibility(View.GONE);
                temp_out.setVisibility(View.GONE);
                Press_out.setVisibility(View.GONE);
                Humid_out.setVisibility(View.GONE);
                Ws_out.setVisibility(View.GONE);
                Visi_out.setVisibility(View.GONE);
                Cloud_out.setVisibility(View.GONE);
                Toast.makeText(requireActivity(), "No City found", Toast.LENGTH_SHORT).show();
            }
        });

        return rootView;
    }

    public void getWeatherData(String name) {
        // The ViewModel controls loading the data, so we just
        // tell it what the new name is - this kicks off loading
        // the data, which will automatically call through to
        // our observe() call when the data load completes
        viewModel.setCityName(name);
    }
}

With these changes, you'll find that your Fragment now correctly handles:

  • Being put on the Fragment back stack
  • Configuration changes (i.e., rotating your device)
  • Process death and recreation (i.e., testing with 'Don't keep activities' on)

You'll note how we use new ViewModelProvider(this).get(WeatherDataViewModel.class) - that creates a WeatherDataViewModel that is tied to this - your Fragment itself. This is best if the data your ViewModel loads is only used in that one Fragment.

If you also wanted to use this same data in your Activity, your activity could use new ViewModelProvider(this).get(WeatherDataViewModel.class) to create a WeatherDataViewModel that is scoped to the entire Activity. Any Fragment could then use new ViewModelProvider(requireActivity()).get(WeatherDataViewModel.class) to get that Activity owned ViewModel. This would potentially mean that you wouldn't need a getWeatherData() method on your Fragment at all - instead, your Activity would directly call viewModel.setCityName(name) itself and all Fragments would just instantly update (as they read from the same ViewModel).

Pygmy answered 12/6, 2021 at 21:59 Comment(9)
I'm sorry for taking so long to respond. It's because I'm currently reading for series of exams in college and I noticed this solution was quite long so I decided to reserve enough time to implement it. I honestly appreciate your priceless effort in helping me. But while the code ran with no errors, it behaves exactly the same way as beforeEasterner
Does this solve your problem with configuration changes? If so, how are you returning to your first fragment - via the system back button or by clicking on the item in the bottom nav? What version of Navigation are you using?Pygmy
No, It doesn't solve the problem. I'm returning back via clicking on the item in the bottom nav, the data still resets. My navigation version(on build.gradle) is implementation 'Androidx.navigation:navigation-fragment:2.3.5Easterner
What do you mean by 'it doesn't solve the problem' - it doesn't solve the problem of a configuration change? It certainly should fix that part. Does the data retain itself if you return via the system back button? If so, then you need to upgrade to Navigation 2.4.0 to utilize the support there for multiple back stacks (i.e., saving and restoring the state of each bottom nav item) if you want to also save/restore when tapping on the nav item.Pygmy
I mean that my data doesn't remain intact after clicking the bottom navigation views and that's my aim. Please how do I upgrade to 2.4.0? Because Android studio doesn't suggest to me on lintEasterner
That's why I'm asking if it does save your state when you hit the system back button or when you do a config change - that'll tell you if your fragment is saving its state properly on its own (and the only remaining problem would be with Navigation). To upgrade, you'd replace 2.3.5 with the latest release - i.e., 2.4.0-alpha02.Pygmy
Let us continue this discussion in chat.Easterner
Hey, here's the link https://mcmap.net/q/161384/-problem-upgrading-my-fragment-39-s-navigation-version-from-2-3-5-to-2-4-0-alpha03/16020235Easterner
Downgrading gradle back to 6.1.1 and downgrading both my navigation-fragment and UI to 2.4.0-alpha01 made the fix. Thanks a lot, man and to everyone who contributed in their own way to help me fix this issue!Easterner
D
0

I think, this is because your FirstFragment gets reinstantiated every time. To save state, modify onCreate() of your FirstFragment like following:

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

And add onSaveInstanceState() to your FirstFragment:

@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putString(ARG_PARAM1, mParam1);
    outState.putString(ARG_PARAM2, mParam2);
}
Dinothere answered 9/6, 2021 at 16:15 Comment(2)
Unfortunately, this solution didn't work as it continues to act the same way after implementing it.Easterner
Since it is unclear how you have implemented NavHostController, this might help https://mcmap.net/q/258526/-android-jetpack-navigation-bottomnavigationview-with-youtube-or-instagram-like-proper-back-navigation-fragment-back-stackDinothere
G
0

The mistake seems to be that every time you select an item in the bottom navigation bar the fragment gets re-instantized thus creating a new fragment(of same type, but a new object of that fragment).

I would suggest you create an object of all the fragment at the start like this

private final Fragment[] fragments = {
            new HomeFragment(),
            new MyNotesFragment(),
            new PreferencesFragment()
    };

and then for the Bottom Nav Listener part, we will change the fragments according to the button clicked by passing already made instances of fragments rather than creating new ones.

bottom_navigation_view.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
            @Override
            public boolean onNavigationItemSelected(@NonNull MenuItem item) {
                // Checking which bottom_nav items is clicked by comparing with string names..
                if (item.getTitle().toString().equals("Home")) {
                    // Code to change the fragments
                    final FragmentTransaction ft = 
                    getFragmentManager().beginTransaction(); 
                    // we passed the already made instance of the home fragment rather than creating a new one
                    ft.replace(R.id.my_fragment, fragments[0], "HomeFrag"); 
                    ft.commit(); 
                } else if (item.getTitle().toString().equals("My Notes")) {
                    // Code to change the fragments
                    final FragmentTransaction ft = 
                    getFragmentManager().beginTransaction(); 
                    ft.replace(R.id.my_fragment, fragments[1], "MyNotesFrag"); 
                    ft.commit(); 
                } else if (item.getTitle().toString().equals("Preferences")) {
                    // Code to change the fragments
                    final FragmentTransaction ft = 
                    getFragmentManager().beginTransaction(); 
                    ft.replace(R.id.my_fragment, fragments[2], "PrefFrag"); 
                    ft.commit(); 
                }
                return true;
            }
        });

(Though this is for the ViewPager library the concept works the same)

Geode answered 12/6, 2021 at 6:17 Comment(5)
Ok but on this line you shared ft.replace(R.id.my_fragment. Please what exactly am I supposed to use as my id? because when I'm using my_fragment. It's saying required type: android.fragment, provided: androidx.fragmentEasterner
Here in place of R.id.my_fragmet pass the id of the fragment view which you created in the XML file. In your "my_nav.xml" it can be seen that you have created 3 <fragment> components. Just create one component and use fragment manager to change the fragment in that <fragment> componentGeode
<fragment> is a placeholder for the fragment which you want to show at that time. Judging by your code it seems you un-intentionally created 3 <fragment> instead of One A more generic way to understand this is <fragment> is like a bag in which you can store one thing at a time. Suppose, if you have an apple put it inside that bag, now when you want to put orange in it you have to take out the apple and put orange in it. (Because you want only 1 thing present inside of it at a given time right). What you have done here is created 3 separate bags for 3 separate items. got it @RichardWilson ?Geode
Even while using your suggestion, They said 'replace(int, android.app.fragment, jave.lang.String)' in 'android.app.FragmentTransaction' cannot be applied to '(int, androidx.fragment.app.Fragment, java.lang.String)'Easterner
Maybe that's a library mismatch issue. Check out these links: developer.android.com/guide/fragments/transactions && developer.android.com/guide/fragments/createGeode
C
0

If you still have not solve it yet, you might want to try this solution that uses SharedPreferences.

public class FirstFragment extends Fragment
{
    // ...
    private SharedPreferences prefs;
    private SharedPreferences.Editor prefsEditor;
    // ...

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        // Inflate the layout for this fragment
        View rootView = inflater.inflate(R.layout.fragment_first, container, false);
        // For displaying weather data
        current_temp = rootView.findViewById(R.id.textView10);
        current_output = rootView.findViewById(R.id.textView11);
        // ...
        prefs = new SharedPreferences(getActivity().getApplicationContext());
        prefsEditor = prefs.edit();
        current_temp.setText(prefs.getString("current_temp", "not yet initialised"));
        current_output.setText(prefs.getString("current_output", "not yet initialised"));
        // ...
        return rootView;
    }

    public 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;
                    final String currentTemp = response.body().getMain().getTemp() + " ℃";
                    editor.putString("current_temp", currentTemp);
                    editor.commit();
                    current_temp.setText(currentTemp);
                    current_temp.setVisibility(View.VISIBLE);

                    final String currentOutput = response.body().getWeather().get(0).getDescription();
                    editor.putString("current_output", currentOutput);
                    editor.commit();
                    current_output.setText(currentOutput);
                    current_output.setVisibility(View.VISIBLE);
                    // ...
                } catch (Exception e) {
                    Log.e("TAG", "No City found");
                    current_temp.setVisibility(View.GONE);
                    current_output.setVisibility(View.GONE);
                    // ...
                    Toast.makeText(getActivity(), "No City found", Toast.LENGTH_SHORT).show();
                }
            }
        // ...
        });
    }
}
Castiron answered 13/6, 2021 at 12:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.