1

After successfully implementing sounds on my weather app, it plays quite OK when a city is searched. But the problem is that even when I minimize the app, it still keeps playing the sounds on repeat until I close/exit the app. I would like it to pause when I minimize the app, then continue playing from exactly where it stopped the moment I enter back.

So I tried adding this code:

@Override
    public void onStop() {
        super.onStop();
        mMediaPlayer.pause();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mMediaPlayer.pause();
    }

It only entirely stops the sound after minimizing and doesn't resume when entering back. It also slows the app from searching a city.

Also, the app contains 3 fragments(today, hourly and daily tabs) which I navigate through by clicking on the bottom navs. If I'm on the first tab and the sound is playing then I switch to the 2nd/3rd tab then move back to the first tab, the sound automatically restarts whichever sound it is playing. I would like to resolve the issue also.

Here is the Fragment's code:

public class FirstFragment extends Fragment {

    private WeatherDataViewModel viewModel;

    private MediaPlayer mMediaPlayer; // Single MediaPlayer object

    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(this).get(WeatherDataViewModel.class);

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

            realSwipe.setOnRefreshListener(() -> {
                // perform you action here for ex. add refresh screen code here
                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);


                if (soundResource != -1) {

                    if (mMediaPlayer != null) {

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

                        // release mMediaPlayer resoruces
                        mMediaPlayer.release();
                        mMediaPlayer = null;
                    }

                    // Play the new resource
                    prepareMediaPlayer(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);
    }


    private void prepareMediaPlayer(int resource) {
        // add track file
        mMediaPlayer = MediaPlayer.create(requireActivity(), resource);

        // listening to when the media file finishes playing so that we can release the resources
        mMediaPlayer.setLooping(true);
        mMediaPlayer.start();

    }

}

EDIT:

WeatherDataViewModel:

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();
            }
        });
    }
}
Richard Wilson
  • 297
  • 4
  • 17

2 Answers2

1

when I minimize the app, it still keeps playing the sounds on repeat until I close/exit the app. I would like it to pause when I minimize the app, then continue playing from exactly where it stopped the moment I enter back.

You can pause the mediPlayer when the fragment is not shown on the screen i.e in onPause() callback, and resume it in onResume().

But be careful as the mediaPlayer doesn't start immediately in onCreate() because it waits until the data is grabbed from the weather API; so you need to do null-ability check:

public void onResume() {
    super.onResume();
    if (mMediaPlayer != null)
        mMediaPlayer.start();
}

@Override
public void onPause() {
    super.onPause();
    if (mMediaPlayer != null)
        mMediaPlayer.pause();
}

Side note: In order to avoid potential memory/battery leaks whenever you do fragment transaction, The mediaPlayer resource should be released if the application is destroyed:

@Override
public void onDestroy() {
    super.onDestroy();
    // release mMediaPlayer resoruces
    mMediaPlayer.release();
    mMediaPlayer = null;
}

But I would suggest to add the mMediaPlayer object into the ViewModel, in order to save its state during configuration changes like phone orientation.

Zain
  • 37,492
  • 7
  • 60
  • 84
  • Regarding `It also slows the app from searching a city.` part, please elaborate more, how do you figure it out? what are the symptoms of the slowness? – Zain Oct 11 '21 at 17:40
  • Tested your solution on the first issue, it now works very well(the exact way I described it)Thanks a lot. But you didn't elaborate on the fragment switch issue, the sounds still behave the same way there. "But I would suggest adding the mMediaPlayer object into the ViewModel, to save its state during configuration changes like phone orientation." Okay, thanks for your suggestion. Only that I don't know how I can do it. It no more slows the app again. Only had that issue when I tried my previous codes. – Richard Wilson Oct 11 '21 at 20:52
  • I would bounty if you don't mind to get the 2nd issue fixed? – Richard Wilson Oct 11 '21 at 20:54
  • @RichardWilson just transfer the object to the ViewModel class; you already have one as I see in your shared code .. Your class is named `WeatherDataViewModel`, so just move the `private MediaPlayer mMediaPlayer;` there, create a public getter & setter to access it (or make it public)... And then to use it in the fragment, just call `viewModel.foo`; for instance `viewModel.getMediaPlayer()` .. Give it a try and turn around if you got stuck .. good luck I can't wait until I see your app up and running :) – Zain Oct 11 '21 at 21:09
  • @RichardWilson let me know if you got stuck.. but update the question with the viewModel; it's not a big deal for bounties .. keep them for something sophisticated :) – Zain Oct 12 '21 at 03:48
  • Okay, I tried your suggestion. It gave output: Result of viewModel.getmMediaPlayer() is ignored when I called it in the fragment. Still have much to do on the app before launching but thanks a lot for your encouragement. Really appreciate. Just updated the question with the viewModel. – Richard Wilson Oct 12 '21 at 12:12
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/238065/discussion-between-richard-wilson-and-zain). – Richard Wilson Oct 12 '21 at 15:16
0

you need to use Services : https://developer.android.com/guide/components/services

see this simple exaple too : https://www.tutorialspoint.com/how-to-play-background-music-in-android-app

farid
  • 300
  • 1
  • 8
  • 1
    Why a service is needed? What are you going to run on it? They are not requesting to run the audio in background, but just stop it until they return back. – Zain Oct 11 '21 at 18:12
  • if you want keep a instance of your player you can use a static class too – farid Oct 12 '21 at 06:12