Search code examples
javaandroidandroid-livedataweatherswiperefreshlayout

Refresh panel reloads forever if a city hasn't been searched


I set up a swipe-to-refresh panel on my weather app to update the weather data I receive from the API whenever I swipe down the refresh button. This feature works successfully well when a city has been searched but the problem now is that if I haven't yet searched any city before swiping down the refresh panel, it reloads forever until I exit the app obstructing the app processes and my app was designed to not save the previous weather data until the user search a city again.

I want to either stop the refresh panel entirely from reloading until a city has been searched or just limit the time it takes to reload before a city has been searched. Please how can I do this? I've checked this site for similar questions/answers and found none that helps/relates to what I'm trying to achieve.

Here's the current refresh panel programmatic setup:

realSwipe.setOnRefreshListener(() -> {
                // perform the delay action
                new Handler().postDelayed(() -> {
                    // this code is for stop refreshing icon, After 1000 ms automatically refresh icon will stop
                    realSwipe.setRefreshing(false);
                }, 1000);
            });

Here's my Fragment's code(in case it'll be needed):

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);
        final ImageView current_icon = rootView.findViewById(R.id.imageView6);
        final SwipeRefreshLayout realSwipe = rootView.findViewById(R.id.real_swipe);

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

        // And whenever the data changes, refresh the UI
        viewModel.getWeatherDataLiveData().observe(getViewLifecycleOwner(), data -> {

            realSwipe.setOnRefreshListener(() -> {
                // perform the delay action
                new Handler().postDelayed(() -> {
                    // this code is for stop refreshing icon, After 1000 ms automatically refresh icon will stop
                    realSwipe.setRefreshing(false);
                }, 1000);
            });

            int drawableResource = -1; // here define default icon for example R.drawable.default_weather_icon

            int soundResource = -1; // Default sound is nothing

            if (data != null) {
                current_temp.setVisibility(View.VISIBLE);
                current_temp.setText(data.getMain().getTemp() + " ℃"); // for that you can use strings resource and templates more in https://developer.android.com/guide/topics/resources/string-resource.html#formatting-strings
                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() + " %");

                // get actual weather.

                String icon = data.getWeather().get(0).getIcon();

                switch (icon) {
                    case "01d":
                    case "01n":
                        drawableResource = R.drawable.sun;
                        soundResource = R.raw.clear_sky_sound;
                        break;

                    case "02d":
                    case "021n":
                        drawableResource = R.drawable.few_clouds;
                        soundResource = R.raw.clouds_sound;
                        break;

                    case "03d":
                    case "03n":
                        drawableResource = R.drawable.scattered_clouds;
                        soundResource = R.raw.clouds_sound;
                        break;

                    case "04d":
                    case "04n":
                        drawableResource = R.drawable.broken_clouds;
                        soundResource = R.raw.clouds_sound;
                        break;

                    case "09d":
                    case "09n":
                        drawableResource = R.drawable.shower_rain;
                        soundResource = R.raw.shower_rain_sound;
                        break;

                    case "10d":
                    case "10n":
                        drawableResource = R.drawable.small_rain;
                        soundResource = R.raw.shower_rain_sound;
                        break;

                    case "11d":
                    case "11n":
                        drawableResource = R.drawable.thunderstorm;
                        soundResource = R.raw.thunderstorm_sound;
                        break;

                    case "13d":
                    case "13n":
                        drawableResource = R.drawable.snow;
                        soundResource = R.raw.snow_sound;
                        break;

                    case "50d":
                    case "50n":
                        drawableResource = R.drawable.mist;
                        soundResource = R.raw.mist_sound;
                        break;
                }

                if (drawableResource != -1)
                    current_icon.setImageResource(drawableResource);
                    // set the first host instance for displaying the weather icons

                if (soundResource != -1 && viewModel.soundResource != soundResource) {
                    viewModel.soundResource = soundResource;
                    if (viewModel.getMediaPlayer() != null) {
                        // we set our soundplayer in retrospect to our viewmodel

                        // stop the playing
                        if (viewModel.getMediaPlayer().isPlaying()) {
                            viewModel.getMediaPlayer().stop();
                        }

                        // release mMediaPlayer resoruces
                        viewModel.getMediaPlayer().release();
                        viewModel.setMediaPlayer(null);
                    }

                    // Play the new resource
                    viewModel.prepareMediaPlayer(requireContext(), soundResource);

                }

            } 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);
    }
}

Solution

  • the problem now is that if I haven't yet searched any city before swiping down the refresh panel, it reloads forever

    So, the problem occurs whenever the searched city is blank/empty String. And this means that the observed MutuableLiveData callback is triggered on that case.

    As I was involved in your last question; you load the data without empty string checks:

    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();
            }
        });
    }
    

    To fix this:

    • Return early if the city name is empty
    • Check if there is no response from the the API Retrofit before updating the UI.
    private void loadData() {
        // Get the last name that was set
        String name = state.get("name");
        
        // Return early if the city name is empty
        if (name == null || name.isEmpty()) return;
    
        // 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) {
            
                // Check if there is no response from the the Retrofit before updating the UI
                if (response.body() == null) return;
            
                // 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();
            }
        });
    }
    

    UPDATE

    As it's pointed out from the comments and chats, that the desired behavior is not to update the UI on regular basis, but instead to make the user swipes the screen from the top to update the UI.

    And to resolve the swipeToRefresh reloading issue requires to get the the swipeToRefresh listener out of the LiveData observation:

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
                             
        // Code is omitted  ....                 
                             
         realSwipe.setOnRefreshListener(() -> {
            // perform the delay action
            new Handler().postDelayed(() -> {
                // this code is for stop refreshing icon, After 1000 ms automatically refresh icon will stop
                realSwipe.setRefreshing(false);
            }, 1000);
        });
    
        // And whenever the data changes, refresh the UI
        viewModel.getWeatherDataLiveData().observe(getViewLifecycleOwner(), data -> {
    
            int drawableResource = -1; // here define default icon for example R.drawable.default_weather_icon
    
            int soundResource = -1; // Default sound is nothing
            
            // Rest of the code ....