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