1

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);
    }
}
Zain
  • 37,492
  • 7
  • 60
  • 84
Richard Wilson
  • 297
  • 4
  • 17

1 Answers1

1

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 ....
Zain
  • 37,492
  • 7
  • 60
  • 84
  • On trying your suggestion, the refresh panel still behaves the same way i.e still reloads continuously until I exit the app. – Richard Wilson Oct 16 '21 at 21:17
  • @RichardWilson Do you have any other related code to `realSwipe`? – Zain Oct 16 '21 at 21:30
  • Another tip make `swipeRefreshLayout.setRefreshing(false);` as the default behavior in `onCreateView()`, and when you hit the search button, toggle it: `swipeRefreshLayout.setRefreshing(true);` – Zain Oct 16 '21 at 21:36
  • Please I don't understand, added realSwipe.setRefreshing(true); after the delay but it still behaved the same way. How do you see this code: If the user hasn't searched a city, refuse to refresh the panel? – Richard Wilson Oct 17 '21 at 09:17
  • Sorry, I am confused about how do you expect the `realSwipe` to achieve .. Do you want to run it on regular basis to update the UI from the API; or do you let the user swipes it from the top to update the UI? – Zain Oct 17 '21 at 09:45
  • The latter. And it works already, the only problem is that if I haven't searched a city yet and I swipe it, it keeps reloading non stop that's what I want to fix – Richard Wilson Oct 17 '21 at 14:49
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/238255/discussion-between-zain-and-richard-wilson). – Zain Oct 17 '21 at 19:08
  • Good evening bro. Please if you won't mind, I bountied a question that hasn't been solved for a few days now https://stackoverflow.com/q/69608600/16020235 – Richard Wilson Oct 22 '21 at 23:23
  • @RichardWilson Good evening .. That looks challenging, let me check it out; probably I need to ask some questions in advance – Zain Oct 23 '21 at 00:33
  • Okay no p, I'd patiently wait and be checking around. Thanks – Richard Wilson Oct 23 '21 at 05:51